Merge branch 'release-4.2'
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f71b283..269b044 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -110,7 +110,7 @@
 
     # NOTE See BUG https://github.com/crate-ci/typos/issues/390
   - repo: https://github.com/adhtruong/mirrors-typos
-    rev: v1.36.1
+    rev: v1.38.1
     hooks:
       - id: typos
         # NOTE Override hook's default args to prevent automatic
diff --git a/.typos.toml b/.typos.toml
index 42cf331..db890af 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -20,6 +20,12 @@
 # script accordingly.
 #extend-ignore-identifiers-re=["\\b[0-9a-f]{8,12}\\b"]
 
+[default.extend-identifiers]
+_tru64 = "_tru64"
+__tru64 = "__tru64"
+__TRU64__ = "__TRU64__"
+Tru64 = "Tru64"
+
 # Add repo-wide false positives here in the form of `word = "word"`.
 # Check the manual for details.
 [default.extend-words]
@@ -83,6 +89,9 @@
 [type.py.extend-identifiers]
 typ = "typ"
 
+[type.sh.extend-identifiers]
+xTRUEx = "xTRUEx"
+
 [type.rst]
 extend-ignore-re = [
     # NOTE Allow to mark block of text to exclude from spellchecking as RST comments
diff --git a/Help/command/remove_definitions.rst b/Help/command/remove_definitions.rst
index faad16d..74730e5 100644
--- a/Help/command/remove_definitions.rst
+++ b/Help/command/remove_definitions.rst
@@ -1,11 +1,32 @@
 remove_definitions
 ------------------
 
-Remove -D define flags added by :command:`add_definitions`.
+Removes compile definitions added by :command:`add_compile_definitions`, or
+:command:`add_definitions`:
 
 .. code-block:: cmake
 
-  remove_definitions(-DFOO -DBAR ...)
+  remove_definitions([<definitions>...])
 
-Removes flags (added by :command:`add_definitions`) from the compiler
-command line for sources in the current directory and below.
+The arguments are:
+
+``<definitions>...``
+  Zero or more compile definitions.
+
+This command can be also used to remove any flags added by
+:command:`add_definitions`, but it is intended to remove preprocessor
+definitions passed with ``-D``, or ``/D``.
+
+Examples
+^^^^^^^^
+
+In the following example targets of the current directory scope will have
+only ``BAZ`` and ``QUUX`` compile definitions:
+
+.. code-block:: cmake
+
+  add_compile_definitions(FOO BAR BAZ -DQUUX)
+
+  # ...
+
+  remove_definitions(-DFOO -DBAR)
diff --git a/Help/dev/source.rst b/Help/dev/source.rst
index d8de4e1..c36f334 100644
--- a/Help/dev/source.rst
+++ b/Help/dev/source.rst
@@ -111,7 +111,8 @@
     ``cm::shared_lock``
 
   * ``<cm/type_traits>``:
-    ``cm::enable_if_t``
+    ``cm::conditional_t``, ``cm::decay_t``, ``cm::enable_if_t``,
+    ``cm::remove_cv_t``, ``cm::remove_reference_t``
 
   * ``<cm/unordered_map>``:
     ``cm::cbegin``, ``cm::cend``, ``cm::rbegin``, ``cm::rend``,
@@ -169,7 +170,7 @@
 
   * ``<cm/type_traits>``:
     ``cm::bool_constant``, ``cm::invoke_result_t``, ``cm::invoke_result``,
-    ``cm::void_t``
+    ``cm::is_same_v``, ``cm::void_t``
 
   * ``<cm/unordered_map>``:
     ``cm::size``, ``cm::empty``, ``cm::data``
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index a477897..fd01048 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -34,10 +34,10 @@
 
 .. code-block:: cmake
 
-  cmake_minimum_required(VERSION 3.10...4.1)
+  cmake_minimum_required(VERSION 3.10...4.2)
 
 This uses the ``<min>...<max>`` syntax to enable the ``NEW`` behaviors
-of policies introduced in CMake 4.1 and earlier while only requiring a
+of policies introduced in CMake 4.2 and earlier while only requiring a
 minimum version of CMake 3.10.  The project is expected to work with
 both the ``OLD`` and ``NEW`` behaviors of policies introduced between
 those versions.
diff --git a/Help/release/dev/0-sample-topic.rst b/Help/release/dev/0-sample-topic.rst
new file mode 100644
index 0000000..e4cc01e
--- /dev/null
+++ b/Help/release/dev/0-sample-topic.rst
@@ -0,0 +1,7 @@
+0-sample-topic
+--------------
+
+* This is a sample release note for the change in a topic.
+  Developers should add similar notes for each topic branch
+  making a noteworthy change.  Each document should be named
+  and titled to match the topic name to avoid merge conflicts.
diff --git a/Help/release/dev/UseJava-include-modules.rst b/Help/release/dev/UseJava-include-modules.rst
new file mode 100644
index 0000000..a26e8ee
--- /dev/null
+++ b/Help/release/dev/UseJava-include-modules.rst
@@ -0,0 +1,7 @@
+UseJava-include-modules
+-----------------------
+
+* The :module:`UseJava` module's :command:`add_jar` command now accepts a new
+  option `INCLUDE_MODULES` that adds its arguments to the `--module-path`
+  argument to the Java compiler. This allows building JAR files that use JPMS
+  modules in their build.
diff --git a/Help/release/index.rst b/Help/release/index.rst
index 54190ee..edeab50 100644
--- a/Help/release/index.rst
+++ b/Help/release/index.rst
@@ -7,6 +7,8 @@
   This file should include the adjacent "dev.txt" file
   in development versions but not in release versions.
 
+.. include:: dev.txt
+
 Releases
 ========
 
diff --git a/Modules/CPackIFW.cmake b/Modules/CPackIFW.cmake
index b114430..8826064 100644
--- a/Modules/CPackIFW.cmake
+++ b/Modules/CPackIFW.cmake
@@ -645,7 +645,7 @@
 endmacro()
 
 # Resolve full path to license file
-macro(_cpack_ifw_resolve_lisenses _variable)
+macro(_cpack_ifw_resolve_licenses _variable)
   if(${_variable})
     set(_ifw_license_file FALSE)
     set(_ifw_licenses_fix)
@@ -689,7 +689,7 @@
   cmake_parse_arguments(CPACK_IFW_COMPONENT_${_CPACK_IFWCOMP_UNAME} "${_IFW_OPT}" "${_IFW_ARGS}" "${_IFW_MULTI_ARGS}" ${ARGN})
 
   _cpack_ifw_resolve_script(CPACK_IFW_COMPONENT_${_CPACK_IFWCOMP_UNAME}_SCRIPT)
-  _cpack_ifw_resolve_lisenses(CPACK_IFW_COMPONENT_${_CPACK_IFWCOMP_UNAME}_LICENSES)
+  _cpack_ifw_resolve_licenses(CPACK_IFW_COMPONENT_${_CPACK_IFWCOMP_UNAME}_LICENSES)
   _cpack_ifw_resolve_file_list(CPACK_IFW_COMPONENT_${_CPACK_IFWCOMP_UNAME}_USER_INTERFACES)
   _cpack_ifw_resolve_file_list(CPACK_IFW_COMPONENT_${_CPACK_IFWCOMP_UNAME}_TRANSLATIONS)
 
@@ -730,7 +730,7 @@
   cmake_parse_arguments(CPACK_IFW_COMPONENT_GROUP_${_CPACK_IFWGRP_UNAME} "${_IFW_OPT}" "${_IFW_ARGS}" "${_IFW_MULTI_ARGS}" ${ARGN})
 
   _cpack_ifw_resolve_script(CPACK_IFW_COMPONENT_GROUP_${_CPACK_IFWGRP_UNAME}_SCRIPT)
-  _cpack_ifw_resolve_lisenses(CPACK_IFW_COMPONENT_GROUP_${_CPACK_IFWGRP_UNAME}_LICENSES)
+  _cpack_ifw_resolve_licenses(CPACK_IFW_COMPONENT_GROUP_${_CPACK_IFWGRP_UNAME}_LICENSES)
   _cpack_ifw_resolve_file_list(CPACK_IFW_COMPONENT_GROUP_${_CPACK_IFWGRP_UNAME}_USER_INTERFACES)
   _cpack_ifw_resolve_file_list(CPACK_IFW_COMPONENT_GROUP_${_CPACK_IFWGRP_UNAME}_TRANSLATIONS)
 
diff --git a/Modules/CPackIFWConfigureFile.cmake b/Modules/CPackIFWConfigureFile.cmake
index f92fc10..bed78f9 100644
--- a/Modules/CPackIFWConfigureFile.cmake
+++ b/Modules/CPackIFWConfigureFile.cmake
@@ -7,8 +7,8 @@
 
 .. versionadded:: 3.8
 
-This module defines :command:`configure_file` similar command to
-configure file templates prepared in QtIFW/SDK/Creator style.
+This module provides a command similar to :command:`configure_file` for
+configuring file templates prepared in QtIFW/SDK/Creator style.
 
 Load this module in a CMake project with:
 
@@ -19,21 +19,100 @@
 Commands
 ^^^^^^^^
 
-The module defines the following commands:
+This module provides the following command:
 
 .. command:: cpack_ifw_configure_file
 
-  Copy a file to another location and modify its contents.
+  Copies a file template to output file and substitutes variable values
+  referenced as ``%{VAR}`` or ``%VAR%`` from the input file template
+  content:
 
   .. code-block:: cmake
 
     cpack_ifw_configure_file(<input> <output>)
 
-  Copies an ``<input>`` file to an ``<output>`` file and substitutes variable
-  values referenced as ``%{VAR}`` or ``%VAR%`` in the input file content.
+  ``<input>``
+    Input file template.  If given as a relative path, it is interpreted as
+    relative to the current source directory
+    (:variable:`CMAKE_CURRENT_SOURCE_DIR`).
+
+  ``<output>``
+    Output file.  If given as a relative path, it is interpreted as relative
+    to the current binary directory (:variable:`CMAKE_CURRENT_BINARY_DIR`).
+
+  Qt Installer Framework (QtIFW) uses ``@`` characters for embedding
+  predefined variables (``TargetDir``, ``StartMenuDir``, etc.) in Qt
+  installer scripts:
+
+  .. code-block:: javascript
+    :caption: ``example.qs``
+
+    component.addOperation(
+      "CreateShortcut",
+      "@TargetDir@/example.com.html",
+      "@StartMenuDir@/Example Web Site.lnk"
+    );
+
+  The purpose of this command is to preserve the QtIFW predefined variables
+  containing the ``@`` characters (``@VAR@``), and instead use the ``%``
+  characters for template placeholders (``%VAR%``, ``%{VAR}``) in
+  Qt/IFW/SDK/Creator templates.  The :command:`configure_file` command
+  would otherwise replace all variable references containing the ``@``
+  characters.
+
   Each variable reference will be replaced with the current value of the
   variable, or the empty string if the variable is not defined.
 
+Examples
+^^^^^^^^
+
+In the following example this module is used to create an IFW component
+script from a given template file ``qt.tools.foo.qs.in``, where
+``%FOO_DOC_DIR%`` variable reference will be replaced by the values of
+the ``FOO_DOC_DIR`` CMake variable.
+
+.. code-block:: cmake
+  :caption: ``CMakeLists.txt``
+
+  cmake_minimum_required(VERSION 3.8)
+
+  project(Foo)
+
+  # ...
+
+  include(CPackIFWConfigureFile)
+
+  set(FOO_DOC_DIR "doc/foo")
+
+  cpack_ifw_configure_file(qt.tools.foo.qs.in qt.tools.foo.qs)
+
+.. code-block:: javascript
+  :caption: ``qt.tools.foo.qs.in``
+
+  function Component()
+  {
+  }
+
+  Component.prototype.createOperations = function()
+  {
+    if (installer.value("os") === "win") {
+      component.addOperation(
+        "CreateShortcut",
+        "@TargetDir@/%FOO_DOC_DIR%/example.com.html",
+        "@StartMenuDir@/Example Web Site.lnk"
+      );
+    }
+
+    component.createOperations();
+  }
+
+  // ...
+
+See Also
+^^^^^^^^
+
+* The :cpack_gen:`CPack IFW Generator`.
+* The :module:`CPackIFW` module.
 #]=======================================================================]
 
 if(NOT DEFINED CPackIFWConfigureFile_CMake_INCLUDED)
diff --git a/Modules/FindMatlab.cmake b/Modules/FindMatlab.cmake
index 8acc6f3..231700a 100644
--- a/Modules/FindMatlab.cmake
+++ b/Modules/FindMatlab.cmake
@@ -5,308 +5,767 @@
 FindMatlab
 ----------
 
-Finds Matlab or Matlab Compiler Runtime (MCR) and provides Matlab tools,
+Finds MATLAB or MATLAB Compiler Runtime (MCR) and provides its tools,
 libraries and compilers to CMake:
 
 .. code-block:: cmake
 
-  find_package(Matlab [<version>] [COMPONENTS <components>...] [...])
+  find_package(Matlab [<version>] [COMPONENTS <components>...] [REGISTRY_VIEW <view>] [...])
 
-This package primary purpose is to find the libraries associated with Matlab
-or the MCR in order to be able to build Matlab extensions (mex files). It
-can also be used:
+The primary purpose of this module is to find the libraries associated with
+MATLAB or the MCR in order to be able to build MATLAB extensions (MEX files).
+It can also be used:
 
-* to run specific commands in Matlab in case Matlab is available
-* for declaring Matlab unit test
-* to retrieve various information from Matlab (mex extensions, versions and
+* to run specific commands in MATLAB in case MATLAB is available
+* for declaring MATLAB unit tests
+* to retrieve various information from MATLAB (MEX extensions, versions and
   release queries, ...)
 
 .. versionadded:: 3.12
-  Added Matlab Compiler Runtime (MCR) support.
-
-The module supports the following components:
-
-* ``ENG_LIBRARY`` and ``MAT_LIBRARY``: respectively the ``ENG`` and ``MAT``
-  libraries of Matlab
-* ``MAIN_PROGRAM`` the Matlab binary program. Note that this component is not
-  available on the MCR version, and will yield an error if the MCR is found
-  instead of the regular Matlab installation.
-* ``MEX_COMPILER`` the MEX compiler.
-* ``MCC_COMPILER`` the MCC compiler, included with the Matlab Compiler add-on.
-* ``SIMULINK`` the Simulink environment.
-
-.. versionadded:: 3.7
-  Added the ``MAT_LIBRARY`` component.
-
-.. versionadded:: 3.13
-  Added the ``ENGINE_LIBRARY``, ``DATAARRAY_LIBRARY`` and ``MCC_COMPILER``
-  components.
-
-.. versionchanged:: 3.14
-  Removed the ``MX_LIBRARY``, ``ENGINE_LIBRARY`` and ``DATAARRAY_LIBRARY``
-  components.  These libraries are found unconditionally.
+  MATLAB Compiler Runtime (MCR) support.
 
 .. versionadded:: 3.30
-  Added support for specifying a version range to :command:`find_package` and
-  added support for specifying ``REGISTRY_VIEW`` to :command:`find_package`,
+  Support for specifying a version range to :command:`find_package`,
+  support for specifying ``REGISTRY_VIEW`` argument to
+  :command:`find_package`,
   :command:`matlab_extract_all_installed_versions_from_registry` and
-  :command:`matlab_get_all_valid_matlab_roots_from_registry`. The default
-  behavior remained unchanged, by using the registry view ``TARGET``.
+  :command:`matlab_get_all_valid_matlab_roots_from_registry` commands.
+  The default behavior remained unchanged, by using the registry view
+  ``TARGET``.
 
 .. note::
 
-  The version given to the :command:`find_package` directive is the Matlab
-  **version**, which should not be confused with the Matlab *release* name
-  (e.g. `R2023b`).
-  The :command:`matlab_get_version_from_release_name` and
-  :command:`matlab_get_release_name_from_version` provide a mapping
-  between the release name and the version.
+  The version given to the :command:`find_package` argument is the MATLAB
+  *version*, which should not be confused with the MATLAB *release name*
+  (e.g. ``R2023b``).  The :command:`matlab_get_version_from_release_name`
+  and :command:`matlab_get_release_name_from_version` commands provide a
+  mapping between the release name and the version.
 
-The variable :variable:`Matlab_ROOT_DIR` may be specified in order to give
-the path of the desired Matlab version. Otherwise, the behavior is platform
-specific:
+Components
+^^^^^^^^^^
 
-* Windows: The installed versions of Matlab/MCR are retrieved from the
-  Windows registry. The ``REGISTRY_VIEW`` argument may optionally be specified
-  to manually control whether 32bit or 64bit versions shall be searched for.
-* macOS: The installed versions of Matlab/MCR are given by the MATLAB
-  default installation paths under ``$HOME/Applications`` and ``/Applications``.
-  If no such application is found, it falls back to the one that might be
-  accessible from the ``PATH``.
-* Unix: The desired Matlab should be accessible from the ``PATH``. This does
-  not work for MCR installation and :variable:`Matlab_ROOT_DIR` should be
-  specified on this platform.
+This module supports optional components which can be specified using the
+:command:`find_package` command:
 
-Additional information is provided when :variable:`MATLAB_FIND_DEBUG` is set.
-When a Matlab/MCR installation is found automatically and the ``MATLAB_VERSION``
-is not given, the version is queried from Matlab directly (on Windows this
-may pop up a Matlab window) or from the MCR installation.
+.. code-block:: cmake
 
-The mapping of the release names and the version of Matlab is performed by
-defining pairs (name, version).  The variable
-:variable:`MATLAB_ADDITIONAL_VERSIONS` may be provided before the call to
-the :command:`find_package` in order to handle additional versions.
+  find_package(Matlab [COMPONENTS <components>...])
 
-A Matlab scripts can be added to the set of tests using the
-:command:`matlab_add_unit_test`. By default, the Matlab unit test framework
-will be used (>= 2013a) to run this script, but regular ``.m`` files
-returning an exit code can be used as well (0 indicating a success).
+Supported components include:
 
-Module Input Variables
-^^^^^^^^^^^^^^^^^^^^^^
+``ENG_LIBRARY``
+  .. versionadded:: 3.3
 
-Users or projects may set the following variables to configure the module
-behavior:
+  Finds the ``ENG`` library of MATLAB.
 
-:variable:`Matlab_ROOT <<PackageName>_ROOT>`
-  .. versionadded:: 3.25
+``MAT_LIBRARY``
+  .. versionadded:: 3.7
 
-  Default value for :variable:`Matlab_ROOT_DIR`, the root of the Matlab
-  installation.
+  Finds the ``MAT`` library of MATLAB.
 
-:variable:`Matlab_ROOT_DIR`
-  The root of the Matlab installation.
+``MAIN_PROGRAM``
+  .. versionadded:: 3.3
 
-:variable:`MATLAB_FIND_DEBUG`
-  outputs debug information
+  Finds the MATLAB binary program.  Note that this component is not
+  available on the MCR version, and will yield an error if the MCR is found
+  instead of the regular MATLAB installation.
 
-:variable:`MATLAB_ADDITIONAL_VERSIONS`
-  additional versions of Matlab for the automatic retrieval of the installed
-  versions.
+``MEX_COMPILER``
+  .. versionadded:: 3.3
+
+  Finds the MEX compiler.
+
+``MCC_COMPILER``
+  .. versionadded:: 3.13
+
+  Finds the MCC compiler, included with the MATLAB Compiler add-on.
+
+``SIMULINK``
+  .. versionadded:: 3.3
+
+  Finds the Simulink environment.
+
+Implicitly Found Components
+"""""""""""""""""""""""""""
+
+The following components are always found unconditionally, without needing
+to be specified explicitly:
+
+``MX_LIBRARY``
+  .. versionchanged:: 3.14
+    This component has been removed and is now always found unconditionally.
+
+  .. versionadded:: 3.3
+
+  Finds the MATLAB mx library.
+
+``ENGINE_LIBRARY``
+  .. versionchanged:: 3.14
+    This component has been removed and is now always found unconditionally.
+
+  .. versionadded:: 3.13
+
+  Finds the MATLAB engine library.
+
+``DATAARRAY_LIBRARY``
+  .. versionchanged:: 3.14
+    This component has been removed and is now always found unconditionally.
+
+  .. versionadded:: 3.13
+
+  Finds the C++ MATLAB data array library.
+
+If no components are specified, the module looks for the ``MX_LIBRARY``,
+``ENGINE_LIBRARY``, and ``DATAARRAY_LIBRARY`` by default.
 
 Imported Targets
 ^^^^^^^^^^^^^^^^
 
-.. versionadded:: 3.22
-
-This module defines the following :prop_tgt:`IMPORTED` targets:
+This module provides the following :ref:`Imported Targets`:
 
 ``Matlab::mex``
-  The ``mex`` library, always available for MATLAB installations. Available for
-  MCR installations if provided by MCR.
+  .. versionadded:: 3.22
+
+  Target encapsulating the ``mex`` library usage requirements, always
+  available for MATLAB installations.  Available for MCR installations if
+  provided by MCR.
 
 ``Matlab::mx``
-  The mx library of Matlab (arrays), always available for MATLAB installations.
-  Available for MCR installations if provided by MCR.
+  .. versionadded:: 3.22
+
+  Target encapsulating the usage requirements of the mx library of MATLAB
+  (arrays), always available for MATLAB installations.  Available for MCR
+  installations if provided by MCR.
 
 ``Matlab::eng``
-  Matlab engine library. Available only if the ``ENG_LIBRARY`` component
-  is requested.
+  .. versionadded:: 3.22
+
+  Target encapsulating the MATLAB engine library usage requirements.
+  Available only if the ``ENG_LIBRARY`` component is requested.
 
 ``Matlab::mat``
-  Matlab matrix library. Available only if the ``MAT_LIBRARY`` component
-  is requested.
+  .. versionadded:: 3.22
+
+  Target encapsulating the MATLAB matrix library usage requirements.
+  Available only if the ``MAT_LIBRARY`` component is requested.
 
 ``Matlab::MatlabEngine``
-  Matlab C++ engine library, always available for MATLAB R2018a and newer.
-  Available for MCR installations if provided by MCR.
+  .. versionadded:: 3.22
+
+  Target encapsulating the MATLAB C++ engine library usage requirements,
+  always available for MATLAB R2018a and newer.  Available for MCR
+  installations if provided by MCR.
 
 ``Matlab::MatlabDataArray``
-  Matlab C++ data array library, always available for MATLAB R2018a and newer.
-  Available for MCR installations if provided by MCR.
+  .. versionadded:: 3.22
 
-Variables defined by the module
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  Target encapsulating the MATLAB C++ data array library usage requirements,
+  always available for MATLAB R2018a and newer.  Available for MCR
+  installations if provided by MCR.
 
-Result variables
-""""""""""""""""
+Result Variables
+^^^^^^^^^^^^^^^^
+
+This module defines the following variables:
 
 ``Matlab_FOUND``
   .. versionadded:: 3.3
 
-  Boolean indicating whether the (requested version of) Matlab installation
-  was found.  All variables below are defined if Matlab is found.
+  Boolean indicating whether the (requested version of) MATLAB installation
+  was found.  All variables below are defined if MATLAB is found.
 
 ``Matlab_VERSION``
   .. versionadded:: 3.27
 
-  the numerical version (e.g. 23.2.0) of Matlab found. Not to be confused with
-  Matlab release name (e.g. R2023b) that can be obtained with
-  :command:`matlab_get_release_name_from_version`.
+  The numerical version (e.g. ``23.2.0``) of MATLAB found.  Not to be
+  confused with MATLAB release name (e.g. ``R2023b``) that can be obtained
+  with :command:`matlab_get_release_name_from_version`.
+
+  When a MATLAB/MCR installation is found automatically and the
+  ``Matlab_VERSION`` is not given, the version is queried from MATLAB
+  directly (on Windows this may pop up a MATLAB window) or from the MCR
+  installation.
+
 ``Matlab_ROOT_DIR``
-  the final root of the Matlab installation determined by the FindMatlab
-  module.
+  .. versionadded:: 3.3
+
+  The final root of the MATLAB installation determined by this module.
+
 ``Matlab_MAIN_PROGRAM``
-  the Matlab binary program. Available only if the component ``MAIN_PROGRAM``
-  is given in the :command:`find_package` directive.
+  .. versionadded:: 3.3
+
+  The MATLAB binary program.  Available only if the component ``MAIN_PROGRAM``
+  is given in the :command:`find_package` argument.
+
 ``Matlab_INCLUDE_DIRS``
- the path of the Matlab libraries headers
+  .. versionadded:: 3.3
+
+  The path of the MATLAB libraries headers.
+
 ``Matlab_MEX_LIBRARY``
-  library for mex, always available for MATLAB installations. Available for MCR
-  installations if provided by MCR.
+  Library for MEX, always available for MATLAB installations.  Available
+  for MCR installations if provided by MCR.
+
 ``Matlab_MX_LIBRARY``
-  mx library of Matlab (arrays), always available for MATLAB installations.
-  Available for MCR installations if provided by MCR.
+  The mx library of MATLAB (arrays), always available for MATLAB
+  installations.  Available for MCR installations if provided by MCR.
+
 ``Matlab_ENG_LIBRARY``
-  Matlab engine library. Available only if the component ``ENG_LIBRARY``
+  MATLAB engine library.  Available only if the component ``ENG_LIBRARY``
   is requested.
+
 ``Matlab_MAT_LIBRARY``
-  Matlab matrix library. Available only if the component ``MAT_LIBRARY``
+  .. versionadded:: 3.7
+
+  Matlab matrix library.  Available only if the component ``MAT_LIBRARY``
   is requested.
+
 ``Matlab_ENGINE_LIBRARY``
   .. versionadded:: 3.13
 
   Matlab C++ engine library, always available for MATLAB R2018a and newer.
   Available for MCR installations if provided by MCR.
+
 ``Matlab_DATAARRAY_LIBRARY``
   .. versionadded:: 3.13
 
-  Matlab C++ data array library, always available for MATLAB R2018a and newer.
-  Available for MCR installations if provided by MCR.
+  Matlab C++ data array library, always available for MATLAB R2018a and
+  newer.  Available for MCR installations if provided by MCR.
+
 ``Matlab_LIBRARIES``
-  the whole set of libraries of Matlab
+  The whole set of libraries of MATLAB.
+
 ``Matlab_MEX_COMPILER``
-  the mex compiler of Matlab. Currently not used.
-  Available only if the component ``MEX_COMPILER`` is requested.
+  .. versionadded:: 3.3
+
+  The MEX compiler of MATLAB.  Currently not used.  Available only if the
+  component ``MEX_COMPILER`` is requested.
+
 ``Matlab_MCC_COMPILER``
   .. versionadded:: 3.13
 
-  the mcc compiler of Matlab. Included with the Matlab Compiler add-on.
+  The mcc compiler of MATLAB.  Included with the MATLAB Compiler add-on.
   Available only if the component ``MCC_COMPILER`` is requested.
 
-Cached variables
-""""""""""""""""
+Cache Variables
+^^^^^^^^^^^^^^^
+
+The following cache variables may also be set:
 
 ``Matlab_MEX_EXTENSION``
-  the extension of the mex files for the current platform (given by Matlab).
+  .. versionadded:: 3.3
+
+  The extension of the MEX files for the current platform (given by MATLAB).
+
 ``Matlab_ROOT_DIR``
-  the location of the root of the Matlab installation found. If this value
+  .. versionadded:: 3.3
+
+  The location of the root of the MATLAB installation found.  If this value
   is changed by the user, the result variables are recomputed.
 
-Provided commands
-^^^^^^^^^^^^^^^^^
+Input Variables
+^^^^^^^^^^^^^^^
 
-:command:`matlab_get_version_from_release_name`
-  returns the version from the Matlab release name
-:command:`matlab_get_release_name_from_version`
-  returns the release name from the Matlab version
-:command:`matlab_add_mex`
-  adds a target compiling a MEX file.
-:command:`matlab_add_unit_test`
-  adds a Matlab unit test file as a test to the project.
-:command:`matlab_extract_all_installed_versions_from_registry`
-  parses the registry for all Matlab versions. Available on Windows only.
-  The part of the registry parsed is dependent on the host processor
-:command:`matlab_get_all_valid_matlab_roots_from_registry`
-  returns all the possible Matlab or MCR paths, according to a previously
-  given list. Only the existing/accessible paths are kept. This is mainly
-  useful for the searching all possible Matlab installation.
-:command:`matlab_get_mex_suffix`
-  returns the suffix to be used for the mex files
-  (platform/architecture dependent)
-:command:`matlab_get_version_from_matlab_run`
-  returns the version of Matlab/MCR, given the full directory of the Matlab/MCR
-  installation path.
+Users or projects may set the following variables to configure the module
+behavior before calling ``find_package(Matlab)``:
 
+:variable:`Matlab_ROOT <<PackageName>_ROOT>`
+  .. versionadded:: 3.25
 
-Known issues
-^^^^^^^^^^^^
+  Default value for the ``Matlab_ROOT_DIR`` variable, the root of the
+  MATLAB installation.
 
-**Symbol clash in a MEX target**
-  By default, every symbols inside a MEX
-  file defined with the command :command:`matlab_add_mex` have hidden
-  visibility, except for the entry point. This is the default behavior of
-  the MEX compiler, which lowers the risk of symbol collision between the
-  libraries shipped with Matlab, and the libraries to which the MEX file is
-  linking to. This is also the default on Windows platforms.
+``Matlab_ROOT_DIR``
+  .. versionadded:: 3.3
 
-  However, this is not sufficient in certain case, where for instance your
-  MEX file is linking against libraries that are already loaded by Matlab,
-  even if those libraries have different SONAMES.
-  A possible solution is to hide the symbols of the libraries to which the
-  MEX target is linking to. This can be achieved in GNU GCC compilers with
-  the linker option ``-Wl,--exclude-libs,ALL``.
+  The root folder of the MATLAB installation.  If set before the call to
+  :command:`find_package`, the module will look for the components in that
+  path.  If not set, then an automatic search of MATLAB will be performed.
+  If set, it should point to a valid version of MATLAB.
 
-**Tests using GPU resources**
-  in case your MEX file is using the GPU and
-  in order to be able to run unit tests on this MEX file, the GPU resources
-  should be properly released by Matlab. A possible solution is to make
-  Matlab aware of the use of the GPU resources in the session, which can be
-  performed by a command such as ``D = gpuDevice()`` at the beginning of
-  the test script (or via a fixture).
+  This variable may be specified in order to give the path of the desired
+  MATLAB version.  Otherwise, the behavior is platform specific:
 
+  * Windows: The installed versions of MATLAB/MCR are retrieved from the
+    Windows registry.  The ``REGISTRY_VIEW`` argument may optionally be
+    specified to manually control whether 32bit or 64bit versions shall be
+    searched for.
+  * macOS: The installed versions of MATLAB/MCR are given by the MATLAB
+    default installation paths under ``$HOME/Applications`` and
+    ``/Applications``.  If no such application is found, it falls back to
+    the one that might be accessible from the ``PATH``.
+  * Unix: The desired MATLAB should be accessible from the ``PATH``.  This
+    does not work for MCR installation and ``Matlab_ROOT_DIR`` should be
+    specified on this platform.
 
-Reference
-^^^^^^^^^
+``MATLAB_FIND_DEBUG``
+  .. versionadded:: 3.3
 
-.. variable:: Matlab_ROOT_DIR
+  If set to a boolean true, additional debug information is outputted to
+  the console, such as the lookup of MATLAB and the intermediate
+  configuration steps.
 
-   The root folder of the Matlab installation. If set before the call to
-   :command:`find_package`, the module will look for the components in that
-   path. If not set, then an automatic search of Matlab
-   will be performed. If set, it should point to a valid version of Matlab.
+``MATLAB_ADDITIONAL_VERSIONS``
+  .. versionadded:: 3.3
 
-.. variable:: MATLAB_FIND_DEBUG
+  If set, it specifies additional versions of MATLAB for the automatic
+  retrieval of the installed versions that may be handled and looked for.
 
-   If set, the lookup of Matlab and the intermediate configuration steps are
-   outputted to the console.
-
-.. variable:: MATLAB_ADDITIONAL_VERSIONS
-
-  If set, specifies additional versions of Matlab that may be looked for.
-  The variable should be a list of strings, organized by pairs of release
-  name and versions, such as follows:
+  The mapping of the release names and the version of MATLAB is performed
+  by defining pairs (name, version).  The variable should be a list of
+  strings, organized by pairs of release name and versions, such as
+  follows:
 
   .. code-block:: cmake
 
-    set(MATLAB_ADDITIONAL_VERSIONS
+    set(
+      MATLAB_ADDITIONAL_VERSIONS
         "release_name1=corresponding_version1"
         "release_name2=corresponding_version2"
-        ...
-        )
+        # ...
+    )
+
+    find_package(Matlab)
 
   Example:
 
   .. code-block:: cmake
 
-    set(MATLAB_ADDITIONAL_VERSIONS
+    set(
+      MATLAB_ADDITIONAL_VERSIONS
         "R2013b=8.2"
         "R2013a=8.1"
-        "R2012b=8.0")
+        "R2012b=8.0"
+        # ...
+    )
 
-  The order of entries in this list matters when several versions of
-  Matlab are installed. The priority is set according to the ordering in
-  this list.
+    find_package(Matlab)
+
+  The order of entries in this list matters when several versions of MATLAB
+  are installed.  The priority is set according to the ordering in this
+  list.
+
+Commands
+^^^^^^^^
+
+This module provides the following commands:
+
+* :command:`matlab_get_version_from_release_name`
+* :command:`matlab_get_release_name_from_version`
+* :command:`matlab_get_version_from_matlab_run`
+* :command:`matlab_extract_all_installed_versions_from_registry`
+* :command:`matlab_get_all_valid_matlab_roots_from_registry`
+* :command:`matlab_add_mex`
+* :command:`matlab_get_mex_suffix`
+* :command:`matlab_add_unit_test`
+
+.. command:: matlab_get_version_from_release_name
+
+  .. versionadded:: 3.3
+
+  Returns the version of MATLAB from a release name:
+
+  .. code-block:: cmake
+
+    matlab_get_version_from_release_name(<release-name> <version-var>)
+
+  The arguments are:
+
+  ``<release-name>``
+    Input release name string (e.g. ``R2023b``).
+
+  ``<version-var>``
+    The name of the variable in which to store the version of MATLAB.  The
+    output result is e.g. ``23.2.0``.
+
+  .. note::
+
+    This command provides correct versions mappings for MATLAB but not MCR.
+
+.. command:: matlab_get_release_name_from_version
+
+  .. versionadded:: 3.3
+
+  Returns the release name from the version of MATLAB:
+
+  .. code-block:: cmake
+
+    matlab_get_release_name_from_version(<version> <release-name-var>)
+
+  The arguments are:
+
+  ``<version>``
+    Input MATLAB version string (e.g. ``23.2.0``).
+
+  ``<release-name-var>``
+    The name of the variable in which to store the MATLAB release name.  The
+    output result is e.g. ``R2023b``.
+
+  .. note::
+
+    This command provides correct version mappings for MATLAB but not MCR.
+
+.. command:: matlab_get_version_from_matlab_run
+
+  .. versionadded:: 3.3
+
+  Runs the specified MATLAB program and extracts the version of MATLAB/MCR,
+  given the full directory of the MATLAB/MCR installation path:
+
+  .. code-block:: cmake
+
+    matlab_get_version_from_matlab_run(<matlab-binary> <matlab-versions-var>)
+
+  The arguments are:
+
+  ``<matlab-binary>``
+    The path to the ``matlab`` binary executable.
+
+  ``<matlab-versions-var>``
+    The name of the variable in which a list of extracted MATLAB versions
+    are stored.
+
+  If the path provided for the MATLAB installation points to an MCR
+  installation, the version is extracted from the installed files.
+
+.. command:: matlab_extract_all_installed_versions_from_registry
+
+  .. versionadded:: 3.3
+
+  Parses the Windows registry and finds all installed MATLAB versions:
+
+  .. signature::
+    matlab_extract_all_installed_versions_from_registry(<versions-var>
+      [REGISTRY_VIEW <view>])
+    :target: matlab_extract_all_installed_versions_from_registry-keyword
+
+  The arguments are:
+
+  ``<versions-var>``
+    The name of the variable in which to store the list of all MATLAB
+    versions found.
+
+  ``REGISTRY_VIEW <view>``
+    .. versionadded:: 3.30
+
+    Optional registry view argument that provides a more precise interface
+    on how to interact with the Windows Registry.  The argument is passed
+    (or omitted) to :command:`cmake_host_system_information` without
+    further checks or modification.  For example, ``<view>`` value can be
+    one of ``64``, ``32``, ``64_32``, ``32_64``, ``HOST``, ``TARGET``,
+    ``BOTH``.
+
+  The old signature is:
+
+  .. signature::
+    matlab_extract_all_installed_versions_from_registry(<win64> <versions-var>)
+    :target: matlab_extract_all_installed_versions_from_registry-positional
+
+  The arguments are:
+
+  ``<win64>``
+    Boolean whether to search for the 64-bit version of MATLAB.  If set
+    to boolean true, 64-bit registry view will be searched.  If set to
+    boolean false, 32-bit registry view will be searched.  For finer
+    control, use the above signature.
+
+  ``<versions-var>``
+    The name of the variable in which to store the list of all MATLAB
+    versions found.
+
+  This command is available on Windows only.  The part of the registry
+  parsed is dependent on the host processor.
+
+  The returned list contains all versions under
+  ``HKLM\SOFTWARE\Mathworks\MATLAB``,
+  ``HKLM\SOFTWARE\Mathworks\MATLAB Runtime`` and
+  ``HKLM\SOFTWARE\Mathworks\MATLAB Compiler Runtime`` or an empty list in
+  case an error occurred (or nothing found).
+
+  .. note::
+
+    Only the versions are provided.  No check is made over the existence of
+    the installation referenced in the registry.
+
+.. command:: matlab_get_all_valid_matlab_roots_from_registry
+
+  .. versionadded:: 3.3
+
+  Returns all the possible MATLAB or MCR paths, according to a previously
+  given list:
+
+  .. code-block:: cmake
+
+    matlab_get_all_valid_matlab_roots_from_registry(
+      <matlab-versions>
+      <matlab-roots-var>
+      [REGISTRY_VIEW <view>]
+    )
+
+  This command populates the MATLAB root with valid versions of MATLAB or
+  MATLAB Runtime (MCR) and is mainly useful for the searching of all
+  possible MATLAB installations.  Only the existing/accessible paths are
+  kept.
+
+  The arguments are:
+
+  ``<matlab-versions>``
+    A :ref:`semicolon-separated list <CMake Language Lists>` of each of the
+    MATLAB or MCR installations.  Specify it as a single string value.
+
+  ``<matlab-roots-var>``
+    The name of the variable in which to store a list of locations of each
+    of the MATLAB or MCR installations.
+
+    The value of this variable is organized in triplets
+    ``(type,version_number,matlab_root_path)``, where ``type`` indicates
+    either ``MATLAB`` or ``MCR``.
+
+  ``REGISTRY_VIEW <view>``
+    .. versionadded:: 3.30
+
+    Optional registry view argument that provides a more precise interface
+    on how to interact with the Windows Registry.  The argument is passed
+    (or omitted) to :command:`cmake_host_system_information` without
+    further checks or modification.  For example, ``<view>`` value can be
+    one of ``64``, ``32``, ``64_32``, ``32_64``, ``HOST``, ``TARGET``,
+    ``BOTH``.
+
+.. command:: matlab_add_mex
+
+  .. versionadded:: 3.3
+
+  Adds a target that compiles MATLAB MEX target file:
+
+  .. code-block:: cmake
+
+    matlab_add_mex(
+      NAME <name>
+      [EXECUTABLE | MODULE | SHARED]
+      SRC <sources>...
+      [OUTPUT_NAME <output-name>]
+      [DOCUMENTATION <file>]
+      [LINK_TO <targets>...]
+      [R2017b | R2018a]
+      [EXCLUDE_FROM_ALL]
+      [NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES]
+      [...]
+    )
+
+  This commands compiles the given sources with the current tool-chain in
+  order to produce a MEX file.  The final name of the produced output may
+  be specified, as well as additional link libraries, and a documentation
+  entry for the MEX file.  Remaining arguments of the call are passed to
+  the :command:`add_library` or :command:`add_executable` command.
+
+  The arguments are:
+
+  ``NAME <name>``
+    The name of the target.
+
+  ``SRC <sources>...``
+    One or more source files to be compiled.
+
+  ``LINK_TO <targets>...``
+    A list of additional link dependencies.  The target links to ``libmex``
+    and ``libmx`` by default, unless the
+    ``NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES`` option is passed.
+
+  ``OUTPUT_NAME <output-name>``
+    If given, overrides the default name.  The default name is the name of
+    the target without any prefix and with ``Matlab_MEX_EXTENSION`` suffix.
+
+  ``DOCUMENTATION <file>``
+    If given, the ``<file>`` will be considered as being the documentation
+    file for the MEX file.  This file is copied into the same folder without
+    any processing, with the same name as the final MEX file, and with
+    extension ``.m``.  In that case, typing ``help <name>`` in MATLAB
+    prints the documentation contained in this file.
+
+    The documentation file is not processed and should be in the following
+    format:
+
+    .. code-block:: matlab
+
+      % This is the documentation
+      function ret = mex_target_output_name(input1)
+
+  ``R2017b`` or ``R2018a``
+    .. versionadded:: 3.14
+
+    May be given to specify the version of the C API
+    to use: ``R2017b`` specifies the traditional (separate complex) C API,
+    and corresponds to the ``-R2017b`` flag for the ``mex`` command.
+    ``R2018a`` specifies the new interleaved complex C API, and corresponds
+    to the ``-R2018a`` flag for the ``mex`` command.  Ignored for MATLAB
+    versions prior to R2018a.  Defaults to ``R2017b``.
+
+  ``MODULE`` or ``SHARED``
+    .. versionadded:: 3.7
+
+    May be given to specify the type of library to be created.
+
+  ``EXECUTABLE``
+    .. versionadded:: 3.7
+
+    May be given to create an executable instead of a library.  If no type
+    is given explicitly, the default type is ``SHARED``.
+
+  ``EXCLUDE_FROM_ALL``
+    This option has the same meaning as the :prop_tgt:`EXCLUDE_FROM_ALL`
+    target property and is forwarded to the :command:`add_library`, or
+    :command:`add_executable` command.
+
+  ``NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES``
+    .. versionadded:: 3.24
+
+    This option permits to disable the automatic linking of MATLAB
+    libraries, so that only the libraries that are actually required can be
+    linked via the ``LINK_TO`` option.
+
+.. command:: matlab_get_mex_suffix
+
+  .. versionadded:: 3.3
+
+  Returns the extension to be used for the MEX files (the suffix):
+
+  .. code-block:: cmake
+
+    matlab_get_mex_suffix(<matlab-root> <mex-suffix-var>)
+
+  The arguments are:
+
+  ``<matlab-root>``
+    The root of MATLAB/MCR installation.  For example, the value of the
+    ``Matlab_ROOT_DIR`` variable.
+
+  ``<mex-suffix-var>``
+    The name of the variable in which the suffix will be returned.
+
+  This command is platform and architecture dependent.  It should not be
+  called before the appropriate MATLAB root has been found.
+
+.. command:: matlab_add_unit_test
+
+  .. versionadded:: 3.3
+
+  Adds a MATLAB unit test file to the project's test set of CMake/CTest:
+
+  .. code-block:: cmake
+
+    matlab_add_unit_test(
+      NAME <name>
+      UNITTEST_FILE <matlab-file-containing-unittest.m>
+      [CUSTOM_TEST_COMMAND <matlab-command-to-run-as-test>]
+      [UNITTEST_PRECOMMAND <matlab-command-to-run>]
+      [TIMEOUT <timeout>]
+      [ADDITIONAL_PATH <paths>...]
+      [MATLAB_ADDITIONAL_STARTUP_OPTIONS <options>...]
+      [TEST_ARGS <args>...]
+      [NO_UNITTEST_FRAMEWORK]
+      [WORKING_DIRECTORY <dir>]
+    )
+
+  By default, the MATLAB unit test framework will be used (>= 2013a) to run
+  the added MATLAB script, but regular ``.m`` files returning an exit code
+  can be used as well (0 indicating a success).
+
+  This command requires the component ``MAIN_PROGRAM`` and hence is not
+  available for an MCR installation.
+
+  The unit test uses the MATLAB unittest framework (default, available
+  starting MATLAB 2013b+) except if the option ``NO_UNITTEST_FRAMEWORK``
+  is given.
+
+  The command expects one MATLAB test script file to be given.
+  In the case ``NO_UNITTEST_FRAMEWORK`` is given, the unittest script file
+  should contain the script to be run, plus an exit command with the exit
+  value.  This exit value will be passed to the ctest framework (0 success,
+  non 0 failure).  Additional arguments accepted by :command:`add_test` can
+  be passed through ``TEST_ARGS`` (e.g. ``CONFIGURATIONS <config> ...``).
+
+  The arguments are:
+
+  ``NAME <name>``
+    The name of the unittest in ctest.
+
+  ``UNITTEST_FILE <matlab-file-containing-unittest.m>``
+    The MATLAB unittest file.  Its path will be automatically added to the
+    MATLAB path.
+
+  ``CUSTOM_TEST_COMMAND <matlab-command-to-run-as-test>``
+    MATLAB script command to run as the test.
+    If this is not set, then the following is run:
+    ``runtests('matlab_file_name'), exit(max([ans(1,:).Failed]))`` where
+    ``matlab_file_name`` is the ``UNITTEST_FILE`` without the extension.
+
+  ``UNITTEST_PRECOMMAND <matlab-command-to-run>``
+    MATLAB script command to be ran before the file
+    containing the test (e.g. GPU device initialization based on CMake
+    variables).
+
+  ``TIMEOUT <timeout>``
+    The test timeout in seconds.  Defaults to 180 seconds as the
+    MATLAB unit test may hang.
+
+  ``ADDITIONAL_PATH <paths>...``
+    A list of paths to add to the MATLAB path prior to running the unit
+    test.
+
+  ``MATLAB_ADDITIONAL_STARTUP_OPTIONS <options>``
+    A list of additional option in order to run MATLAB from the command
+    line.  The ``-nosplash``, ``-nodesktop``, and ``-nodisplay`` options
+    are always added automatically.
+
+  ``TEST_ARGS <args>...``
+    Additional options provided to the add_test command.  These
+    options are added to the default options (e.g. ``CONFIGURATIONS Release``).
+
+  ``NO_UNITTEST_FRAMEWORK``
+    When set, indicates that the test should not
+    use the unittest framework of MATLAB (available for versions >= R2013a).
+
+  ``WORKING_DIRECTORY <dir>``
+    This will be the working directory for the test.  If specified it will
+    also be the output directory used for the log file of the test run.
+    If not specified the temporary directory ``${CMAKE_BINARY_DIR}/Matlab``
+    will be used as the working directory and the log location.
+
+Known Issues
+^^^^^^^^^^^^
+
+**Symbol clash in a MEX target**
+  By default, every symbol inside a MEX
+  file defined with the command :command:`matlab_add_mex` have hidden
+  visibility, except for the entry point.  This is the default behavior of
+  the MEX compiler, which lowers the risk of symbol collision between the
+  libraries shipped with MATLAB, and the libraries to which the MEX file is
+  linking to.  This is also the default on Windows platforms.
+
+  However, this is not sufficient in certain case, where for instance the
+  MEX file is linking against libraries that are already loaded by MATLAB,
+  even if those libraries have different SONAMES.
+  A possible solution is to hide the symbols of the libraries to which the
+  MEX target is linking to.  This can be achieved in GNU GCC compilers with
+  the linker option ``-Wl,--exclude-libs,ALL``.
+
+**Tests using GPU resources**
+  In case the MEX file is using the GPU and in order to be able to run unit
+  tests on this MEX file, the GPU resources should be properly released by
+  MATLAB.  A possible solution is to make MATLAB aware of the use of the
+  GPU resources in the session, which can be performed by a command, such
+  as ``D = gpuDevice()``, at the beginning of the test script (or via a
+  fixture).
+
+Examples
+^^^^^^^^
+
+Finding MATLAB and linking imported target to a project target:
+
+.. code-block:: cmake
+
+  find_package(Matlab)
+  target_link_libraries(example PRIVATE Matlab::mx)
 #]=======================================================================]
 
 cmake_policy(PUSH)
@@ -370,23 +829,7 @@
 
 file(MAKE_DIRECTORY "${_matlab_temporary_folder}")
 
-#[=======================================================================[.rst:
-.. command:: matlab_get_version_from_release_name
-
-  .. code-block:: cmake
-
-    matlab_get_version_from_release_name(release version)
-
-  * Input: ``release`` is the release name (e.g. R2023b)
-  * Output: ``version`` is the version of Matlab (e.g. 23.2.0)
-
-  Returns the version of Matlab from a release name
-
-  .. note::
-
-    This command provides correct versions mappings for Matlab but not MCR.
-
-#]=======================================================================]
+# Public.
 macro(matlab_get_version_from_release_name release_name version_name)
 
   string(REGEX MATCHALL "${release_name}=([0-9]+\\.[0-9]+)" _matched ${MATLAB_VERSIONS_MAPPING})
@@ -401,24 +844,7 @@
 
 endmacro()
 
-
-#[=======================================================================[.rst:
-.. command:: matlab_get_release_name_from_version
-
-  .. code-block:: cmake
-
-    matlab_get_release_name_from_version(version release_name)
-
-  * Input: ``version`` is the version of Matlab (e.g. 23.2.0)
-  * Output: ``release_name`` is the release name (R2023b)
-
-  Returns the release name from the version of Matlab
-
-  .. note::
-
-    This command provides correct version mappings for Matlab but not MCR.
-
-#]=======================================================================]
+# Public.
 function(matlab_get_release_name_from_version version release_name)
 
   # only the major.minor version is used
@@ -435,7 +861,6 @@
 
 endfunction()
 
-
 # extracts all the supported release names (R2022b...) of Matlab
 # internal use
 macro(matlab_get_supported_releases list_releases)
@@ -451,8 +876,6 @@
   unset(_var)
 endmacro()
 
-
-
 # extracts all the supported versions of Matlab
 # internal use
 macro(matlab_get_supported_versions list_versions)
@@ -468,47 +891,7 @@
   unset(_var)
 endmacro()
 
-
-#[=======================================================================[.rst:
-.. command:: matlab_extract_all_installed_versions_from_registry
-
-  This function parses the Windows registry and finds the Matlab versions that
-  are installed. The found versions are stored in a given ``<versions-var>``.
-
-  .. signature::
-    matlab_extract_all_installed_versions_from_registry(<versions-var>
-      [REGISTRY_VIEW view])
-    :target: matlab_extract_all_installed_versions_from_registry-keyword
-
-    .. versionadded:: 3.30
-
-    * Output: ``<versions-var>`` is a list of all the versions of Matlab found
-    * Input: ``REGISTRY_VIEW`` Optional registry view to use for registry
-      interaction. The argument is passed (or omitted) to
-      :command:`cmake_host_system_information` without further checks or
-      modification.
-
-  .. signature::
-    matlab_extract_all_installed_versions_from_registry(<win64> <versions-var>)
-    :target: matlab_extract_all_installed_versions_from_registry-positional
-
-    * Input: ``win64`` is a boolean to search for the 64 bit version of
-      Matlab. Set to ``ON`` to use the 64bit registry view or ``OFF`` to use the
-      32bit registry view. If finer control is needed, see signature above.
-    * Output: ``<versions-var>`` is a list of all the versions of Matlab found
-
-  The returned list contains all versions under
-  ``HKLM\SOFTWARE\Mathworks\MATLAB``,
-  ``HKLM\SOFTWARE\Mathworks\MATLAB Runtime`` and
-  ``HKLM\SOFTWARE\Mathworks\MATLAB Compiler Runtime`` or an empty list in
-  case an error occurred (or nothing found).
-
-  .. note::
-
-    Only the versions are provided. No check is made over the existence of the
-    installation referenced in the registry,
-
-#]=======================================================================]
+# Public.
 function(matlab_extract_all_installed_versions_from_registry win64_or_matlab_versions)
 
   if(NOT CMAKE_HOST_WIN32)
@@ -562,8 +945,6 @@
 
 endfunction()
 
-
-
 # (internal)
 macro(extract_matlab_versions_from_registry_brute_force matlab_versions)
   # get the supported versions
@@ -579,32 +960,7 @@
   set(${matlab_versions} ${matlab_supported_versions})
 endmacro()
 
-
-#[=======================================================================[.rst:
-.. command:: matlab_get_all_valid_matlab_roots_from_registry
-
-  Populates the Matlab root with valid versions of Matlab or
-  Matlab Runtime (MCR).
-  The returned matlab_roots is organized in triplets
-  ``(type,version_number,matlab_root_path)``, where ``type``
-  indicates either ``MATLAB`` or ``MCR``.
-
-  .. code-block:: cmake
-
-    matlab_get_all_valid_matlab_roots_from_registry(matlab_versions matlab_roots [REGISTRY_VIEW view])
-
-  * Input: ``matlab_versions`` of each of the Matlab or MCR installations
-  * Output: ``matlab_roots`` location of each of the Matlab or MCR installations
-  * Input: ``REGISTRY_VIEW`` Optional registry view to use for registry
-    interaction. The argument is passed (or omitted) to
-    :command:`cmake_host_system_information` without further checks or
-    modification.
-
-  .. versionadded:: 3.30
-    The optional ``REGISTRY_VIEW`` argument was added to provide a more precise
-    interface on how to interact with the Windows Registry.
-
-#]=======================================================================]
+# Public.
 function(matlab_get_all_valid_matlab_roots_from_registry matlab_versions matlab_roots)
 
   # The matlab_versions comes either from
@@ -672,20 +1028,7 @@
   set(${matlab_roots} ${_matlab_roots_list} PARENT_SCOPE)
 endfunction()
 
-#[=======================================================================[.rst:
-.. command:: matlab_get_mex_suffix
-
-  Returns the extension of the mex files (the suffixes).
-  This function should not be called before the appropriate Matlab root has
-  been found.
-
-  .. code-block:: cmake
-
-    matlab_get_mex_suffix(matlab_root mex_suffix)
-
-  * Input: ``matlab_root`` root of Matlab/MCR install e.g. ``Matlab_ROOT_DIR``
-  * Output: ``mex_suffix`` variable name in which the suffix will be returned.
-#]=======================================================================]
+# Public.
 function(matlab_get_mex_suffix matlab_root mex_suffix)
 
   # find_program does not consider script suffix .bat for Matlab mexext.bat on Windows
@@ -761,21 +1104,7 @@
   set(${mex_suffix} ${_matlab_mex_extension} PARENT_SCOPE)
 endfunction()
 
-
-#[=======================================================================[.rst:
-.. command:: matlab_get_version_from_matlab_run
-
-  This function runs Matlab program specified on arguments and extracts its
-  version. If the path provided for the Matlab installation points to an MCR
-  installation, the version is extracted from the installed files.
-
-  .. code-block:: cmake
-
-    matlab_get_version_from_matlab_run(matlab_binary_path matlab_list_versions)
-
-  * Input: ``matlab_binary_path`` path of the `matlab` binary executable
-  * Output: ``matlab_list_versions`` the version extracted from Matlab
-#]=======================================================================]
+# Public.
 function(matlab_get_version_from_matlab_run matlab_binary_program matlab_list_versions)
 
   set(${matlab_list_versions} "" PARENT_SCOPE)
@@ -926,77 +1255,7 @@
 
 endfunction()
 
-#[=======================================================================[.rst:
-.. command:: matlab_add_unit_test
-
-  Adds a Matlab unit test to the test set of cmake/ctest.
-  This command requires the component ``MAIN_PROGRAM`` and hence is not
-  available for an MCR installation.
-
-  The unit test uses the Matlab unittest framework (default, available
-  starting Matlab 2013b+) except if the option ``NO_UNITTEST_FRAMEWORK``
-  is given.
-
-  The function expects one Matlab test script file to be given.
-  In the case ``NO_UNITTEST_FRAMEWORK`` is given, the unittest script file
-  should contain the script to be run, plus an exit command with the exit
-  value. This exit value will be passed to the ctest framework (0 success,
-  non 0 failure). Additional arguments accepted by :command:`add_test` can be
-  passed through ``TEST_ARGS`` (eg. ``CONFIGURATION <config> ...``).
-
-  .. code-block:: cmake
-
-    matlab_add_unit_test(
-        NAME <name>
-        UNITTEST_FILE matlab_file_containing_unittest.m
-        [CUSTOM_TEST_COMMAND matlab_command_to_run_as_test]
-        [UNITTEST_PRECOMMAND matlab_command_to_run]
-        [TIMEOUT timeout]
-        [ADDITIONAL_PATH path1 [path2 ...]]
-        [MATLAB_ADDITIONAL_STARTUP_OPTIONS option1 [option2 ...]]
-        [TEST_ARGS arg1 [arg2 ...]]
-        [NO_UNITTEST_FRAMEWORK]
-        )
-
-  Function Parameters:
-
-  ``NAME``
-    name of the unittest in ctest.
-  ``UNITTEST_FILE``
-    the matlab unittest file. Its path will be automatically
-    added to the Matlab path.
-  ``CUSTOM_TEST_COMMAND``
-    Matlab script command to run as the test.
-    If this is not set, then the following is run:
-    ``runtests('matlab_file_name'), exit(max([ans(1,:).Failed]))``
-    where ``matlab_file_name`` is the ``UNITTEST_FILE`` without the extension.
-  ``UNITTEST_PRECOMMAND``
-    Matlab script command to be ran before the file
-    containing the test (eg. GPU device initialization based on CMake
-    variables).
-  ``TIMEOUT``
-    the test timeout in seconds. Defaults to 180 seconds as the
-    Matlab unit test may hang.
-  ``ADDITIONAL_PATH``
-    a list of paths to add to the Matlab path prior to
-    running the unit test.
-  ``MATLAB_ADDITIONAL_STARTUP_OPTIONS``
-    a list of additional option in order
-    to run Matlab from the command line.
-    ``-nosplash -nodesktop -nodisplay`` are always added.
-  ``TEST_ARGS``
-    Additional options provided to the add_test command. These
-    options are added to the default options (eg. "CONFIGURATIONS Release")
-  ``NO_UNITTEST_FRAMEWORK``
-    when set, indicates that the test should not
-    use the unittest framework of Matlab (available for versions >= R2013a).
-  ``WORKING_DIRECTORY``
-    This will be the working directory for the test. If specified it will
-    also be the output directory used for the log file of the test run.
-    If not specified the temporary directory ``${CMAKE_BINARY_DIR}/Matlab`` will
-    be used as the working directory and the log location.
-
-#]=======================================================================]
+# Public.
 function(matlab_add_unit_test)
 
   if(NOT Matlab_MAIN_PROGRAM)
@@ -1049,93 +1308,7 @@
            )
 endfunction()
 
-
-#[=======================================================================[.rst:
-.. command:: matlab_add_mex
-
-  Adds a Matlab MEX target.
-  This commands compiles the given sources with the current tool-chain in
-  order to produce a MEX file. The final name of the produced output may be
-  specified, as well as additional link libraries, and a documentation entry
-  for the MEX file. Remaining arguments of the call are passed to the
-  :command:`add_library` or :command:`add_executable` command.
-
-  .. code-block:: cmake
-
-     matlab_add_mex(
-         NAME <name>
-         [EXECUTABLE | MODULE | SHARED]
-         SRC src1 [src2 ...]
-         [OUTPUT_NAME output_name]
-         [DOCUMENTATION file.txt]
-         [LINK_TO target1 target2 ...]
-         [R2017b | R2018a]
-         [EXCLUDE_FROM_ALL]
-         [NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES]
-         [...]
-     )
-
-  Function Parameters:
-
-  ``NAME``
-    name of the target.
-  ``SRC``
-    list of source files.
-  ``LINK_TO``
-    a list of additional link dependencies.  The target links to ``libmex``
-    and ``libmx`` by default, unless the
-    ``NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES`` option is passed.
-  ``OUTPUT_NAME``
-    if given, overrides the default name. The default name is
-    the name of the target without any prefix and
-    with ``Matlab_MEX_EXTENSION`` suffix.
-  ``DOCUMENTATION``
-    if given, the file ``file.txt`` will be considered as
-    being the documentation file for the MEX file. This file is copied into
-    the same folder without any processing, with the same name as the final
-    mex file, and with extension `.m`. In that case, typing ``help <name>``
-    in Matlab prints the documentation contained in this file.
-  ``R2017b`` or ``R2018a``
-    .. versionadded:: 3.14
-
-    May be given to specify the version of the C API
-    to use: ``R2017b`` specifies the traditional (separate complex) C API,
-    and corresponds to the ``-R2017b`` flag for the `mex` command. ``R2018a``
-    specifies the new interleaved complex C API, and corresponds to the
-    ``-R2018a`` flag for the `mex` command. Ignored if MATLAB version prior
-    to R2018a. Defaults to ``R2017b``.
-
-  ``MODULE`` or ``SHARED``
-    .. versionadded:: 3.7
-
-    May be given to specify the type of library to be
-    created.
-
-  ``EXECUTABLE``
-    .. versionadded:: 3.7
-
-    May be given to create an executable instead of
-    a library. If no type is given explicitly, the type is ``SHARED``.
-  ``EXCLUDE_FROM_ALL``
-    This option has the same meaning as for :prop_tgt:`EXCLUDE_FROM_ALL` and
-    is forwarded to :command:`add_library` or :command:`add_executable`
-    commands.
-  ``NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES``
-    .. versionadded:: 3.24
-
-    This option permits to disable the automatic linking of MATLAB
-    libraries, so that only the libraries that are actually required can be
-    linked via the ``LINK_TO`` option.
-
-  The documentation file is not processed and should be in the following
-  format:
-
-  ::
-
-    % This is the documentation
-    function ret = mex_target_output_name(input1)
-
-#]=======================================================================]
+# Public.
 function(matlab_add_mex)
 
   set(options EXECUTABLE MODULE SHARED R2017b R2018a EXCLUDE_FROM_ALL NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES)
@@ -1331,7 +1504,6 @@
 
 endfunction()
 
-
 # (internal)
 # Used to get the version of matlab, using caching. This basically transforms the
 # output of the root list, with possible unknown version, to a version
@@ -1477,7 +1649,6 @@
 
 endfunction()
 
-
 function(_Matlab_VersionInfoXML matlab_root _version)
 
   set(_ver "unknown")
@@ -1496,7 +1667,6 @@
 
 endfunction()
 
-
 # Utility function for finding Matlab or MCR on Win32
 function(_Matlab_find_instances_win32 matlab_roots)
   # On WIN32, we look for Matlab installation in the registry
@@ -1819,7 +1989,6 @@
 endif()
 
 
-
 set(MATLAB_INCLUDE_DIR_TO_LOOK ${Matlab_ROOT_DIR}/extern/include)
 if(CMAKE_SIZEOF_VOID_P EQUAL 4)
   set(_matlab_current_suffix ${_matlab_bin_suffix_32bits})
@@ -1851,8 +2020,6 @@
   message(STATUS "[MATLAB] _matlab_lib_prefix_for_search = ${_matlab_lib_prefix_for_search} | _matlab_lib_dir_for_search = ${_matlab_lib_dir_for_search}")
 endif()
 
-
-
 # internal
 # This small stub around find_library is to prevent any pollution of CMAKE_FIND_LIBRARY_PREFIXES in the global scope.
 # This is the function to be used below instead of the find_library directives.
diff --git a/Modules/GoogleTest.cmake b/Modules/GoogleTest.cmake
index 2fded3f..5f4eaf8 100644
--- a/Modules/GoogleTest.cmake
+++ b/Modules/GoogleTest.cmake
@@ -332,7 +332,7 @@
 
 # Save project's policies
 block(SCOPE_FOR POLICIES)
-cmake_policy(VERSION 3.30)
+cmake_policy(VERSION 4.2)
 
 #------------------------------------------------------------------------------
 function(gtest_add_tests)
diff --git a/Modules/GoogleTestAddTests.cmake b/Modules/GoogleTestAddTests.cmake
index ceaba3e..8622424 100644
--- a/Modules/GoogleTestAddTests.cmake
+++ b/Modules/GoogleTestAddTests.cmake
@@ -1,8 +1,7 @@
 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
 # file LICENSE.rst or https://cmake.org/licensing for details.
 
-cmake_minimum_required(VERSION 3.30)
-cmake_policy(SET CMP0174 NEW)   # TODO: Remove this when we can update the above to 3.31
+cmake_minimum_required(VERSION 4.2)
 
 function(add_command name test_name)
   set(args "")
diff --git a/Modules/Internal/CPack/CPackRPM.cmake b/Modules/Internal/CPack/CPackRPM.cmake
index b733cfe..d0454e1 100644
--- a/Modules/Internal/CPack/CPackRPM.cmake
+++ b/Modules/Internal/CPack/CPackRPM.cmake
@@ -445,13 +445,13 @@
 endfunction()
 
 function(cpack_rpm_symlink_add_for_relocation_script PACKAGE_PREFIXES SYMLINK SYMLINK_RELOCATION_PATHS POINT POINT_RELOCATION_PATHS)
-  list(LENGTH SYMLINK_RELOCATION_PATHS SYMLINK_PATHS_COUTN)
+  list(LENGTH SYMLINK_RELOCATION_PATHS SYMLINK_PATHS_COUNT)
   list(LENGTH POINT_RELOCATION_PATHS POINT_PATHS_COUNT)
 
   list(APPEND _RPM_RELOCATION_SCRIPT_PAIRS "${SYMLINK}:${POINT}")
   list(LENGTH _RPM_RELOCATION_SCRIPT_PAIRS PAIR_NO)
 
-  if(SYMLINK_PATHS_COUTN)
+  if(SYMLINK_PATHS_COUNT)
     foreach(SYMLINK_RELOC_PATH IN LISTS SYMLINK_RELOCATION_PATHS)
       list(FIND PACKAGE_PREFIXES "${SYMLINK_RELOC_PATH}" SYMLINK_INDEX)
 
diff --git a/Modules/UseJava.cmake b/Modules/UseJava.cmake
index b79d8d2..4192169 100644
--- a/Modules/UseJava.cmake
+++ b/Modules/UseJava.cmake
@@ -53,6 +53,7 @@
             [SOURCES] <source1> [<source2>...] [<resource1>...]
             [RESOURCES NAMESPACE <ns1> <resource1>... [NAMESPACE <nsX> <resourceX>...]... ]
             [INCLUDE_JARS <jar1> [<jar2>...]]
+            [INCLUDE_MODULES <jar1> [<jar2>...]]
             [ENTRY_POINT <entry>]
             [VERSION <version>]
             [MANIFEST <manifest>]
@@ -109,6 +110,13 @@
     jar files listed as sources are ignored (as they have been since the first
     version of this module).
 
+  ``INCLUDE_MODULES``
+    .. versionadded:: 4.3
+
+    The list of jars are added to the module path when building the java sources
+    and also to the dependencies of the target. ``INCLUDE_MODULES`` also
+    accepts other target names created by ``add_jar()``.
+
   ``ENTRY_POINT``
     Defines an entry point in the jar file.
 
@@ -568,6 +576,9 @@
     generated docs. Same behavior as option ``-version`` of ``javadoc`` tool.
 #]=======================================================================]
 
+block(SCOPE_FOR POLICIES)
+cmake_policy(SET CMP0140 NEW) # For return(PROPAGATE ...)
+
 function (__java_copy_file src dest comment)
     add_custom_command(
         OUTPUT  ${dest}
@@ -665,11 +676,42 @@
     set(_UseJava_PATH_SEP ":")
 endif()
 
+function(__java_resolve_jar outvar_path path_or_tgt)
+    if (TARGET "${path_or_tgt}")
+        # Check if this is a target created by add_jar
+        get_target_property(jarpath "${path_or_tgt}" JAR_FILE)
+        set("${outvar_path}" "${jarpath}")
+        if (NOT jarpath) # Some other target that we do not know how to use as a JAR
+            message(SEND_ERROR "target ${path_or_tgt} is not a jar")
+        endif ()
+    else () # Interpreted as the path to an external JAR
+        set("${outvar_path}" "${path_or_tgt}")
+    endif ()
+    return(PROPAGATE "${outvar_path}")
+endfunction()
+
+# Helper that processes a list of add_jar-created targets or paths to external
+# JAR files and:
+# - appends each item (target name or JAR path) to the list in outvar_depsraw.
+# - appends the resolved JAR path to the list in outvar_depsresolved.
+# If any item cannot be resolved, because it is a target name that was not
+# created by add_jar, a SEND_ERROR message is raised.
+function(__java_build_deplists outvar_depsraw outvar_depsresolved jars_or_targets_lst)
+    foreach(item IN LISTS "${jars_or_targets_lst}")
+        __java_resolve_jar(res_path "${item}")
+        if (res_path) # We would get a falsy value in the error case
+            list(APPEND "${outvar_depsraw}" "${item}") # Keep the original name (possibly a target) here
+            list(APPEND "${outvar_depsresolved}" "${res_path}")
+        endif ()
+    endforeach()
+    return(PROPAGATE "${outvar_depsraw}" "${outvar_depsresolved}")
+endfunction()
+
 function(add_jar _TARGET_NAME)
 
     set(options)  # currently there are no zero value args (aka: options)
     set(oneValueArgs "ENTRY_POINT;MANIFEST;OUTPUT_DIR;;OUTPUT_NAME;VERSION" )
-    set(multiValueArgs "GENERATE_NATIVE_HEADERS;INCLUDE_JARS;RESOURCES;SOURCES" )
+    set(multiValueArgs "GENERATE_NATIVE_HEADERS;INCLUDE_JARS;INCLUDE_MODULES;RESOURCES;SOURCES" )
 
     cmake_parse_arguments(PARSE_ARGV 1 _add_jar
                     "${options}"
@@ -803,6 +845,7 @@
     set(_JAVA_COMPILE_FILELISTS)
     set(_JAVA_DEPENDS)
     set(_JAVA_COMPILE_DEPENDS)
+    set(_JAVA_COMPILE_MODDEPENDS)
     set(_JAVA_RESOURCE_FILES)
     set(_JAVA_RESOURCE_FILES_RELATIVE)
     foreach(_JAVA_SOURCE_FILE IN LISTS _JAVA_SOURCE_FILES)
@@ -838,7 +881,8 @@
             # Ignored for backward compatibility
 
         elseif (_JAVA_EXT STREQUAL "")
-            list(APPEND CMAKE_JAVA_INCLUDE_PATH ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}} ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}_CLASSPATH})
+            # XXX: this was not being used after setting!
+            # list(APPEND CMAKE_JAVA_INCLUDE_PATH ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}} ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}_CLASSPATH})
             list(APPEND _JAVA_DEPENDS ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}})
 
         else ()
@@ -857,24 +901,21 @@
                                         _JAVA_RESOURCE_FILES_RELATIVE)
     endif()
 
-    foreach(_JAVA_INCLUDE_JAR IN LISTS _add_jar_INCLUDE_JARS)
-        if (TARGET ${_JAVA_INCLUDE_JAR})
-            get_target_property(_JAVA_JAR_PATH ${_JAVA_INCLUDE_JAR} JAR_FILE)
-            if (_JAVA_JAR_PATH)
-                string(APPEND CMAKE_JAVA_INCLUDE_PATH_FINAL "${_UseJava_PATH_SEP}${_JAVA_JAR_PATH}")
-                list(APPEND CMAKE_JAVA_INCLUDE_PATH ${_JAVA_JAR_PATH})
-                list(APPEND _JAVA_DEPENDS ${_JAVA_INCLUDE_JAR})
-                list(APPEND _JAVA_COMPILE_DEPENDS ${_JAVA_JAR_PATH})
-            else ()
-                message(SEND_ERROR "add_jar: INCLUDE_JARS target ${_JAVA_INCLUDE_JAR} is not a jar")
-            endif ()
-        else ()
-            string(APPEND CMAKE_JAVA_INCLUDE_PATH_FINAL "${_UseJava_PATH_SEP}${_JAVA_INCLUDE_JAR}")
-            list(APPEND CMAKE_JAVA_INCLUDE_PATH "${_JAVA_INCLUDE_JAR}")
-            list(APPEND _JAVA_DEPENDS "${_JAVA_INCLUDE_JAR}")
-            list(APPEND _JAVA_COMPILE_DEPENDS "${_JAVA_INCLUDE_JAR}")
-        endif ()
-    endforeach()
+    # Build dependency lists and arguments for JARs in the classpath
+    __java_build_deplists(_JAVA_DEPENDS _JAVA_COMPILE_DEPENDS _add_jar_INCLUDE_JARS)
+    foreach (resolved_cp_item IN LISTS _JAVA_COMPILE_DEPENDS)
+        string(APPEND CMAKE_JAVA_INCLUDE_PATH_FINAL "${_UseJava_PATH_SEP}${resolved_cp_item}")
+    endforeach ()
+    # Build dependency lists and arguments for JARs in the modulepath
+    set(javac_mp_args)
+    if (_add_jar_INCLUDE_MODULES)
+        if (Java_VERSION VERSION_LESS 9)
+            message(SEND_ERROR "INCLUDE_MODULES requires Java 9+")
+        endif()
+        __java_build_deplists(_JAVA_DEPENDS _JAVA_COMPILE_MODDEPENDS _add_jar_INCLUDE_MODULES)
+        list(JOIN _JAVA_COMPILE_MODDEPENDS "${_UseJava_PATH_SEP}" javac_mp_args)
+        set(javac_mp_args "--module-path [[${javac_mp_args}]]")
+    endif()
 
     if (_JAVA_COMPILE_FILES OR _JAVA_COMPILE_FILELISTS)
         set (_JAVA_SOURCES_FILELISTS)
@@ -899,33 +940,41 @@
 
         cmake_language(GET_MESSAGE_LOG_LEVEL _LOG_LEVEL)
         # Compile the java files and create a list of class files
-        add_custom_command(
-            # NOTE: this command generates an artificial dependency file
-            OUTPUT ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}
+        # NOTE: this command generates an artificial dependency file
+        set(stamp_file "${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}")
+        add_custom_command(OUTPUT "${stamp_file}"
             COMMAND ${CMAKE_COMMAND}
                 -DCMAKE_JAVA_CLASS_OUTPUT_PATH=${CMAKE_JAVA_CLASS_OUTPUT_PATH}
                 -DCMAKE_JAR_CLASSES_PREFIX=${CMAKE_JAR_CLASSES_PREFIX}
                 -P ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/UseJava/ClearClassFiles.cmake
                 --log-level ${_LOG_LEVEL}
-            COMMAND ${Java_JAVAC_EXECUTABLE}
-                ${CMAKE_JAVA_COMPILE_FLAGS}
-                -classpath "${CMAKE_JAVA_INCLUDE_PATH_FINAL}"
-                -d ${CMAKE_JAVA_CLASS_OUTPUT_PATH}
-                ${_GENERATE_NATIVE_HEADERS}
-                ${_JAVA_SOURCES_FILELISTS}
-            COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}
-            DEPENDS ${_JAVA_COMPILE_FILES} ${_JAVA_COMPILE_FILELISTS} ${_JAVA_COMPILE_DEPENDS} ${_JAVA_SOURCES_FILE}
+            DEPENDS ${_JAVA_COMPILE_FILES} ${_JAVA_COMPILE_FILELISTS} ${_JAVA_COMPILE_DEPENDS} ${_JAVA_COMPILE_MODDEPENDS} ${_JAVA_SOURCES_FILE}
             WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
             COMMENT "Building Java objects for ${_TARGET_NAME}.jar"
             VERBATIM
         )
+        cmake_language(EVAL CODE "
+            add_custom_command(OUTPUT [[${stamp_file}]]
+                APPEND COMMAND [[${Java_JAVAC_EXECUTABLE}]]
+                    \${CMAKE_JAVA_COMPILE_FLAGS}
+                    -classpath [[${CMAKE_JAVA_INCLUDE_PATH_FINAL}]]
+                    ${javac_mp_args}
+                    -d [[${CMAKE_JAVA_CLASS_OUTPUT_PATH}]]
+                    \${_GENERATE_NATIVE_HEADERS}
+                    \${_JAVA_SOURCES_FILELISTS}
+            )
+        ")
+        add_custom_command(OUTPUT "${stamp_file}"
+            APPEND COMMAND ${CMAKE_COMMAND} -E touch "${stamp_file}"
+        )
+
         add_custom_command(
             OUTPUT ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist
             COMMAND ${CMAKE_COMMAND}
                 -DCMAKE_JAVA_CLASS_OUTPUT_PATH=${CMAKE_JAVA_CLASS_OUTPUT_PATH}
                 -DCMAKE_JAR_CLASSES_PREFIX=${CMAKE_JAR_CLASSES_PREFIX}
                 -P ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/UseJava/ClassFilelist.cmake
-            DEPENDS ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}
+            DEPENDS "${stamp_file}"
             WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
             VERBATIM
         )
@@ -1725,3 +1774,5 @@
             DESTINATION ${_install_jar_exports_DESTINATION}
             ${_COMPONENT})
 endfunction()
+
+endblock()
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 44f87db..c76eee4 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -701,6 +701,8 @@
   cmRemoveDefinitionsCommand.h
   cmReturnCommand.cxx
   cmReturnCommand.h
+  cmSbomObject.h
+  cmSbomSerializer.h
   cmSearchPath.cxx
   cmSearchPath.h
   cmSeparateArgumentsCommand.cxx
@@ -721,9 +723,10 @@
   cmSiteNameCommand.h
   cmSourceGroupCommand.cxx
   cmSourceGroupCommand.h
-  cmSPDXSerializer.cxx
-  cmSPDXSerializer.h
-  cmSPDXTypes.def
+  cmSpdx.cxx
+  cmSpdx.h
+  cmSpdxSerializer.cxx
+  cmSpdxSerializer.h
   cmString.cxx
   cmString.hxx
   cmStringReplaceHelper.cxx
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index fd83e70..be0a659 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,8 +1,8 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 4)
 set(CMake_VERSION_MINOR 2)
-set(CMake_VERSION_PATCH 0)
-set(CMake_VERSION_RC 1)
+set(CMake_VERSION_PATCH 20251028)
+#set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
 # Start with the full version number used in tags.  It has no dev info.
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index c9cec0d..842130a 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -1698,10 +1698,10 @@
     if (ispcSuffixes.size() > 1) {
       std::string rootObjectDir =
         this->GeneratorTarget->GetObjectDirectory(config);
-      auto ispcSideEfffectObjects = detail::ComputeISPCExtraObjects(
+      auto ispcSideEffectObjects = detail::ComputeISPCExtraObjects(
         objectName, rootObjectDir, ispcSuffixes);
 
-      for (auto sideEffect : ispcSideEfffectObjects) {
+      for (auto sideEffect : ispcSideEffectObjects) {
         sideEffect = this->ConvertToNinjaPath(sideEffect);
         objBuild.ImplicitOuts.emplace_back(sideEffect);
         this->GetGlobalGenerator()->AddAdditionalCleanFile(sideEffect, config);
diff --git a/Source/cmSPDXSerializer.cxx b/Source/cmSPDXSerializer.cxx
deleted file mode 100644
index 7d22103..0000000
--- a/Source/cmSPDXSerializer.cxx
+++ /dev/null
@@ -1,1375 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file LICENSE.rst or https://cmake.org/licensing for details.  */
-#include "cmSPDXSerializer.h"
-
-#include <new>
-#include <string>
-
-#include <cm/optional>
-
-#include <cm3p/json/value.h>
-
-// Serialization Utilities
-
-template <typename T>
-void addVectorSPDXValue(Json::Value& obj, std::string const& key,
-                        std::vector<T> const& vec)
-{
-  auto& list = obj[key];
-  list = Json::Value(Json::arrayValue);
-  for (auto const& val : vec) {
-    list.append(val.toJsonLD());
-  }
-}
-
-template <>
-void addVectorSPDXValue(Json::Value& obj, std::string const& key,
-                        std::vector<std::string> const& vec)
-{
-  auto& list = obj[key];
-  list = Json::Value(Json::arrayValue);
-  for (auto const& val : vec) {
-    list.append(val);
-  }
-}
-
-template <typename T>
-void addOptionalSPDXValue(Json::Value& obj, std::string const& key,
-                          cm::optional<std::vector<T>> const& opt)
-{
-  if (opt) {
-    addVectorSPDXValue(obj, key, *opt);
-  }
-}
-
-template <typename T>
-void addOptionalSPDXValue(Json::Value& obj, std::string const& key,
-                          cm::optional<T> const& opt)
-{
-  if (opt) {
-    obj[key] = opt->toJsonLD();
-  }
-}
-
-template <>
-void addOptionalSPDXValue(Json::Value& obj, std::string const& key,
-                          cm::optional<std::string> const& opt)
-{
-  if (opt) {
-    obj[key] = *opt;
-  }
-}
-
-// Base Class
-
-cmSPDXSerializationBase::SPDXTypeId cmSPDXSerializationBase::getTypeId() const
-{
-  return TypeId;
-}
-
-cmSPDXSerializationBase::cmSPDXSerializationBase(SPDXTypeId id)
-  : TypeId(id)
-{
-}
-
-cmSPDXSerializationBase::cmSPDXSerializationBase(SPDXTypeId id,
-                                                 std::string nodeId)
-  : NodeId(std::move(nodeId))
-  , TypeId(id)
-{
-}
-
-// Convenience Classes
-
-cmSPDXIdentifierReference::cmSPDXIdentifierReference()
-  : cmSPDXSerializationBase(CM_IDENTIFIER_REFERENCE)
-{
-}
-cmSPDXIdentifierReference::cmSPDXIdentifierReference(
-  cmSPDXSerializationBase const& ref)
-  : cmSPDXSerializationBase(CM_IDENTIFIER_REFERENCE, ref.NodeId)
-{
-}
-cmSPDXIdentifierReference::cmSPDXIdentifierReference(std::string const& ref)
-  : cmSPDXSerializationBase(CM_IDENTIFIER_REFERENCE, ref)
-{
-}
-
-Json::Value cmSPDXIdentifierReference::toJsonLD() const
-{
-  return NodeId;
-}
-
-cmSPDXNonElementBase::cmSPDXNonElementBase(SPDXTypeId id)
-  : cmSPDXSerializationBase(id)
-{
-}
-
-Json::Value cmSPDXNonElementBase::toJsonLD() const
-{
-  Json::Value obj(Json::objectValue);
-  obj["@id"] = NodeId;
-  return obj;
-}
-
-// SPDX Core Enums
-
-cmSPDXAnnotationType::cmSPDXAnnotationType(cmSPDXAnnotationTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXAnnotationType::toJsonLD() const
-{
-  switch (TypeId) {
-    case OTHER:
-      return "other";
-    case REVIEW:
-      return "review";
-    default:
-      return "INVALID_ANNOTATION_TYPE_ID";
-  }
-}
-
-cmSPDXExternalIdentifierType::cmSPDXExternalIdentifierType(
-  cmSPDXExternalIdentifierTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXExternalIdentifierType::toJsonLD() const
-{
-  switch (TypeId) {
-    case CPE22:
-      return "cpe22";
-    case CPE23:
-      return "cpe23";
-    case CVE:
-      return "cve";
-    case EMAIL:
-      return "email";
-    case GITOID:
-      return "gitoid";
-    case OTHER:
-      return "other";
-    case PACKAGE_URL:
-      return "packageUrl";
-    case SECURITY_OTHER:
-      return "securityOther";
-    case SWHID:
-      return "swhid";
-    case SWID:
-      return "swid";
-    case URL_SCHEME:
-      return "urlScheme";
-    default:
-      return "INVALID_EXTERNAL_IDENTIFIER_TYPE_ID";
-  }
-}
-
-cmSPDXExternalRefType::cmSPDXExternalRefType(cmSPDXExternalRefTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXExternalRefType::toJsonLD() const
-{
-  switch (TypeId) {
-    case ALT_DOWNLOAD_LOCATION:
-      return "altDownloadLocation:";
-    case ALT_WEB_PAGE:
-      return "altWebPage";
-    case BINARY_ARTIFACT:
-      return "binaryArtifact";
-    case BOWER:
-      return "bower";
-    case BUILD_META:
-      return "buildMeta";
-    case BUILD_SYSTEM:
-      return "buildSystem";
-    case CERTIFICATION_REPORT:
-      return "certificationReport";
-    case CHAT:
-      return "chat";
-    case COMPONENT_ANALYSIS_REPORT:
-      return "componentAnalysisReport";
-    case CWE:
-      return "cwe";
-    case DOCUMENTATION:
-      return "documentation";
-    case DYNAMIC_ANALYSIS_REPORT:
-      return "dynamicAnalysisReport";
-    case EOL_NOTICE:
-      return "eolNotice";
-    case EXPORT_CONTROL_ASSESSMENT:
-      return "exportControlAssessment";
-    case FUNDING:
-      return "funding";
-    case ISSUE_TRACKER:
-      return "issueTracker";
-    case LICENSE:
-      return "license";
-    case MAILING_LIST:
-      return "mailingList";
-    case MAVEN_CENTRAL:
-      return "mavenCentral";
-    case METRICS:
-      return "metrics";
-    case NPM:
-      return "npm";
-    case NUGET:
-      return "nuget";
-    case OTHER:
-      return "other";
-    case PRIVACY_ASSESSMENT:
-      return "privacyAssessment";
-    case PRODUCT_METADATA:
-      return "productMetadata";
-    case PURCHASE_ORDER:
-      return "purchaseOrder";
-    case QUALITY_ASSESSMENT_REPORT:
-      return "qualityAssessmentReport";
-    case RELEASE_HISTORY:
-      return "releaseHistory";
-    case RELEASE_NOTES:
-      return "releaseNotes";
-    case RISK_ASSESSMENT:
-      return "riskAssessment";
-    case RUNTIME_ANALYSIS_REPORT:
-      return "runtimeAnalysisReport";
-    case SECURE_SOFTWARE_ATTESTATION:
-      return "secureSoftwareAttestation";
-    case SECURITY_ADVERSARY_MODEL:
-      return "securityAdversaryModel";
-    case SECURITY_ADVISORY:
-      return "securityAdvisory";
-    case SECURITY_FIX:
-      return "securityFix";
-    case SECURITY_OTHER:
-      return "securityOther";
-    case SECURITY_PEN_TEST_REPORT:
-      return "securityPenTestReport";
-    case SECURITY_POLICY:
-      return "securityPolicy";
-    case SECURITY_THREAT_MODEL:
-      return "securityThreatModel";
-    case SOCIAL_MEDIA:
-      return "socialMedia";
-    case SOURCE_ARTIFACT:
-      return "sourceArtifact";
-    case STATIC_ANALYSIS_REPORT:
-      return "staticAnalysisReport";
-    case SUPPORT:
-      return "support";
-    case VCS:
-      return "vcs";
-    case VULNERABILITY_DISCLOSURE_REPORT:
-      return "vulnerabilityDisclosureReport";
-    case VULNERABILITY_EXPLOITABILITY_ASSESSMENT:
-      return "vulnerabilityExploitabilityAssessment";
-    default:
-      return "INVALID_EXTERNAL_REF_TYPE_ID";
-  }
-}
-
-cmSPDXHashAlgorithm::cmSPDXHashAlgorithm(cmSPDXHashAlgorithmId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXHashAlgorithm::toJsonLD() const
-{
-  switch (TypeId) {
-    case ADLER32:
-      return "adler32";
-    case BLAKE2B256:
-      return "blake2b256";
-    case BLAKE2B384:
-      return "blake2b384";
-    case BLAKE2B512:
-      return "blake2b512";
-    case BLAKE3:
-      return "blake3";
-    case CRYSTALS_DILITHIUM:
-      return "crystalsDilithium";
-    case CRYSTALS_KYBER:
-      return "crystalsLyber";
-    case FALCON:
-      return "falcon";
-    case MD2:
-      return "md2";
-    case MD4:
-      return "md4";
-    case MD5:
-      return "md5";
-    case MD6:
-      return "md6";
-    case OTHER:
-      return "other";
-    case SHA1:
-      return "sha1";
-    case SHA224:
-      return "sha224";
-    case SHA256:
-      return "sha256";
-    case SHA384:
-      return "sha384";
-    case SHA3_224:
-      return "sha3_224";
-    case SHA3_256:
-      return "sha3_256";
-    case SHA3_384:
-      return "sha3_384";
-    case SHA3_512:
-      return "sha3_512";
-    case SHA512:
-      return "sha512";
-    default:
-      return "INVALID_HASH_TYPE_ID";
-  }
-}
-
-cmSPDXLifecycleScopeType::cmSPDXLifecycleScopeType(
-  cmSPDXLifecycleScopeTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXLifecycleScopeType::toJsonLD() const
-{
-  switch (TypeId) {
-    case BUILD:
-      return "build";
-    case DESIGN:
-      return "design";
-    case DEVELOPMENT:
-      return "development";
-    case OTHER:
-      return "other";
-    case RUNTIME:
-      return "runtime";
-    case TEST:
-      return "test";
-    default:
-      return "INVALID_LIFECYCLE_SCOPE_TYPE_ID";
-  }
-}
-
-cmSPDXProfileIdentifierType::cmSPDXProfileIdentifierType(
-  cmSPDXProfileIdentifierTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXProfileIdentifierType::toJsonLD() const
-{
-  switch (TypeId) {
-    case AI:
-      return "ai";
-    case BUILD:
-      return "build";
-    case CORE:
-      return "code";
-    case DATASET:
-      return "dataset";
-    case EXPANDED_LICENSING:
-      return "expandedLicensing";
-    case EXTENSION:
-      return "extension";
-    case LITE:
-      return "lite";
-    case SECURITY:
-      return "security";
-    case SIMPLE_LICENSING:
-      return "simpleLicensing";
-    case SOFTWARE:
-      return "software";
-    default:
-      return "INVALID_PROFILE_IDENTIFIER_TYPE_ID";
-  }
-}
-
-cmSPDXRelationshipCompletenessType::cmSPDXRelationshipCompletenessType(
-  cmSPDXRelationshipCompletenessTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXRelationshipCompletenessType::toJsonLD() const
-{
-  switch (TypeId) {
-    case COMPLETE:
-      return "complete";
-    case INCOMPLETE:
-      return "incomplete";
-    case NO_ASSERTION:
-      return "noAssertion";
-    default:
-      return "INVALID_RELATIONSHIP_COMPLETENESS_TYPE_ID";
-  }
-}
-
-cmSPDXRelationshipType::cmSPDXRelationshipType(cmSPDXRelationshipTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXRelationshipType::toJsonLD() const
-{
-  switch (TypeId) {
-    case AFFECTS:
-      return "affects";
-    case AMENDED_BY:
-      return "amendedBy";
-    case ANCESTOR_OF:
-      return "ancestorOf";
-    case AVAILABLE_FROM:
-      return "availableFrom";
-    case CONFIGURES:
-      return "configures";
-    case CONTAINS:
-      return "contains";
-    case COORDINATED_BY:
-      return "coordinatedBy";
-    case COPIED_TO:
-      return "copiedTo";
-    case DELEGATED_TO:
-      return "delegatedTo";
-    case DEPENDS_ON:
-      return "dependsOn";
-    case DESCENDANT_OF:
-      return "descendantOf";
-    case DESCRIBES:
-      return "describes";
-    case DOES_NOT_AFFECT:
-      return "doesNotAffect";
-    case EXPANDS_TO:
-      return "expandsTo";
-    case EXPLOIT_CREATED_BY:
-      return "exploitCreatedBy";
-    case FIXED_BY:
-      return "fixedBy";
-    case FIXED_IN:
-      return "fixedIn";
-    case FOUND_BY:
-      return "foundBy";
-    case GENERATES:
-      return "generates";
-    case HAS_ADDED_FILE:
-      return "hasAddedFile";
-    case HAS_ASSESSMENT_FOR:
-      return "hasAssessmentFor";
-    case HAS_ASSOCIATED_VULNERABILITY:
-      return "hasAssociatedVulnerability";
-    case HAS_CONCLUDED_LICENSE:
-      return "hasConcludedLicense";
-    case HAS_DATA_FILE:
-      return "hasDataFile";
-    case HAS_DECLARED_LICENSE:
-      return "hasDeclaredLicense";
-    case HAS_DELETED_FILE:
-      return "hasDeletedFile";
-    case HAS_DEPENDENCY_MANIFEST:
-      return "hasDependencyManifest";
-    case HAS_DISTRIBUTION_ARTIFACT:
-      return "hasDistributionArtifact";
-    case HAS_DOCUMENTATION:
-      return "hasDocumentation";
-    case HAS_DYNAMIC_LINK:
-      return "hasDynamicLink";
-    case HAS_EVIDENCE:
-      return "hasEvidence";
-    case HAS_EXAMPLE:
-      return "hasExample";
-    case HAS_HOST:
-      return "hasHost";
-    case HAS_INPUT:
-      return "hasInput";
-    case HAS_METADATA:
-      return "hasMetadata";
-    case HAS_OPTIONAL_COMPONENT:
-      return "hasOptionalComponent";
-    case HAS_OPTIONAL_DEPENDENCY:
-      return "hasOptionalDependency";
-    case HAS_OUTPUT:
-      return "hasOutput";
-    case HAS_PREREQUISITE:
-      return "hasPrerequisite";
-    case HAS_PROVIDED_DEPENDENCY:
-      return "hasProvidedDependency";
-    case HAS_REQUIREMENT:
-      return "hasRequirement";
-    case HAS_SPECIFICATION:
-      return "hasSpecification";
-    case HAS_STATIC_LINK:
-      return "hasStaticLink";
-    case HAS_TEST:
-      return "hasTest";
-    case HAS_TEST_CASE:
-      return "hasTestCase";
-    case HAS_VARIANT:
-      return "hasVariant";
-    case INVOKED_BY:
-      return "invokedBy";
-    case MODIFIED_BY:
-      return "modifiedBy";
-    case OTHER:
-      return "other";
-    case PACKAGED_BY:
-      return "packagedBy";
-    case PATCHED_BY:
-      return "patchedBy";
-    case PUBLISHED_BY:
-      return "publishedBy";
-    case REPORTED_BY:
-      return "reportedBy";
-    case REPUBLISHED_BY:
-      return "republishedBy";
-    case SERIALIZED_IN_ARTIFACT:
-      return "serializedInArtifact";
-    case TESTED_ON:
-      return "testedOn";
-    case TRAINED_ON:
-      return "trainedOn";
-    case UNDER_INVESTIGATION_FOR:
-      return "underInvestigationFor";
-    case USES_TOOL:
-      return "usesTool";
-    default:
-      return "INVALID_RELATIONSHIP_TYPE_ID";
-  }
-}
-
-cmSPDXSupportType::cmSPDXSupportType(cmSPDXSupportTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXSupportType::toJsonLD() const
-{
-  switch (TypeId) {
-    case DEPLOYED:
-      return "deployed";
-    case DEVELOPMENT:
-      return "development";
-    case END_OF_SUPPORT:
-      return "endOfSupport";
-    case LIMITED_SUPPORT:
-      return "limitedSupport";
-    case NO_ASSERTION:
-      return "noAssertion";
-    case NO_SUPPORT:
-      return "noSupport";
-    case SUPPORT:
-      return "support";
-    default:
-      return "INVALID_SUPPORT_TYPE_ID";
-  }
-}
-
-// SPDX Core NonElement Classes, Abstract
-
-cmSPDXIntegrityMethod::cmSPDXIntegrityMethod(SPDXTypeId id)
-  : cmSPDXNonElementBase(id)
-{
-}
-
-Json::Value cmSPDXIntegrityMethod::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  addOptionalSPDXValue(obj, "comment", Comment);
-  return obj;
-}
-
-// SPDX Core NonElement Classes, Concrete
-
-cmSPDXCreationInfo::cmSPDXCreationInfo()
-  : cmSPDXNonElementBase(CORE_CREATION_INFO)
-{
-}
-
-Json::Value cmSPDXCreationInfo::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  obj["type"] = "CreationInfo";
-  addOptionalSPDXValue(obj, "Comment", Comment);
-  obj["created"] = Created;
-  addVectorSPDXValue(obj, "createdBy", CreatedBy);
-  addOptionalSPDXValue(obj, "createdUsing", CreatedUsing);
-  return obj;
-}
-
-cmSPDXDictionaryEntry::cmSPDXDictionaryEntry()
-  : cmSPDXNonElementBase(CORE_DICTIONARY_ENTRY)
-{
-}
-cmSPDXDictionaryEntry::cmSPDXDictionaryEntry(std::string key)
-  : cmSPDXNonElementBase(CORE_DICTIONARY_ENTRY)
-  , Key(std::move(key))
-{
-}
-cmSPDXDictionaryEntry::cmSPDXDictionaryEntry(std::string key, std::string val)
-  : cmSPDXNonElementBase(CORE_DICTIONARY_ENTRY)
-  , Key(std::move(key))
-  , Value(std::move(val))
-{
-}
-
-Json::Value cmSPDXDictionaryEntry::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  obj["type"] = "DictionaryEntry";
-  obj["key"] = Key;
-  addOptionalSPDXValue(obj, "value", Value);
-  return obj;
-}
-
-cmSPDXExternalIdentifier::cmSPDXExternalIdentifier()
-  : cmSPDXNonElementBase(CORE_EXTERNAL_IDENTIFIER)
-{
-}
-
-Json::Value cmSPDXExternalIdentifier::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  obj["type"] = "ExternalIdentifier";
-  addOptionalSPDXValue(obj, "comment", Comment);
-  obj["externalIdentifierType"] = ExternalIdentifierType.toJsonLD();
-  obj["identifier"] = Identifier;
-  addOptionalSPDXValue(obj, "identifierLocator", IdentifierLocator);
-  addOptionalSPDXValue(obj, "issuingAuthority", IssuingAuthority);
-  return obj;
-}
-
-cmSPDXExternalMap::cmSPDXExternalMap()
-  : cmSPDXNonElementBase(CORE_EXTERNAL_MAP)
-{
-}
-
-Json::Value cmSPDXExternalMap::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  obj["type"] = "ExternalMap";
-  addOptionalSPDXValue(obj, "definingArtifact", DefiningArtifact);
-  obj["externalSpdxId"] = ExternalSpdxId;
-  addOptionalSPDXValue(obj, "locationHint", LocationHint);
-  addOptionalSPDXValue(obj, "integrityMethod", IntegrityMethod);
-  return obj;
-}
-
-cmSPDXExternalRef::cmSPDXExternalRef()
-  : cmSPDXNonElementBase(CORE_EXTERNAL_REF)
-{
-}
-
-Json::Value cmSPDXExternalRef::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  obj["type"] = "ExternalRef";
-  addOptionalSPDXValue(obj, "comment", Comment);
-  addOptionalSPDXValue(obj, "contentType", ContentType);
-  addOptionalSPDXValue(obj, "externalRefType", ExternalRefType);
-  addOptionalSPDXValue(obj, "locator", Locator);
-  return obj;
-}
-
-cmSPDXHash::cmSPDXHash()
-  : cmSPDXIntegrityMethod(CORE_HASH)
-{
-}
-
-Json::Value cmSPDXHash::toJsonLD() const
-{
-  auto obj = cmSPDXIntegrityMethod::toJsonLD();
-  obj["type"] = "Hash";
-  obj["algorithm"] = Algorithm.toJsonLD();
-  obj["hashValue"] = HashValue;
-  return obj;
-}
-
-cmSPDXNamespaceMap::cmSPDXNamespaceMap()
-  : cmSPDXNonElementBase(CORE_NAMESPACE_MAP)
-{
-}
-
-Json::Value cmSPDXNamespaceMap::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  obj["type"] = "NamespaceMap";
-  obj["namespace"] = Namespace;
-  obj["prefix"] = Namespace;
-  return obj;
-}
-
-cmSPDXPackageVerificationCode::cmSPDXPackageVerificationCode()
-  : cmSPDXIntegrityMethod(CORE_PACKAGE_VERIFICATION_CODE)
-{
-}
-
-Json::Value cmSPDXPackageVerificationCode::toJsonLD() const
-{
-  auto obj = cmSPDXIntegrityMethod::toJsonLD();
-  obj["type"] = "PackageVerificationCode";
-  obj["algorithm"] = Algorithm.toJsonLD();
-  obj["hashValue"] = HashValue;
-  return obj;
-}
-
-cmSPDXPositiveIntegerRange::cmSPDXPositiveIntegerRange(
-  unsigned int beingIntegerRange, unsigned int endIntegerRange)
-  : cmSPDXNonElementBase(CORE_POSITIVE_INTEGER_RANGE)
-  , BeginIntegerRange(beingIntegerRange)
-  , EndIntegerRange(endIntegerRange)
-{
-}
-
-Json::Value cmSPDXPositiveIntegerRange::toJsonLD() const
-{
-  auto obj = cmSPDXNonElementBase::toJsonLD();
-  obj["type"] = "PositiveIntegerRange";
-  obj["beginIntegerRange"] = BeginIntegerRange;
-  obj["endIntegerRange"] = EndIntegerRange;
-  return obj;
-}
-
-// SPDX Core Element Classes, Abstract
-
-cmSPDXElement::cmSPDXElement(SPDXTypeId id,
-                             cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXSerializationBase(id)
-  , CreationInfo(creationInfo)
-{
-}
-
-Json::Value cmSPDXElement::toJsonLD() const
-{
-  Json::Value obj(Json::objectValue);
-  addOptionalSPDXValue(obj, "comment", Comment);
-  obj["creationInfo"] = CreationInfo.toJsonLD();
-  addOptionalSPDXValue(obj, "description", Description);
-  addOptionalSPDXValue(obj, "extension", Extension);
-  addOptionalSPDXValue(obj, "externalIdentifier", ExternalIdentifier);
-  addOptionalSPDXValue(obj, "externalRef", ExternalRef);
-  addOptionalSPDXValue(obj, "name", Name);
-  obj["spdxId"] = NodeId;
-  addOptionalSPDXValue(obj, "summary", Summary);
-  addOptionalSPDXValue(obj, "verifiedUsing", VerifiedUsing);
-  return obj;
-}
-
-cmSPDXArtifact::cmSPDXArtifact(SPDXTypeId id,
-                               cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElement(id, creationInfo)
-{
-}
-
-Json::Value cmSPDXArtifact::toJsonLD() const
-{
-  auto obj = cmSPDXElement::toJsonLD();
-  addOptionalSPDXValue(obj, "builtTime", BuiltTime);
-  addOptionalSPDXValue(obj, "originateBy", OriginatedBy);
-  addOptionalSPDXValue(obj, "releaseTime", ReleaseTime);
-  addOptionalSPDXValue(obj, "standardName", StandardName);
-  addOptionalSPDXValue(obj, "suppliedBy", SuppliedBy);
-  addOptionalSPDXValue(obj, "supportType", SupportType);
-  addOptionalSPDXValue(obj, "validUntilTime", ValidUntilTime);
-  return obj;
-}
-
-cmSPDXElementCollection::cmSPDXElementCollection(
-  SPDXTypeId id, cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElement(id, creationInfo)
-{
-}
-
-Json::Value cmSPDXElementCollection::toJsonLD() const
-{
-  auto obj = cmSPDXElement::toJsonLD();
-  addOptionalSPDXValue(obj, "element", Element);
-  addOptionalSPDXValue(obj, "profileConformance", ProfileConformance);
-  addOptionalSPDXValue(obj, "rootElement", RootElement);
-  return obj;
-}
-
-// SPDX Implicit Core Element Classes, Abstract
-
-// Nominally an inheritable class, but adds nothing to Element
-using cmSPDXAgentAbstract = cmSPDXElement;
-
-cmSPDXBundleAbstract::cmSPDXBundleAbstract(
-  SPDXTypeId id, cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElementCollection(id, creationInfo)
-{
-}
-
-Json::Value cmSPDXBundleAbstract::toJsonLD() const
-{
-  auto obj = cmSPDXElementCollection::toJsonLD();
-  obj["context"] = Context;
-  return obj;
-}
-
-// Nominally an inheritable class, but adds nothing to Bundle
-using cmSPDXBomAbstract = cmSPDXBundleAbstract;
-
-cmSPDXRelationshipAbstract::cmSPDXRelationshipAbstract(
-  SPDXTypeId id, cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElement(id, creationInfo)
-{
-}
-
-Json::Value cmSPDXRelationshipAbstract::toJsonLD() const
-{
-  auto obj = cmSPDXElement::toJsonLD();
-  addOptionalSPDXValue(obj, "completeness", Completeness);
-  addOptionalSPDXValue(obj, "endTime", EndTime);
-  obj["from"] = From.toJsonLD();
-  obj["relationshipType"] = RelationshipType.toJsonLD();
-  addOptionalSPDXValue(obj, "startTime", StartTime);
-  addVectorSPDXValue(obj, "to", To);
-  return obj;
-}
-
-// SPDX Core Element Classes, Concrete
-
-cmSPDXAgent::cmSPDXAgent(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXAgentAbstract(CORE_AGENT, creationInfo)
-{
-}
-
-Json::Value cmSPDXAgent::toJsonLD() const
-{
-  auto obj = cmSPDXAgentAbstract::toJsonLD();
-  obj["type"] = "Agent";
-  return obj;
-}
-
-cmSPDXAnnotation::cmSPDXAnnotation(cmSPDXCreationInfo const& creationInfo,
-                                   cmSPDXIdentifierReference subject)
-  : cmSPDXElement(CORE_ANNOTATION, creationInfo)
-  , Subject(std::move(subject))
-{
-}
-
-Json::Value cmSPDXAnnotation::toJsonLD() const
-{
-  auto obj = cmSPDXElement::toJsonLD();
-  obj["type"] = "Annotation";
-  obj["annotationType"] = AnnotationType.toJsonLD();
-  addOptionalSPDXValue(obj, "contentType", ContentType);
-  addOptionalSPDXValue(obj, "statement", Statement);
-  obj["subject"] = Subject.toJsonLD();
-  return obj;
-}
-
-cmSPDXBom::cmSPDXBom(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXBomAbstract(CORE_BOM, creationInfo)
-{
-}
-
-Json::Value cmSPDXBom::toJsonLD() const
-{
-  auto obj = cmSPDXBomAbstract::toJsonLD();
-  obj["type"] = "Bom";
-  return obj;
-}
-
-cmSPDXBundle::cmSPDXBundle(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXBundleAbstract(CORE_BUNDLE, creationInfo)
-{
-}
-
-Json::Value cmSPDXBundle::toJsonLD() const
-{
-  auto obj = cmSPDXBundleAbstract::toJsonLD();
-  obj["type"] = "Bundle";
-  return obj;
-}
-
-cmSPDXIndividualElement::cmSPDXIndividualElement(
-  cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElement(CORE_INDIVIDUAL_ELEMENT, creationInfo)
-{
-}
-
-Json::Value cmSPDXIndividualElement::toJsonLD() const
-{
-  auto obj = cmSPDXElement::toJsonLD();
-  obj["type"] = "IndividualElement";
-  return obj;
-}
-
-cmSPDXLifecycleScopedRelationship::cmSPDXLifecycleScopedRelationship(
-  cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXRelationshipAbstract(CORE_LIFECYCLE_SCOPED_RELATIONSHIP,
-                               creationInfo)
-{
-}
-
-Json::Value cmSPDXLifecycleScopedRelationship::toJsonLD() const
-{
-  auto obj = cmSPDXRelationshipAbstract::toJsonLD();
-  obj["type"] = "LifecycleScopedRelationship";
-  addOptionalSPDXValue(obj, "scope", Scope);
-  return obj;
-}
-
-cmSPDXOrganization::cmSPDXOrganization(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXAgentAbstract(CORE_ORGANIZATION, creationInfo)
-{
-}
-
-Json::Value cmSPDXOrganization::toJsonLD() const
-{
-  auto obj = cmSPDXAgentAbstract::toJsonLD();
-  obj["type"] = "Organization";
-  return obj;
-}
-
-cmSPDXPerson::cmSPDXPerson(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXAgentAbstract(CORE_PERSON, creationInfo)
-{
-}
-
-Json::Value cmSPDXPerson::toJsonLD() const
-{
-  auto obj = cmSPDXAgentAbstract::toJsonLD();
-  obj["type"] = "Person";
-  return obj;
-}
-
-cmSPDXRelationship::cmSPDXRelationship(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXRelationshipAbstract(CORE_RELATIONSHIP, creationInfo)
-{
-}
-
-Json::Value cmSPDXRelationship::toJsonLD() const
-{
-  auto obj = cmSPDXRelationshipAbstract::toJsonLD();
-  obj["type"] = "Relationship";
-  return obj;
-}
-
-cmSPDXSoftwareAgent::cmSPDXSoftwareAgent(
-  cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXAgentAbstract(CORE_SOFTWARE_AGENT, creationInfo)
-{
-}
-
-Json::Value cmSPDXSoftwareAgent::toJsonLD() const
-{
-  auto obj = cmSPDXAgentAbstract::toJsonLD();
-  obj["type"] = "SoftwareAgent";
-  return obj;
-}
-
-cmSPDXSpdxDocument::cmSPDXSpdxDocument(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElementCollection(CORE_SPDX_DOCUMENT, creationInfo)
-{
-}
-
-Json::Value cmSPDXSpdxDocument::toJsonLD() const
-{
-  auto obj = cmSPDXElementCollection::toJsonLD();
-  obj["type"] = "SpdxDocument";
-  addOptionalSPDXValue(obj, "dataLicense", DataLicense);
-  addOptionalSPDXValue(obj, "externalMap", ExternalMap);
-  addOptionalSPDXValue(obj, "namespaceMap", NamespaceMap);
-  return obj;
-}
-
-cmSPDXTool::cmSPDXTool(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElement(CORE_TOOL, creationInfo)
-{
-}
-
-Json::Value cmSPDXTool::toJsonLD() const
-{
-  auto obj = cmSPDXElement::toJsonLD();
-  obj["type"] = "Tool";
-  return obj;
-}
-
-// SPDX Software Enums
-
-cmSPDXContentIdentifierType::cmSPDXContentIdentifierType(
-  cmSPDXContentIdentifierTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXContentIdentifierType::toJsonLD() const
-{
-  switch (TypeId) {
-    case GITOID:
-      return "gitoid";
-    case SWHID:
-      return "swhid";
-    default:
-      return "INVALID_CONTENT_IDENTIFIER_TYPE_ID";
-  }
-}
-
-cmSPDXFileKindType::cmSPDXFileKindType(cmSPDXFileKindTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXFileKindType::toJsonLD() const
-{
-  switch (TypeId) {
-    case DIRECTORY:
-      return "directory";
-    case FILE:
-      return "file";
-    default:
-      return "INVALID_FILE_KIND_TYPE_ID";
-  }
-}
-
-cmSPDXSbomType::cmSPDXSbomType(cmSPDXSbomTypeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXSbomType::toJsonLD() const
-{
-  switch (TypeId) {
-    case ANALYZED:
-      return "analyzed";
-    case BUILD:
-      return "build";
-    case DEPLOYED:
-      return "deployed";
-    case DESIGN:
-      return "design";
-    case RUNTIME:
-      return "runtime";
-    case SOURCE:
-      return "source";
-    default:
-      return "INVALID_SBOM_TYPE_ID";
-  }
-}
-
-cmSPDXSoftwarePurpose::cmSPDXSoftwarePurpose(cmSPDXSoftwarePurposeId typeId)
-  : TypeId(typeId)
-{
-}
-
-Json::Value cmSPDXSoftwarePurpose::toJsonLD() const
-{
-  switch (TypeId) {
-    case APPLICATION:
-      return "application";
-    case ARCHIVE:
-      return "archive";
-    case BOM:
-      return "bom";
-    case CONFIGURATION:
-      return "configuration";
-    case CONTAINER:
-      return "container";
-    case DATA:
-      return "data";
-    case DEVICE:
-      return "device";
-    case DEVICE_DRIVER:
-      return "deviceDriver";
-    case DISK_IMAGE:
-      return "diskImage";
-    case DOCUMENTATION:
-      return "documentation";
-    case EVIDENCE:
-      return "evidence";
-    case EXECUTABLE:
-      return "executable";
-    case FILE:
-      return "file";
-    case FILESYSTEM_IMAGE:
-      return "filesystemImage";
-    case FIRMWARE:
-      return "firmware";
-    case FRAMEWORK:
-      return "framework";
-    case INSTALL:
-      return "install";
-    case LIBRARY:
-      return "library";
-    case MANIFEST:
-      return "manifest";
-    case MODEL:
-      return "model";
-    case MODULE:
-      return "module";
-    case OPERATING_SYSTEM:
-      return "operatingSystem";
-    case OTHER:
-      return "other";
-    case PATCH:
-      return "patch";
-    case PLATFORM:
-      return "platform";
-    case REQUIREMENT:
-      return "requirement";
-    case SOURCE:
-      return "source";
-    case SPECIFICATION:
-      return "specification";
-    case TEST:
-      return "test";
-    default:
-      return "INVALID_SOFTWARE_PURPOSE_ID";
-  }
-}
-
-// SPDX Software NonElement Classes, Concrete
-
-cmSPDXContentIdentifier::cmSPDXContentIdentifier()
-  : cmSPDXIntegrityMethod(SOFTWARE_CONTENT_IDENTIFIER)
-{
-}
-
-Json::Value cmSPDXContentIdentifier::toJsonLD() const
-{
-  auto obj = cmSPDXIntegrityMethod::toJsonLD();
-  obj["type"] = "ContentIdentifier";
-  obj["contentIdentifierType"] = ContentIdentifierType.toJsonLD();
-  obj["contentIdentifierValue"] = ContentIdentifierValue;
-  return obj;
-}
-
-// SPDX Software Element Classes, Abstract
-
-cmSPDXSoftwareArtifact::cmSPDXSoftwareArtifact(
-  SPDXTypeId id, cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXArtifact(id, creationInfo)
-{
-}
-
-Json::Value cmSPDXSoftwareArtifact::toJsonLD() const
-{
-  auto obj = cmSPDXArtifact::toJsonLD();
-  addOptionalSPDXValue(obj, "additionalPurpose", AdditionalPurpose);
-  addOptionalSPDXValue(obj, "attributionText", AttributionText);
-  addOptionalSPDXValue(obj, "contentIdentifier", ContentIdentifier);
-  addOptionalSPDXValue(obj, "copyrightText", CopyrightText);
-  addOptionalSPDXValue(obj, "primaryPurpose", PrimaryPurpose);
-  return obj;
-}
-
-// SPDX Software Element Classes, Concrete
-
-cmSPDXFile::cmSPDXFile(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXSoftwareArtifact(SOFTWARE_FILE, creationInfo)
-{
-}
-
-Json::Value cmSPDXFile::toJsonLD() const
-{
-  auto obj = cmSPDXSoftwareArtifact::toJsonLD();
-  obj["type"] = "File";
-  addOptionalSPDXValue(obj, "contentType", ContentType);
-  addOptionalSPDXValue(obj, "fileKind", FileKind);
-  return obj;
-}
-
-cmSPDXPackage::cmSPDXPackage(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXSoftwareArtifact(SOFTWARE_PACKAGE, creationInfo)
-{
-}
-
-Json::Value cmSPDXPackage::toJsonLD() const
-{
-  auto obj = cmSPDXSoftwareArtifact::toJsonLD();
-  obj["type"] = "Package";
-  addOptionalSPDXValue(obj, "downloadLocation", DownloadLocation);
-  addOptionalSPDXValue(obj, "homePage", HomePage);
-  addOptionalSPDXValue(obj, "packageUrl", PackageUrl);
-  addOptionalSPDXValue(obj, "packageVersion", PackageVersion);
-  addOptionalSPDXValue(obj, "sourceInfo", SourceInfo);
-  return obj;
-}
-
-cmSPDXSbom::cmSPDXSbom(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXBomAbstract(SOFTWARE_SBOM, creationInfo)
-{
-}
-
-Json::Value cmSPDXSbom::toJsonLD() const
-{
-  auto obj = cmSPDXBomAbstract::toJsonLD();
-  obj["type"] = "Sbom";
-  addOptionalSPDXValue(obj, "sbomType", SbomType);
-  return obj;
-}
-
-cmSPDXSnippet::cmSPDXSnippet(cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXSoftwareArtifact(SOFTWARE_SNIPPET, creationInfo)
-{
-}
-
-Json::Value cmSPDXSnippet::toJsonLD() const
-{
-  auto obj = cmSPDXSoftwareArtifact::toJsonLD();
-  obj["type"] = "Snippet";
-  addOptionalSPDXValue(obj, "byteRange", ByteRange);
-  addOptionalSPDXValue(obj, "lineRange", LineRange);
-  obj["snippetFromFile"] = SnippetFromFile.toJsonLD();
-  return obj;
-}
-
-// SPDX SimpleLicensing Element Classes, Concrete
-
-cmSPDXLicenseExpression::cmSPDXLicenseExpression(
-  cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXAnyLicenseInfo(SIMPLE_LICENSING_LICENSE_EXPRESSION, creationInfo)
-{
-}
-
-Json::Value cmSPDXLicenseExpression::toJsonLD() const
-{
-  auto obj = cmSPDXAnyLicenseInfo::toJsonLD();
-  obj["type"] = "LicenseExpression";
-  addOptionalSPDXValue(obj, "customIdToUri", CustomIdToUri);
-  obj["licenseExpression"] = LicenseExpression;
-  addOptionalSPDXValue(obj, "licenseListVersion", LicenseListVersion);
-  return obj;
-}
-
-cmSPDXSimpleLicensingText::cmSPDXSimpleLicensingText(
-  cmSPDXCreationInfo const& creationInfo)
-  : cmSPDXElement(SIMPLE_LICENSING_SIMPLE_LICENSING_TEXT, creationInfo)
-{
-}
-
-Json::Value cmSPDXSimpleLicensingText::toJsonLD() const
-{
-  auto obj = cmSPDXElement::toJsonLD();
-  obj["type"] = "SimpleLicensingText";
-  obj["licenseText"] = LicenseText;
-  return obj;
-}
-
-// Graph Manipulation
-
-#define X_SPDX(classtype, enumid, member, camel)                              \
-  template <>                                                                 \
-  cmSPDXSerializationBase::SPDXTypeId cmSPDXGetTypeId<classtype>()            \
-  {                                                                           \
-    return cmSPDXSerializationBase::enumid;                                   \
-  }                                                                           \
-                                                                              \
-  template <>                                                                 \
-  std::string cmSPDXGetTypeName<classtype>()                                  \
-  {                                                                           \
-    return #camel;                                                            \
-  }                                                                           \
-                                                                              \
-  cmSPDXObject::cmSPDXObject(classtype val)                                   \
-    : member(std::move(val)) {};                                              \
-                                                                              \
-  void cmSPDXObject::get(classtype** ptr)                                     \
-  {                                                                           \
-    *ptr = SerializationBase.getTypeId() == cmSPDXSerializationBase::enumid   \
-      ? &member                                                               \
-      : nullptr;                                                              \
-  }
-#include "cmSPDXTypes.def"
-
-cmSPDXObject::cmSPDXObject()
-  : IdentifierReference("UNINITIALIZED_SPDX_OBJECT")
-{
-}
-
-cmSPDXObject::cmSPDXObject(cmSPDXObject const& other)
-{
-  switch (other.SerializationBase.getTypeId()) {
-#define X_SPDX(classtype, enumid, member, camel)                              \
-  case cmSPDXSerializationBase::enumid:                                       \
-    new (&member) classtype(other.member);                                    \
-    break;
-#include "cmSPDXTypes.def"
-    default:
-      new (&IdentifierReference)
-        cmSPDXIdentifierReference("UNINITIALIZED_SPDX_OBJECT");
-  }
-}
-
-cmSPDXObject::cmSPDXObject(cmSPDXObject&& other) noexcept
-{
-  switch (other.SerializationBase.getTypeId()) {
-#define X_SPDX(classtype, enumid, member, camel)                              \
-  case cmSPDXSerializationBase::enumid:                                       \
-    new (&member) classtype(std::move(other.member));                         \
-    break;
-#include "cmSPDXTypes.def"
-    default:
-      new (&IdentifierReference)
-        cmSPDXIdentifierReference("UNINITIALIZED_SPDX_OBJECT");
-  }
-}
-
-cmSPDXObject& cmSPDXObject::operator=(cmSPDXObject const& other)
-{
-  this->~cmSPDXObject();
-  new (this) cmSPDXObject(other);
-  return *this;
-}
-
-cmSPDXObject& cmSPDXObject::operator=(cmSPDXObject&& other) noexcept
-{
-  this->~cmSPDXObject();
-  new (this) cmSPDXObject(std::move(other));
-  return *this;
-}
-
-cmSPDXObject::~cmSPDXObject()
-{
-  switch (SerializationBase.getTypeId()) {
-#define X_SPDX(classtype, enumid, member, camel)                              \
-  case cmSPDXSerializationBase::enumid:                                       \
-    member.~classtype();                                                      \
-    break;
-#include "cmSPDXTypes.def"
-    default:
-      break;
-  }
-}
-
-Json::Value cmSPDXObject::toJsonLD() const
-{
-  switch (SerializationBase.getTypeId()) {
-#define X_SPDX(classtype, enumid, member, camel)                              \
-  case cmSPDXSerializationBase::enumid:                                       \
-    return member.toJsonLD();
-#include "cmSPDXTypes.def"
-    default:
-      return "INVALID_SPDX_OBJECT_TYPE_ID";
-  }
-}
-
-cmSPDXSimpleGraph::cmSPDXSimpleGraph(std::string iriBase,
-                                     cmSPDXCreationInfo creationInfo)
-  : IRIBase(std::move(iriBase))
-  , CreationInfo(&insert<cmSPDXCreationInfo>(std::move(creationInfo)))
-{
-}
-
-cmSPDXCreationInfo& cmSPDXSimpleGraph::getCreationInfo()
-{
-  return *CreationInfo;
-}
-
-Json::Value cmSPDXSimpleGraph::toJsonLD()
-{
-  Json::Value obj(Json::objectValue);
-  obj["@context"] = "https://spdx.org/rdf/3.0.1/spdx-context.jsonld";
-
-  auto& graph = obj["@graph"];
-  for (auto const& it : Graph) {
-    graph.append(it.second.toJsonLD());
-  }
-
-  return obj;
-}
diff --git a/Source/cmSPDXSerializer.h b/Source/cmSPDXSerializer.h
deleted file mode 100644
index 65bf214..0000000
--- a/Source/cmSPDXSerializer.h
+++ /dev/null
@@ -1,1008 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file LICENSE.rst or https://cmake.org/licensing for details.  */
-#pragma once
-
-#include <array>
-#include <cstddef>
-#include <map>
-#include <new>
-#include <string>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include <cm/optional>
-#include <cm/type_traits>
-
-#include <cm3p/json/value.h>
-
-#include "cmStringAlgorithms.h"
-
-// Base Class
-
-struct cmSPDXSerializationBase
-{
-  enum SPDXTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-
-#define X_SPDX(classtype, enumid, title, camel) enumid,
-#include "cmSPDXTypes.def"
-
-    SPDX_TYPE_ID_MAX,
-  };
-
-  SPDXTypeId getTypeId() const;
-
-  std::string NodeId;
-
-protected:
-  cmSPDXSerializationBase(SPDXTypeId id);
-  cmSPDXSerializationBase(SPDXTypeId id, std::string nodeId);
-
-private:
-  SPDXTypeId TypeId;
-};
-
-// Convenience Classes
-
-struct cmSPDXIdentifierReference : cmSPDXSerializationBase
-{
-  cmSPDXIdentifierReference();
-  cmSPDXIdentifierReference(cmSPDXSerializationBase const& ref);
-  cmSPDXIdentifierReference(std::string const& ref);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXNonElementBase : cmSPDXSerializationBase
-{
-protected:
-  cmSPDXNonElementBase(SPDXTypeId id);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Core Data Types
-
-// Nominally these are supposed to be validated strings
-using cmSPDXDateTime = std::string;
-using cmSPDXMediaType = std::string;
-using cmSPDXSemVer = std::string;
-
-// SPDX Core Enums
-
-struct cmSPDXAnnotationType
-{
-  enum cmSPDXAnnotationTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    OTHER,
-    REVIEW,
-  };
-
-  cmSPDXAnnotationTypeId TypeId;
-
-  cmSPDXAnnotationType(cmSPDXAnnotationTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXExternalIdentifierType
-{
-  enum cmSPDXExternalIdentifierTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    CPE22,
-    CPE23,
-    CVE,
-    EMAIL,
-    GITOID,
-    OTHER,
-    PACKAGE_URL,
-    SECURITY_OTHER,
-    SWHID,
-    SWID,
-    URL_SCHEME,
-  };
-
-  cmSPDXExternalIdentifierTypeId TypeId;
-
-  cmSPDXExternalIdentifierType(
-    cmSPDXExternalIdentifierTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXExternalRefType
-{
-  enum cmSPDXExternalRefTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    ALT_DOWNLOAD_LOCATION,
-    ALT_WEB_PAGE,
-    BINARY_ARTIFACT,
-    BOWER,
-    BUILD_META,
-    BUILD_SYSTEM,
-    CERTIFICATION_REPORT,
-    CHAT,
-    COMPONENT_ANALYSIS_REPORT,
-    CWE,
-    DOCUMENTATION,
-    DYNAMIC_ANALYSIS_REPORT,
-    EOL_NOTICE,
-    EXPORT_CONTROL_ASSESSMENT,
-    FUNDING,
-    ISSUE_TRACKER,
-    LICENSE,
-    MAILING_LIST,
-    MAVEN_CENTRAL,
-    METRICS,
-    NPM,
-    NUGET,
-    OTHER,
-    PRIVACY_ASSESSMENT,
-    PRODUCT_METADATA,
-    PURCHASE_ORDER,
-    QUALITY_ASSESSMENT_REPORT,
-    RELEASE_HISTORY,
-    RELEASE_NOTES,
-    RISK_ASSESSMENT,
-    RUNTIME_ANALYSIS_REPORT,
-    SECURE_SOFTWARE_ATTESTATION,
-    SECURITY_ADVERSARY_MODEL,
-    SECURITY_ADVISORY,
-    SECURITY_FIX,
-    SECURITY_OTHER,
-    SECURITY_PEN_TEST_REPORT,
-    SECURITY_POLICY,
-    SECURITY_THREAT_MODEL,
-    SOCIAL_MEDIA,
-    SOURCE_ARTIFACT,
-    STATIC_ANALYSIS_REPORT,
-    SUPPORT,
-    VCS,
-    VULNERABILITY_DISCLOSURE_REPORT,
-    VULNERABILITY_EXPLOITABILITY_ASSESSMENT,
-  };
-
-  cmSPDXExternalRefTypeId TypeId;
-
-  cmSPDXExternalRefType(cmSPDXExternalRefTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXHashAlgorithm
-{
-  enum cmSPDXHashAlgorithmId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    ADLER32,
-    BLAKE2B256,
-    BLAKE2B384,
-    BLAKE2B512,
-    BLAKE3,
-    CRYSTALS_DILITHIUM,
-    CRYSTALS_KYBER,
-    FALCON,
-    MD2,
-    MD4,
-    MD5,
-    MD6,
-    OTHER,
-    SHA1,
-    SHA224,
-    SHA256,
-    SHA384,
-    SHA3_224,
-    SHA3_256,
-    SHA3_384,
-    SHA3_512,
-    SHA512,
-  };
-
-  cmSPDXHashAlgorithmId TypeId;
-
-  cmSPDXHashAlgorithm(cmSPDXHashAlgorithmId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXLifecycleScopeType
-{
-  enum cmSPDXLifecycleScopeTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    BUILD,
-    DESIGN,
-    DEVELOPMENT,
-    OTHER,
-    RUNTIME,
-    TEST,
-  };
-
-  cmSPDXLifecycleScopeTypeId TypeId;
-
-  cmSPDXLifecycleScopeType(cmSPDXLifecycleScopeTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXProfileIdentifierType
-{
-  enum cmSPDXProfileIdentifierTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    AI,
-    BUILD,
-    CORE,
-    DATASET,
-    EXPANDED_LICENSING,
-    EXTENSION,
-    LITE,
-    SECURITY,
-    SIMPLE_LICENSING,
-    SOFTWARE,
-  };
-
-  cmSPDXProfileIdentifierTypeId TypeId;
-
-  cmSPDXProfileIdentifierType(cmSPDXProfileIdentifierTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXRelationshipCompletenessType
-{
-  enum cmSPDXRelationshipCompletenessTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    COMPLETE,
-    INCOMPLETE,
-    NO_ASSERTION,
-  };
-
-  cmSPDXRelationshipCompletenessTypeId TypeId;
-
-  cmSPDXRelationshipCompletenessType(
-    cmSPDXRelationshipCompletenessTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXRelationshipType
-{
-  enum cmSPDXRelationshipTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    AFFECTS,
-    AMENDED_BY,
-    ANCESTOR_OF,
-    AVAILABLE_FROM,
-    CONFIGURES,
-    CONTAINS,
-    COORDINATED_BY,
-    COPIED_TO,
-    DELEGATED_TO,
-    DEPENDS_ON,
-    DESCENDANT_OF,
-    DESCRIBES,
-    DOES_NOT_AFFECT,
-    EXPANDS_TO,
-    EXPLOIT_CREATED_BY,
-    FIXED_BY,
-    FIXED_IN,
-    FOUND_BY,
-    GENERATES,
-    HAS_ADDED_FILE,
-    HAS_ASSESSMENT_FOR,
-    HAS_ASSOCIATED_VULNERABILITY,
-    HAS_CONCLUDED_LICENSE,
-    HAS_DATA_FILE,
-    HAS_DECLARED_LICENSE,
-    HAS_DELETED_FILE,
-    HAS_DEPENDENCY_MANIFEST,
-    HAS_DISTRIBUTION_ARTIFACT,
-    HAS_DOCUMENTATION,
-    HAS_DYNAMIC_LINK,
-    HAS_EVIDENCE,
-    HAS_EXAMPLE,
-    HAS_HOST,
-    HAS_INPUT,
-    HAS_METADATA,
-    HAS_OPTIONAL_COMPONENT,
-    HAS_OPTIONAL_DEPENDENCY,
-    HAS_OUTPUT,
-    HAS_PREREQUISITE,
-    HAS_PROVIDED_DEPENDENCY,
-    HAS_REQUIREMENT,
-    HAS_SPECIFICATION,
-    HAS_STATIC_LINK,
-    HAS_TEST,
-    HAS_TEST_CASE,
-    HAS_VARIANT,
-    INVOKED_BY,
-    MODIFIED_BY,
-    OTHER,
-    PACKAGED_BY,
-    PATCHED_BY,
-    PUBLISHED_BY,
-    REPORTED_BY,
-    REPUBLISHED_BY,
-    SERIALIZED_IN_ARTIFACT,
-    TESTED_ON,
-    TRAINED_ON,
-    UNDER_INVESTIGATION_FOR,
-    USES_TOOL,
-  };
-
-  cmSPDXRelationshipTypeId TypeId;
-
-  cmSPDXRelationshipType(cmSPDXRelationshipTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSupportType
-{
-  enum cmSPDXSupportTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    DEPLOYED,
-    DEVELOPMENT,
-    END_OF_SUPPORT,
-    LIMITED_SUPPORT,
-    NO_ASSERTION,
-    NO_SUPPORT,
-    SUPPORT,
-  };
-
-  cmSPDXSupportTypeId TypeId;
-
-  cmSPDXSupportType(cmSPDXSupportTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Core NonElement Classes, Abstract
-
-struct cmSPDXIntegrityMethod : cmSPDXNonElementBase
-{
-
-  cm::optional<std::string> Comment;
-
-protected:
-  cmSPDXIntegrityMethod(SPDXTypeId id);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Core NonElement Classes, Concrete
-
-struct cmSPDXCreationInfo : cmSPDXNonElementBase
-{
-  cm::optional<std::string> Comment;
-  cmSPDXDateTime Created;
-  std::vector<cmSPDXIdentifierReference> CreatedBy;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> CreatedUsing;
-
-  cmSPDXCreationInfo();
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXDictionaryEntry : cmSPDXNonElementBase
-{
-  std::string Key;
-  cm::optional<std::string> Value;
-
-  cmSPDXDictionaryEntry();
-  cmSPDXDictionaryEntry(std::string key);
-  cmSPDXDictionaryEntry(std::string key, std::string val);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXExternalIdentifier : cmSPDXNonElementBase
-{
-  cm::optional<std::string> Comment;
-  cmSPDXExternalIdentifierType ExternalIdentifierType;
-  std::string Identifier;
-  cm::optional<std::vector<std::string>> IdentifierLocator;
-  cm::optional<std::string> IssuingAuthority;
-
-  cmSPDXExternalIdentifier();
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXExternalMap : cmSPDXNonElementBase
-{
-  cm::optional<cmSPDXIdentifierReference> DefiningArtifact;
-  std::string ExternalSpdxId;
-  cm::optional<std::string> LocationHint;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> IntegrityMethod;
-
-  cmSPDXExternalMap();
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXExternalRef : cmSPDXNonElementBase
-{
-  cm::optional<std::string> Comment;
-  cm::optional<cmSPDXMediaType> ContentType;
-  cm::optional<cmSPDXExternalRefType> ExternalRefType;
-  cm::optional<std::vector<std::string>> Locator;
-
-  cmSPDXExternalRef();
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXHash : cmSPDXIntegrityMethod
-{
-  cmSPDXHashAlgorithm Algorithm;
-  std::string HashValue;
-
-  cmSPDXHash();
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXNamespaceMap : cmSPDXNonElementBase
-{
-  std::string Namespace;
-  std::string Prefix;
-
-  cmSPDXNamespaceMap();
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXPackageVerificationCode : cmSPDXIntegrityMethod
-{
-  cmSPDXHashAlgorithm Algorithm;
-  std::string HashValue;
-  cm::optional<std::vector<std::string>> PackageVerificationCodeExcludedFile;
-
-  cmSPDXPackageVerificationCode();
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXPositiveIntegerRange : cmSPDXNonElementBase
-{
-  unsigned int BeginIntegerRange;
-  unsigned int EndIntegerRange;
-
-  cmSPDXPositiveIntegerRange(unsigned int beingIntegerRange = 0,
-                             unsigned int endIntegerRange = 0);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Core Element Classes, Abstract
-
-struct cmSPDXElement : cmSPDXSerializationBase
-{
-  cm::optional<std::string> Comment;
-  cmSPDXIdentifierReference CreationInfo;
-  cm::optional<std::string> Description;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> Extension;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> ExternalIdentifier;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> ExternalRef;
-  cm::optional<std::string> Name;
-  // SpdxId is the NodeId
-  cm::optional<std::string> Summary;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> VerifiedUsing;
-
-protected:
-  cmSPDXElement(SPDXTypeId id, cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXArtifact : cmSPDXElement
-{
-  cm::optional<cmSPDXDateTime> BuiltTime;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> OriginatedBy;
-  cm::optional<cmSPDXDateTime> ReleaseTime;
-  cm::optional<std::vector<std::string>> StandardName;
-  cm::optional<cmSPDXIdentifierReference> SuppliedBy;
-  cm::optional<std::vector<cmSPDXSupportType>> SupportType;
-  cm::optional<cmSPDXDateTime> ValidUntilTime;
-
-protected:
-  cmSPDXArtifact(SPDXTypeId id, cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXElementCollection : cmSPDXElement
-{
-  cm::optional<std::vector<cmSPDXIdentifierReference>> Element;
-  cm::optional<std::vector<cmSPDXProfileIdentifierType>> ProfileConformance;
-  cm::optional<std::vector<cmSPDXIdentifierReference>> RootElement;
-
-protected:
-  cmSPDXElementCollection(SPDXTypeId id,
-                          cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Implicit Core Element Classes, Abstract
-
-// Nominally an inheritable class, but adds nothing to Element
-using cmSPDXAgentAbstract = cmSPDXElement;
-
-struct cmSPDXBundleAbstract : cmSPDXElementCollection
-{
-  std::string Context;
-
-protected:
-  cmSPDXBundleAbstract(SPDXTypeId id, cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-// Nominally an inheritable class, but adds nothing to Bundle
-using cmSPDXBomAbstract = cmSPDXBundleAbstract;
-
-struct cmSPDXRelationshipAbstract : cmSPDXElement
-{
-  cm::optional<cmSPDXRelationshipCompletenessType> Completeness;
-  cm::optional<cmSPDXDateTime> EndTime;
-  cmSPDXIdentifierReference From;
-  cmSPDXRelationshipType RelationshipType;
-  cm::optional<cmSPDXDateTime> StartTime;
-  std::vector<cmSPDXIdentifierReference> To;
-
-protected:
-  cmSPDXRelationshipAbstract(SPDXTypeId id,
-                             cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Core Element Classes, Concrete
-
-struct cmSPDXAgent : cmSPDXAgentAbstract
-{
-  cmSPDXAgent(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXAnnotation : cmSPDXElement
-{
-  cmSPDXAnnotationType AnnotationType;
-  cm::optional<cmSPDXMediaType> ContentType;
-  cm::optional<std::string> Statement;
-  cmSPDXIdentifierReference Subject;
-
-  cmSPDXAnnotation(cmSPDXCreationInfo const& creationInfo,
-                   cmSPDXIdentifierReference subject = {});
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXBom : cmSPDXBomAbstract
-{
-  cmSPDXBom(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXBundle : cmSPDXBundleAbstract
-{
-  cmSPDXBundle(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXIndividualElement : cmSPDXElement
-{
-  cmSPDXIndividualElement(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXLifecycleScopedRelationship : cmSPDXRelationshipAbstract
-{
-  cm::optional<cmSPDXLifecycleScopeType> Scope;
-
-  cmSPDXLifecycleScopedRelationship(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXOrganization : cmSPDXAgentAbstract
-{
-  cmSPDXOrganization(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXPerson : cmSPDXAgentAbstract
-{
-  cmSPDXPerson(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXRelationship : cmSPDXRelationshipAbstract
-{
-  cmSPDXRelationship(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSoftwareAgent : cmSPDXAgentAbstract
-{
-  cmSPDXSoftwareAgent(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSpdxDocument : cmSPDXElementCollection
-{
-  cm::optional<cmSPDXIdentifierReference> DataLicense;
-  cm::optional<cmSPDXIdentifierReference> ExternalMap;
-  cm::optional<cmSPDXIdentifierReference> NamespaceMap;
-
-  cmSPDXSpdxDocument(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXTool : cmSPDXElement
-{
-  cmSPDXTool(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Software Enums
-
-struct cmSPDXContentIdentifierType
-{
-  enum cmSPDXContentIdentifierTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    GITOID,
-    SWHID,
-  };
-
-  cmSPDXContentIdentifierTypeId TypeId;
-
-  cmSPDXContentIdentifierType(cmSPDXContentIdentifierTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXFileKindType
-{
-  enum cmSPDXFileKindTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    DIRECTORY,
-    FILE,
-  };
-
-  cmSPDXFileKindTypeId TypeId;
-
-  cmSPDXFileKindType(cmSPDXFileKindTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSbomType
-{
-  enum cmSPDXSbomTypeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    ANALYZED,
-    BUILD,
-    DEPLOYED,
-    DESIGN,
-    RUNTIME,
-    SOURCE,
-  };
-
-  cmSPDXSbomTypeId TypeId;
-
-  cmSPDXSbomType(cmSPDXSbomTypeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSoftwarePurpose
-{
-  enum cmSPDXSoftwarePurposeId
-  {
-    INVALID = -1,
-    NULL_ID = 0,
-    APPLICATION,
-    ARCHIVE,
-    BOM,
-    CONFIGURATION,
-    CONTAINER,
-    DATA,
-    DEVICE,
-    DEVICE_DRIVER,
-    DISK_IMAGE,
-    DOCUMENTATION,
-    EVIDENCE,
-    EXECUTABLE,
-    FILE,
-    FILESYSTEM_IMAGE,
-    FIRMWARE,
-    FRAMEWORK,
-    INSTALL,
-    LIBRARY,
-    MANIFEST,
-    MODEL,
-    MODULE,
-    OPERATING_SYSTEM,
-    OTHER,
-    PATCH,
-    PLATFORM,
-    REQUIREMENT,
-    SOURCE,
-    SPECIFICATION,
-    TEST,
-  };
-
-  cmSPDXSoftwarePurposeId TypeId;
-
-  cmSPDXSoftwarePurpose(cmSPDXSoftwarePurposeId typeId = NULL_ID);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Software NonElement Classes, Concrete
-
-struct cmSPDXContentIdentifier : cmSPDXIntegrityMethod
-{
-  cmSPDXContentIdentifierType ContentIdentifierType;
-  std::string ContentIdentifierValue;
-
-  cmSPDXContentIdentifier();
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Software Element Classes, Abstract
-
-struct cmSPDXSoftwareArtifact : cmSPDXArtifact
-{
-  cm::optional<std::vector<cmSPDXSoftwarePurpose>> AdditionalPurpose;
-  cm::optional<std::string> AttributionText;
-  cm::optional<cmSPDXIdentifierReference> ContentIdentifier;
-  cm::optional<std::string> CopyrightText;
-  cm::optional<cmSPDXSoftwarePurpose> PrimaryPurpose;
-
-protected:
-  cmSPDXSoftwareArtifact(SPDXTypeId id,
-                         cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX Software Element Classes, Concrete
-
-struct cmSPDXFile : cmSPDXSoftwareArtifact
-{
-  cm::optional<cmSPDXMediaType> ContentType;
-  cm::optional<cmSPDXFileKindType> FileKind;
-
-  cmSPDXFile(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXPackage : cmSPDXSoftwareArtifact
-{
-  cm::optional<std::string> DownloadLocation;
-  cm::optional<std::string> HomePage;
-  cm::optional<std::string> PackageUrl;
-  cm::optional<std::string> PackageVersion;
-  cm::optional<std::string> SourceInfo;
-
-  cmSPDXPackage(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSbom : cmSPDXBomAbstract
-{
-  cm::optional<std::vector<cmSPDXSbomType>> SbomType;
-
-  cmSPDXSbom(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSnippet : cmSPDXSoftwareArtifact
-{
-  cm::optional<cmSPDXIdentifierReference> ByteRange;
-  cm::optional<cmSPDXIdentifierReference> LineRange;
-  cmSPDXIdentifierReference SnippetFromFile;
-
-  cmSPDXSnippet(cmSPDXCreationInfo const& creationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-// SPDX SimpleLicensing Element Classes, Abstract
-
-// Nominally an inheritable class, but adds nothing to Element
-using cmSPDXAnyLicenseInfo = cmSPDXElement;
-
-// SPDX SimpleLicensing Element Classes, Concrete
-
-struct cmSPDXLicenseExpression : cmSPDXAnyLicenseInfo
-{
-  cm::optional<std::vector<cmSPDXIdentifierReference>> CustomIdToUri;
-  std::string LicenseExpression;
-  cm::optional<cmSPDXSemVer> LicenseListVersion;
-
-  cmSPDXLicenseExpression(cmSPDXCreationInfo const& CreationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-struct cmSPDXSimpleLicensingText : cmSPDXElement
-{
-  std::string LicenseText;
-
-  cmSPDXSimpleLicensingText(cmSPDXCreationInfo const& CreationInfo);
-
-  Json::Value toJsonLD() const;
-};
-
-// Graph Manipulation
-
-template <typename T>
-cmSPDXSerializationBase::SPDXTypeId cmSPDXGetTypeId();
-
-template <typename T>
-std::string cmSPDXGetTypeName();
-
-#define X_SPDX(classtype, enumid, member, camel)                              \
-  template <>                                                                 \
-  cmSPDXSerializationBase::SPDXTypeId cmSPDXGetTypeId<classtype>();           \
-                                                                              \
-  template <>                                                                 \
-  std::string cmSPDXGetTypeName<classtype>();
-#include "cmSPDXTypes.def"
-
-template <class T>
-struct cmSPDXTag
-{
-  using type = T;
-};
-
-union cmSPDXObject
-{
-
-  cmSPDXSerializationBase SerializationBase;
-
-  cmSPDXObject();
-  cmSPDXObject(cmSPDXObject const& other);
-  cmSPDXObject(cmSPDXObject&& other) noexcept;
-
-#define X_SPDX(classtype, enumid, member, camel)                              \
-  classtype member;                                                           \
-  cmSPDXObject(classtype val);                                                \
-                                                                              \
-  template <typename... Args>                                                 \
-  cmSPDXObject(cmSPDXTag<classtype>, Args&&... args)                          \
-  {                                                                           \
-    new (&member) classtype(std::forward<Args>(args)...);                     \
-  }
-#include "cmSPDXTypes.def"
-
-  cmSPDXObject& operator=(cmSPDXObject const& other);
-  cmSPDXObject& operator=(cmSPDXObject&& other) noexcept;
-
-  ~cmSPDXObject();
-
-  template <typename T>
-  T* get()
-  {
-    T* ptr;
-    get(&ptr);
-    return ptr;
-  }
-
-  Json::Value toJsonLD() const;
-
-private:
-#define X_SPDX(classtype, enumid, member, camel) void get(classtype** ptr);
-#include "cmSPDXTypes.def"
-};
-
-struct cmSPDXSimpleGraph
-{
-  cmSPDXSimpleGraph(std::string iriBase, cmSPDXCreationInfo creationInfo = {});
-
-  template <typename T, typename... Args>
-  cm::enable_if_t<std::is_base_of<cmSPDXElement, T>::value, T>& insert(
-    Args&&... args)
-  {
-    std::string nodeId = cmStrCat(IRIBase, IRICount++);
-    auto const& it =
-      Graph.emplace(std::piecewise_construct, std::forward_as_tuple(nodeId),
-                    std::forward_as_tuple(cmSPDXTag<T>{}, *CreationInfo,
-                                          std::forward<Args>(args)...));
-    auto& node = *it.first->second.template get<T>();
-    node.NodeId = std::move(nodeId);
-    return node;
-  }
-
-  cmSPDXCreationInfo& getCreationInfo();
-
-  template <typename T, typename... Args>
-  cm::enable_if_t<std::is_base_of<cmSPDXNonElementBase, T>::value, T>& insert(
-    Args&&... args)
-  {
-    std::size_t nodeCount = BlankCounts[cmSPDXGetTypeId<T>()]++;
-    std::string nodeId =
-      cmStrCat("_:", cmSPDXGetTypeName<T>(), "_", nodeCount);
-    auto const& it = Graph.emplace(
-      std::piecewise_construct, std::forward_as_tuple(nodeId),
-      std::forward_as_tuple(cmSPDXTag<T>{}, std::forward<Args>(args)...));
-    auto& node = *it.first->second.template get<T>();
-    node.NodeId = std::move(nodeId);
-    return node;
-  }
-
-  template <typename T>
-  T* get(std::string const& key)
-  {
-    auto it = Graph.find(key);
-    if (it == Graph.end()) {
-      return nullptr;
-    }
-    return it->second.get<T>();
-  }
-
-  template <typename T>
-  T* get(cmSPDXIdentifierReference const& key)
-  {
-    auto it = Graph.find(key.NodeId);
-    if (it == Graph.end()) {
-      return nullptr;
-    }
-    return it->second.get<T>();
-  }
-
-  Json::Value toJsonLD();
-
-private:
-  std::string IRIBase;
-  std::size_t IRICount{ 0 };
-  std::array<std::size_t, cmSPDXSerializationBase::SPDX_TYPE_ID_MAX>
-    BlankCounts{};
-
-  std::map<std::string, cmSPDXObject> Graph;
-  cmSPDXCreationInfo* CreationInfo;
-};
diff --git a/Source/cmSPDXTypes.def b/Source/cmSPDXTypes.def
deleted file mode 100644
index 88d8043..0000000
--- a/Source/cmSPDXTypes.def
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file LICENSE.rst or https://cmake.org/licensing for details.  */
-#ifndef X_SPDX
-#  define X_SPDX(classtype, enumid, member, camel)
-#endif
-
-// Convenience
-X_SPDX(cmSPDXIdentifierReference, CM_IDENTIFIER_REFERENCE, IdentifierReference,
-       identifierReference)
-
-// Core
-X_SPDX(cmSPDXAgent, CORE_AGENT, Agent, agent)
-X_SPDX(cmSPDXAnnotation, CORE_ANNOTATION, Annotation, annotation)
-X_SPDX(cmSPDXBom, CORE_BOM, Bom, bom)
-X_SPDX(cmSPDXBundle, CORE_BUNDLE, Bundle, bundle)
-X_SPDX(cmSPDXCreationInfo, CORE_CREATION_INFO, CreationInfo, creationInfo)
-X_SPDX(cmSPDXDictionaryEntry, CORE_DICTIONARY_ENTRY, DictionaryEntry,
-       dictionaryEntry)
-X_SPDX(cmSPDXExternalIdentifier, CORE_EXTERNAL_IDENTIFIER, ExternalIdentifier,
-       externalIdentifier)
-X_SPDX(cmSPDXExternalMap, CORE_EXTERNAL_MAP, ExternalMap, externalMap)
-X_SPDX(cmSPDXExternalRef, CORE_EXTERNAL_REF, ExternalRef, externalRef)
-X_SPDX(cmSPDXHash, CORE_HASH, Hash, hash)
-X_SPDX(cmSPDXIndividualElement, CORE_INDIVIDUAL_ELEMENT, IndividualElement,
-       individualElement)
-X_SPDX(cmSPDXLifecycleScopedRelationship, CORE_LIFECYCLE_SCOPED_RELATIONSHIP,
-       LifecycleScopedRelationship, lifecycleScopedRelationship)
-X_SPDX(cmSPDXNamespaceMap, CORE_NAMESPACE_MAP, NamespaceMap, namespaceMap)
-X_SPDX(cmSPDXOrganization, CORE_ORGANIZATION, Organization, organization)
-X_SPDX(cmSPDXPackageVerificationCode, CORE_PACKAGE_VERIFICATION_CODE,
-       PackageVerificationCode, packageVerificationCode)
-X_SPDX(cmSPDXPerson, CORE_PERSON, Person, person)
-X_SPDX(cmSPDXPositiveIntegerRange, CORE_POSITIVE_INTEGER_RANGE,
-       PositiveIntegerRange, positiveIntegerRange)
-X_SPDX(cmSPDXRelationship, CORE_RELATIONSHIP, Relationship, relationship)
-X_SPDX(cmSPDXSoftwareAgent, CORE_SOFTWARE_AGENT, SoftwareAgent, softwareAgent)
-X_SPDX(cmSPDXSpdxDocument, CORE_SPDX_DOCUMENT, SpdxDocument, spdxDocument)
-X_SPDX(cmSPDXTool, CORE_TOOL, Tool, tool)
-
-// Software
-X_SPDX(cmSPDXContentIdentifier, SOFTWARE_CONTENT_IDENTIFIER, ContentIdentifier,
-       contentIdentifier)
-X_SPDX(cmSPDXFile, SOFTWARE_FILE, File, file)
-X_SPDX(cmSPDXPackage, SOFTWARE_PACKAGE, Package, package)
-X_SPDX(cmSPDXSbom, SOFTWARE_SBOM, Sbom, sbom)
-X_SPDX(cmSPDXSnippet, SOFTWARE_SNIPPET, Snippet, snippet)
-
-// SimpleLicensing
-X_SPDX(cmSPDXLicenseExpression, SIMPLE_LICENSING_LICENSE_EXPRESSION,
-       LicenseExpression, licenseExpression)
-X_SPDX(cmSPDXSimpleLicensingText, SIMPLE_LICENSING_SIMPLE_LICENSING_TEXT,
-       SimpleLicensingText, simpleLicensingText)
-
-#undef X_SPDX
diff --git a/Source/cmSbomObject.h b/Source/cmSbomObject.h
new file mode 100644
index 0000000..3f42cfb
--- /dev/null
+++ b/Source/cmSbomObject.h
@@ -0,0 +1,270 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm/type_traits>
+
+#include "cmSbomSerializer.h"
+
+class cmSbomObject;
+
+template <typename T>
+inline void SerializeDispatch(T const& t, cmSbomSerializer& serializer)
+{
+  serializer.BeginObject();
+  t.Serialize(serializer);
+  serializer.EndObject();
+}
+
+template <typename T>
+inline void SerializeDispatch(T* p, cmSbomSerializer& serializer)
+{
+  if (p->SpdxId) {
+    serializer.AddReference(*p->SpdxId);
+  }
+}
+
+inline void SerializeDispatch(std::nullptr_t, cmSbomSerializer&)
+{
+}
+
+struct ObjectInterface
+{
+  virtual ObjectInterface* Copy(void* storage) const = 0;
+  virtual ObjectInterface* Move(void* storage) = 0;
+  virtual void* Addr() noexcept = 0;
+  virtual void const* Addr() const noexcept = 0;
+  virtual void SerializeImpl(cmSbomSerializer& os) const = 0;
+  virtual ~ObjectInterface() = default;
+
+  ObjectInterface() = default;
+  ObjectInterface(ObjectInterface const&) = default;
+  ObjectInterface& operator=(ObjectInterface const&) = default;
+  ObjectInterface(ObjectInterface&&) noexcept = default;
+  ObjectInterface& operator=(ObjectInterface&&) noexcept = default;
+};
+
+namespace impl {
+
+#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20150623
+using max_align_t = ::max_align_t;
+#else
+using max_align_t = std::max_align_t;
+#endif
+
+enum class StorageKind
+{
+  Stack,
+  Heap
+};
+
+template <typename, StorageKind>
+struct Storage
+{
+};
+
+template <typename T>
+struct Storage<T, StorageKind::Stack> : ObjectInterface
+{
+  using ValueType = cm::decay_t<T>;
+
+  explicit Storage(T x)
+    : Value(std::move(x))
+  {
+  }
+
+  Storage(Storage&&) noexcept(
+    std::is_nothrow_move_constructible<ValueType>::value) = default;
+  ~Storage() override = default;
+
+  void* Addr() noexcept override { return std::addressof(Value); }
+  void const* Addr() const noexcept override { return std::addressof(Value); }
+
+  ValueType& get() { return Value; }
+  ValueType const& get() const { return Value; }
+
+private:
+  ValueType Value;
+};
+
+template <typename T>
+struct Storage<T, StorageKind::Heap> : ObjectInterface
+{
+  using ValueType = cm::decay_t<T>;
+
+  explicit Storage(T x)
+    : Ptr(new T(std::move(x)))
+  {
+  }
+
+  Storage(Storage&&) noexcept = default;
+  ~Storage() override = default;
+
+  void* Addr() noexcept override { return Ptr.get(); }
+  void const* Addr() const noexcept override { return Ptr.get(); }
+
+  ValueType& get() { return *Ptr; }
+  ValueType const& get() const { return *Ptr; }
+
+private:
+  std::unique_ptr<ValueType> Ptr;
+};
+
+template <>
+struct Storage<std::nullptr_t, StorageKind::Stack> : ObjectInterface
+{
+  explicit Storage(std::nullptr_t) {}
+
+  Storage(Storage&&) noexcept = default;
+  ~Storage() override = default;
+
+  void* Addr() noexcept override { return nullptr; }
+  void const* Addr() const noexcept override { return nullptr; }
+
+  void SerializeImpl(cmSbomSerializer&) const override {}
+
+  std::nullptr_t get() { return nullptr; }
+  std::nullptr_t get() const { return nullptr; }
+};
+
+}
+
+struct ObjectStorage
+{
+  template <typename T>
+  using Stack = impl::Storage<T, impl::StorageKind::Stack>;
+  template <typename T>
+  using Heap = impl::Storage<T, impl::StorageKind::Heap>;
+
+  static constexpr std::size_t BufferSize = 128u;
+
+  static constexpr std::size_t Size = sizeof(Heap<std::nullptr_t>) > BufferSize
+    ? sizeof(Heap<std::nullptr_t>)
+    : BufferSize;
+  static constexpr std::size_t Align = alignof(impl::max_align_t);
+
+  struct Buffer
+  {
+    alignas(Align) unsigned char Data[Size];
+  };
+
+  template <typename Concrete>
+  using Model = cm::conditional_t<sizeof(Stack<Concrete>) <= Size &&
+                                    alignof(Stack<Concrete>) <= Align,
+                                  Stack<Concrete>, Heap<Concrete>>;
+};
+
+class cmSbomObject
+{
+public:
+  template <typename T>
+  struct Instance : ObjectStorage::Model<T>
+  {
+    using Base = ObjectStorage::Model<T>;
+    using Base::Base;
+    Instance(Instance&&) noexcept = default;
+
+    ObjectInterface* Copy(void* storage) const override
+    {
+      return ::new (storage) Instance(this->get());
+    }
+
+    ObjectInterface* Move(void* storage) override
+    {
+      return ::new (storage) Instance(std::move(*this));
+    }
+
+    void SerializeImpl(cmSbomSerializer& os) const override
+    {
+      SerializeDispatch(this->get(), os);
+    }
+  };
+
+  cmSbomObject() { ::new (Storage()) Instance<std::nullptr_t>(nullptr); }
+  cmSbomObject(std::nullptr_t)
+    : cmSbomObject()
+  {
+  }
+
+  template <
+    typename T, typename Decayed = cm::remove_cv_t<cm::remove_reference_t<T>>,
+    typename = cm::enable_if_t<!std::is_same<Decayed, cmSbomObject>::value>>
+  cmSbomObject(T&& x)
+  {
+    ::new (Storage()) Instance<Decayed>(std::forward<T>(x));
+  }
+
+  cmSbomObject(cmSbomObject const& other)
+  {
+    other.Interface().Copy(Storage());
+  }
+  cmSbomObject(cmSbomObject&& other) noexcept
+  {
+    other.Interface().Move(Storage());
+  }
+
+  ~cmSbomObject() noexcept { Interface().~ObjectInterface(); }
+
+  cmSbomObject& operator=(cmSbomObject rhs)
+  {
+    Interface().~ObjectInterface();
+    rhs.Interface().Move(Storage());
+    return *this;
+  }
+
+  bool IsNull() const noexcept { return Interface().Addr() == nullptr; }
+  explicit operator bool() const noexcept { return !IsNull(); }
+
+  void Serialize(cmSbomSerializer& os) const { Interface().SerializeImpl(os); }
+
+  template <typename T>
+  T& CastUnchecked()
+  {
+    return *static_cast<T*>(Interface().Addr());
+  }
+
+  template <typename T>
+  T const& CastUnchecked() const
+  {
+    return *static_cast<T const*>(Interface().Addr());
+  }
+
+  ObjectInterface& Interface()
+  {
+    return *static_cast<ObjectInterface*>(Storage());
+  }
+  ObjectInterface const& Interface() const
+  {
+    return *static_cast<ObjectInterface const*>(Storage());
+  }
+
+  void* Storage() { return &Data.Data; }
+  void const* Storage() const { return &Data.Data; }
+
+  template <typename U>
+  U* ptr() noexcept
+  {
+    return std::addressof(this->template CastUnchecked<U>());
+  }
+
+  template <typename U>
+  U const* ptr() const noexcept
+  {
+    return std::addressof(this->template CastUnchecked<U>());
+  }
+
+private:
+  ObjectStorage::Buffer Data{};
+};
+
+template <typename T, typename U = cm::decay_t<T>>
+U* insert_back(std::vector<cmSbomObject>& vec, T&& obj) noexcept
+{
+  vec.emplace_back(std::forward<U>(obj));
+  return std::addressof(vec.back().CastUnchecked<U>());
+}
diff --git a/Source/cmSbomSerializer.h b/Source/cmSbomSerializer.h
new file mode 100644
index 0000000..019a1c7
--- /dev/null
+++ b/Source/cmSbomSerializer.h
@@ -0,0 +1,38 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+
+class cmSbomObject;
+class cmSbomSerializer
+{
+public:
+  cmSbomSerializer() = default;
+  cmSbomSerializer(cmSbomSerializer const&) = default;
+  cmSbomSerializer(cmSbomSerializer&&) = default;
+  cmSbomSerializer& operator=(cmSbomSerializer const&) = default;
+  cmSbomSerializer& operator=(cmSbomSerializer&&) = default;
+
+  virtual void BeginObject() {}
+  virtual void EndObject() {}
+  virtual void BeginArray() {}
+  virtual void EndArray() {}
+
+  virtual void AddReference(std::string const& id) = 0;
+
+  virtual void AddString(std::string const& key, std::string const& value) = 0;
+  virtual void AddVisitable(std::string const& key,
+                            cmSbomObject const& visitable) = 0;
+
+  virtual void AddVectorIfPresent(std::string const& key,
+                                  std::vector<cmSbomObject> const& vec) = 0;
+  virtual void AddVectorIfPresent(std::string const& key,
+                                  std::vector<std::string> const& vec) = 0;
+
+  virtual bool WriteSbom(std::ostream& os, cmSbomObject const& document) = 0;
+  virtual ~cmSbomSerializer() = default;
+};
diff --git a/Source/cmSpdx.cxx b/Source/cmSpdx.cxx
new file mode 100644
index 0000000..6bd37ce
--- /dev/null
+++ b/Source/cmSpdx.cxx
@@ -0,0 +1,520 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#include "cmSpdx.h"
+
+#include <stdexcept>
+#include <string>
+
+#include "cmSbomSerializer.h"
+
+inline void SerializeIfPresent(cmSbomSerializer& s, std::string const& key,
+                               cm::optional<std::string> const& v)
+{
+  if (v) {
+    s.AddString(key, *v);
+  }
+}
+
+std::string to_string(cmSpdxIntegrityMethod::HashAlgorithmId id)
+{
+  switch (id) {
+    case cmSpdxIntegrityMethod::HashAlgorithmId::ADLER32:
+      return "ADLER32";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE2B256:
+      return "BLAKE2B256";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE2B384:
+      return "BLAKE2B384";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE2B512:
+      return "BLAKE2B512";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE3:
+      return "BLAKE3";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::MD2:
+      return "MD2";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::MD4:
+      return "MD4";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::MD5:
+      return "MD5";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::MD6:
+      return "MD6";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA1:
+      return "SHA1";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA224:
+      return "SHA224";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA256:
+      return "SHA256";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA384:
+      return "SHA384";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA512:
+      return "SHA512";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA3_256:
+      return "SHA3_256";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA3_384:
+      return "SHA3_384";
+    case cmSpdxIntegrityMethod::HashAlgorithmId::SHA3_512:
+      return "SHA3_512";
+  }
+  throw std::invalid_argument("Unknown HashAlgorithmId");
+}
+
+std::string to_string(cmSpdxSoftwareArtifact::PurposeId id)
+{
+  switch (id) {
+    case cmSpdxSoftwareArtifact::PurposeId::APPLICATION:
+      return "APPLICATION";
+    case cmSpdxSoftwareArtifact::PurposeId::ARCHIVE:
+      return "ARCHIVE";
+    case cmSpdxSoftwareArtifact::PurposeId::CONTAINER:
+      return "CONTAINER";
+    case cmSpdxSoftwareArtifact::PurposeId::DATA:
+      return "DATA";
+    case cmSpdxSoftwareArtifact::PurposeId::DEVICE:
+      return "DEVICE";
+    case cmSpdxSoftwareArtifact::PurposeId::FIRMWARE:
+      return "FIRMWARE";
+    case cmSpdxSoftwareArtifact::PurposeId::FILE:
+      return "FILE";
+    case cmSpdxSoftwareArtifact::PurposeId::INSTALL:
+      return "INSTALL";
+    case cmSpdxSoftwareArtifact::PurposeId::LIBRARY:
+      return "LIBRARY";
+    case cmSpdxSoftwareArtifact::PurposeId::MODULE:
+      return "MODULE";
+    case cmSpdxSoftwareArtifact::PurposeId::OPERATING_SYSTEM:
+      return "OPERATING_SYSTEM";
+    case cmSpdxSoftwareArtifact::PurposeId::SOURCE:
+      return "SOURCE";
+  }
+  throw std::invalid_argument("Unknown PurposeId");
+}
+
+std::string to_string(cmSpdxSbom::TypeId id)
+{
+  switch (id) {
+    case cmSpdxSbom::TypeId::ANALYZED:
+      return "ANALYZED";
+    case cmSpdxSbom::TypeId::BUILD:
+      return "BUILD";
+    case cmSpdxSbom::TypeId::DEPLOYED:
+      return "DEPLOYED";
+    case cmSpdxSbom::TypeId::DESIGN:
+      return "DESIGN";
+    case cmSpdxSbom::TypeId::RUNTIME:
+      return "RUNTIME";
+    case cmSpdxSbom::TypeId::SOURCE:
+      return "SOURCE";
+    case cmSpdxSbom::TypeId::TEST:
+      return "TEST";
+  }
+  throw std::invalid_argument("Unknown Sbom::TypeId");
+}
+
+std::string to_string(cmSpdxFile::FileKindId id)
+{
+  switch (id) {
+    case cmSpdxFile::FileKindId::DIRECTORY:
+      return "DIRECTORY";
+    case cmSpdxFile::FileKindId::FILE:
+      return "FILE";
+  }
+  throw std::invalid_argument("Unknown File::FileKindId");
+}
+
+std::string to_string(cmSpdxRelationship::RelationshipTypeId id)
+{
+  switch (id) {
+    case cmSpdxRelationship::RelationshipTypeId::DESCRIBES:
+      return "DESCRIBES";
+    case cmSpdxRelationship::RelationshipTypeId::CONTAINS:
+      return "CONTAINS";
+    case cmSpdxRelationship::RelationshipTypeId::DEPENDS_ON:
+      return "DEPENDS_ON";
+    case cmSpdxRelationship::RelationshipTypeId::OTHER:
+      return "OTHER";
+  }
+  throw std::invalid_argument("Unknown RelationshipTypeId");
+}
+
+std::string to_string(cmSpdxLifecycleScopedRelationship::ScopeId id)
+{
+  switch (id) {
+    case cmSpdxLifecycleScopedRelationship::ScopeId::BUILD:
+      return "BUILD";
+    case cmSpdxLifecycleScopedRelationship::ScopeId::DESIGN:
+      return "DESIGN";
+    case cmSpdxLifecycleScopedRelationship::ScopeId::RUNTIME:
+      return "RUNTIME";
+    case cmSpdxLifecycleScopedRelationship::ScopeId::TEST:
+      return "TEST";
+  }
+  throw std::invalid_argument("Unknown Lifecycle ScopeId");
+}
+
+std::string to_string(cmSpdxAnnotation::AnnotationTypeId id)
+{
+  switch (id) {
+    case cmSpdxAnnotation::AnnotationTypeId::REVIEW:
+      return "REVIEW";
+    case cmSpdxAnnotation::AnnotationTypeId::OTHER:
+      return "OTHER";
+  }
+  throw std::invalid_argument("Unknown AnnotationTypeId");
+}
+
+std::string to_string(cmSpdxArtifact::SupportTypeId id)
+{
+  switch (id) {
+    case cmSpdxArtifact::SupportTypeId::COMMUNITY:
+      return "COMMUNITY";
+    case cmSpdxArtifact::SupportTypeId::COMMERCIAL:
+      return "COMMERCIAL";
+    case cmSpdxArtifact::SupportTypeId::NONE:
+      return "NONE";
+  }
+  throw std::invalid_argument("Unknown SupportTypeId");
+}
+
+void cmSpdxExternalIdentifier::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "ExternalIdentifier");
+  SerializeIfPresent(serializer, "externalIdentifierType",
+                     ExternalIdentifierType);
+  SerializeIfPresent(serializer, "identifier", Identifier);
+  SerializeIfPresent(serializer, "comment", Comment);
+  SerializeIfPresent(serializer, "identifierLocation", IdentifierLocation);
+  SerializeIfPresent(serializer, "issuingAuthority", IssuingAuthority);
+}
+
+void cmSpdxExternalRef::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "ExternalRef");
+  SerializeIfPresent(serializer, "externalRefType", ExternalRefType);
+  SerializeIfPresent(serializer, "locator", Locator);
+  SerializeIfPresent(serializer, "contentType", ContentType);
+  SerializeIfPresent(serializer, "comment", Comment);
+}
+
+void cmSpdxChecksum::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "Checksum");
+  serializer.AddString("algorithm", to_string(Algorithm));
+  serializer.AddString("checksumValue", ChecksumValue);
+}
+
+void cmSpdxCreationInfo::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "CreationInfo");
+  SerializeIfPresent(serializer, "@id", SpdxId);
+  if (SpecVersion) {
+    serializer.AddString("specVersion", *SpecVersion);
+  }
+  SerializeIfPresent(serializer, "comment", Comment);
+  SerializeIfPresent(serializer, "created", Created);
+  if (!CreatedBy.empty()) {
+    serializer.AddVectorIfPresent("createdBy", CreatedBy);
+  }
+  if (!CreatedUsing.empty()) {
+    serializer.AddVectorIfPresent("createdUsing", CreatedUsing);
+  }
+}
+
+void cmSpdxIntegrityMethod::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "IntegrityMethod");
+  SerializeIfPresent(serializer, "comment", Comment);
+}
+
+void cmSpdxElement::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "Element");
+  SerializeIfPresent(serializer, "@id", SpdxId);
+  SerializeIfPresent(serializer, "name", Name);
+  SerializeIfPresent(serializer, "summary", Summary);
+  SerializeIfPresent(serializer, "description", Description);
+  SerializeIfPresent(serializer, "comment", Comment);
+  if (CreationInfo) {
+    serializer.AddVisitable("creationInfo", *CreationInfo);
+  }
+  if (VerifiedUsing) {
+    serializer.AddVisitable("verifiedUsing", *VerifiedUsing);
+  }
+  if (!ExternalRef.empty()) {
+    serializer.AddVectorIfPresent("externalRef", ExternalRef);
+  }
+  if (!ExternalIdentifier.empty()) {
+    serializer.AddVectorIfPresent("externalIdentifier", ExternalIdentifier);
+  }
+  if (Extension) {
+    serializer.AddVisitable("extension", *Extension);
+  }
+}
+
+void cmSpdxTool::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElement::Serialize(serializer);
+  serializer.AddString("type", "Tool");
+}
+
+void cmSpdxAgent::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElement::Serialize(serializer);
+  serializer.AddString("type", "Agent");
+}
+
+void cmSpdxOrganization::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxAgent::Serialize(serializer);
+  serializer.AddString("type", "Organization");
+}
+
+void cmSpdxPerson::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxAgent::Serialize(serializer);
+  serializer.AddString("type", "Person");
+}
+
+void cmSpdxSoftwareAgent::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxAgent::Serialize(serializer);
+  serializer.AddString("type", "SoftwareAgent");
+}
+
+void cmSpdxPositiveIntegerRange::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "PositiveIntegerRange");
+  SerializeIfPresent(serializer, "beginIntegerRange", BeginIntegerRange);
+  SerializeIfPresent(serializer, "endIntegerRange", EndIntegerRange);
+}
+
+void cmSpdxRelationship::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElement::Serialize(serializer);
+  serializer.AddString("type", "Relationship");
+  if (From) {
+    serializer.AddVisitable("from", *From);
+  }
+  if (!To.empty()) {
+    serializer.AddVectorIfPresent("to", To);
+  }
+  if (RelationshipType) {
+    serializer.AddString("relationshipType", to_string(*RelationshipType));
+  }
+  SerializeIfPresent(serializer, "startTime", StartTime);
+  SerializeIfPresent(serializer, "endTime", EndTime);
+}
+
+void cmSpdxLifecycleScopedRelationship::Serialize(
+  cmSbomSerializer& serializer) const
+{
+  cmSpdxRelationship::Serialize(serializer);
+  serializer.AddString("type", "LifecycleScopedRelationship");
+  if (Scope) {
+    serializer.AddString("scope", to_string(*Scope));
+  }
+}
+
+void cmSpdxArtifact::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElement::Serialize(serializer);
+  serializer.AddString("type", "Artifact");
+  if (!OriginatedBy.empty()) {
+    serializer.AddVectorIfPresent("originatedBy", OriginatedBy);
+  }
+  if (SuppliedBy) {
+    serializer.AddVisitable("suppliedBy", *SuppliedBy);
+  }
+  SerializeIfPresent(serializer, "builtTime", BuiltTime);
+  SerializeIfPresent(serializer, "releaseTime", ReleaseTime);
+  SerializeIfPresent(serializer, "validUntilTime", ValidUntilTime);
+  SerializeIfPresent(serializer, "standardName", StandardName);
+  if (Support) {
+    serializer.AddString("support", to_string(*Support));
+  }
+}
+
+void cmSpdxIndividualElement::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElement::Serialize(serializer);
+  serializer.AddString("type", "IndividualElement");
+}
+
+void cmSpdxAnnotation::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElement::Serialize(serializer);
+  serializer.AddString("type", "Annotation");
+  if (AnnotationType) {
+    serializer.AddString("annotationType", to_string(*AnnotationType));
+  }
+  SerializeIfPresent(serializer, "contentType", ContentType);
+  SerializeIfPresent(serializer, "statement", Statement);
+  if (Element) {
+    serializer.AddVisitable("element", *Element);
+  }
+}
+
+void cmSpdxExternalMap::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "ExternalMap");
+  SerializeIfPresent(serializer, "externalSpdxId", ExternalSpdxId);
+  if (VerifiedUsing) {
+    serializer.AddVisitable("verifiedUsing", *VerifiedUsing);
+  }
+  SerializeIfPresent(serializer, "locationHistory", LocationHistory);
+  if (DefiningArtifact) {
+    serializer.AddVisitable("definingArtifact", *DefiningArtifact);
+  }
+}
+
+void cmSpdxNamespaceMap::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "NamespaceMap");
+  SerializeIfPresent(serializer, "prefix", Prefix);
+  SerializeIfPresent(serializer, "namespace", Namespace);
+}
+
+void cmSpdxElementCollection::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElement::Serialize(serializer);
+  serializer.AddString("type", "ElementCollection");
+  if (!Elements.empty()) {
+    serializer.AddVectorIfPresent("element", Elements);
+  }
+  if (!RootElements.empty()) {
+    serializer.AddVectorIfPresent("rootElement", RootElements);
+  }
+  if (!ProfileConformance.empty()) {
+    serializer.AddVectorIfPresent("profileConformance", ProfileConformance);
+  }
+}
+
+void cmSpdxPackageVerificationCode::Serialize(
+  cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "PackageVerificationCode");
+  if (Algorithm) {
+    serializer.AddString("algorithm", to_string(*Algorithm));
+  }
+  SerializeIfPresent(serializer, "hashValue", HashValue);
+  SerializeIfPresent(serializer, "packageVerificationCodeExcludedFile",
+                     PackageVerificationCodeExcludedFile);
+}
+
+void cmSpdxHash::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString("type", "Hash");
+  serializer.AddString("hashAlgorithm", to_string(HashAlgorithm));
+  serializer.AddString("hashValue", HashValue);
+}
+
+void cmSpdxBundle::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElementCollection::Serialize(serializer);
+  serializer.AddString("type", "Bundle");
+  SerializeIfPresent(serializer, "context", Context);
+}
+
+void cmSpdxBom::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxBundle::Serialize(serializer);
+  serializer.AddString("type", "Bom");
+}
+
+void cmSpdxSbom::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxBom::Serialize(serializer);
+  serializer.AddString("type", "software_Sbom");
+  if (Types) {
+    for (auto const& t : *Types) {
+      serializer.AddString("sbomType", to_string(t));
+    }
+  }
+  if (LifecycleScope) {
+    serializer.AddString("lifecycleScope", to_string(*LifecycleScope));
+  }
+}
+
+void cmSpdxSoftwareArtifact::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxArtifact::Serialize(serializer);
+  serializer.AddString("type", "software_SoftwareArtifact");
+  if (PrimaryPurpose) {
+    serializer.AddString("primaryPurpose", to_string(*PrimaryPurpose));
+  }
+  if (AdditionalPurpose) {
+    for (auto const& p : *AdditionalPurpose) {
+      serializer.AddString("additionalPurpose", to_string(p));
+    }
+  }
+  SerializeIfPresent(serializer, "copyrightText", CopyrightText);
+  SerializeIfPresent(serializer, "attributionText", AttributionText);
+  if (ContentIdentifier) {
+    serializer.AddVisitable("contentIdentifier", *ContentIdentifier);
+  }
+  SerializeIfPresent(serializer, "artifactSize", ArtifactSize);
+}
+
+void cmSpdxPackage::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxSoftwareArtifact::Serialize(serializer);
+  serializer.AddString("type", "software_Package");
+  SerializeIfPresent(serializer, "downloadLocation", DownloadLocation);
+  SerializeIfPresent(serializer, "homePage", Homepage);
+  SerializeIfPresent(serializer, "packageVersion", PackageVersion);
+  SerializeIfPresent(serializer, "packageUrl", PackageUrl);
+  SerializeIfPresent(serializer, "sourceInfo", SourceInfo);
+}
+
+void cmSpdxFile::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxArtifact::Serialize(serializer);
+  serializer.AddString("type", "software_File");
+  if (ContentType) {
+    serializer.AddString("contentType", *ContentType);
+  }
+  if (FileType) {
+    serializer.AddString("fileType", to_string(*FileType));
+  }
+}
+
+void cmSpdxContentIdentifier::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxIntegrityMethod::Serialize(serializer);
+  serializer.AddString("type", "software_ContentIdentifier");
+  SerializeIfPresent(serializer, "contentIdentifierType",
+                     ContentIdentifierType);
+  SerializeIfPresent(serializer, "contentValue", ContentValue);
+}
+
+void cmSpdxSnippet::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxSoftwareArtifact::Serialize(serializer);
+  serializer.AddString("type", "software_Snippet");
+  SerializeIfPresent(serializer, "byteRange", ByteRange);
+  SerializeIfPresent(serializer, "lineRange", LineRange);
+  if (SnippetFromFile) {
+    serializer.AddVisitable("snippetFromFile", *SnippetFromFile);
+  }
+}
+
+void cmSpdxDocument::Serialize(cmSbomSerializer& serializer) const
+{
+  cmSpdxElementCollection::Serialize(serializer);
+
+  serializer.AddString("type", "SpdxDocument");
+  if (ExternalMap) {
+    serializer.AddVisitable("externalMap", *ExternalMap);
+  }
+  if (NamespaceMap) {
+    serializer.AddVisitable("namespaceMap", *NamespaceMap);
+  }
+  SerializeIfPresent(serializer, "dataLicense", DataLicense);
+}
+
+void cmSbomDocument::Serialize(cmSbomSerializer& serializer) const
+{
+  serializer.AddString(
+    "@context",
+    Context.value_or("https://spdx.org/rdf/3.0.1/spdx-context.jsonld"));
+  if (!Graph.empty()) {
+    serializer.AddVectorIfPresent("@graph", Graph);
+  }
+}
diff --git a/Source/cmSpdx.h b/Source/cmSpdx.h
new file mode 100644
index 0000000..9b37828
--- /dev/null
+++ b/Source/cmSpdx.h
@@ -0,0 +1,390 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+
+#include "cmSbomObject.h"
+
+class cmSbomSerializer;
+
+using Datetime = std::string;
+using MediaType = std::string;
+using SemVer = std::string;
+
+struct cmSpdxExternalIdentifier
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<std::string> ExternalIdentifierType;
+  cm::optional<std::string> Identifier;
+  cm::optional<std::string> Comment;
+  cm::optional<std::string> IdentifierLocation;
+  cm::optional<std::string> IssuingAuthority;
+
+  void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxExternalRef
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<std::string> ExternalRefType;
+  cm::optional<std::string> Locator;
+  cm::optional<std::string> ContentType;
+  cm::optional<std::string> Comment;
+
+  void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxCreationInfo
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<SemVer> SpecVersion;
+  cm::optional<std::string> Comment;
+  cm::optional<Datetime> Created;
+  std::vector<cmSbomObject> CreatedBy;
+  std::vector<cmSbomObject> CreatedUsing;
+
+  void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxIntegrityMethod
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<std::string> Comment;
+
+  enum HashAlgorithmId
+  {
+    ADLER32,
+    BLAKE2B256,
+    BLAKE2B384,
+    BLAKE2B512,
+    BLAKE3,
+    MD2,
+    MD4,
+    MD5,
+    MD6,
+    SHA1,
+    SHA224,
+    SHA256,
+    SHA384,
+    SHA512,
+    SHA3_256,
+    SHA3_384,
+    SHA3_512,
+  };
+
+  cmSpdxIntegrityMethod() = default;
+  cmSpdxIntegrityMethod(cmSpdxIntegrityMethod const&) = default;
+  cmSpdxIntegrityMethod(cmSpdxIntegrityMethod&&) = default;
+  cmSpdxIntegrityMethod& operator=(cmSpdxIntegrityMethod const&) = default;
+  cmSpdxIntegrityMethod& operator=(cmSpdxIntegrityMethod&&) = default;
+
+  virtual void Serialize(cmSbomSerializer&) const;
+  virtual ~cmSpdxIntegrityMethod() = default;
+};
+
+struct cmSpdxChecksum
+{
+  cm::optional<std::string> SpdxId;
+  cmSpdxIntegrityMethod::HashAlgorithmId Algorithm;
+  std::string ChecksumValue;
+
+  void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxElement
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<std::string> Name;
+  cm::optional<std::string> Summary;
+  cm::optional<std::string> Description;
+  cm::optional<std::string> Comment;
+  cm::optional<cmSbomObject> CreationInfo;
+  cm::optional<cmSbomObject> VerifiedUsing;
+  std::vector<cmSbomObject> ExternalRef;
+  std::vector<cmSbomObject> ExternalIdentifier;
+  cm::optional<cmSbomObject> Extension;
+
+  cmSpdxElement() = default;
+  cmSpdxElement(cmSpdxElement const&) = default;
+  cmSpdxElement(cmSpdxElement&&) = default;
+  cmSpdxElement& operator=(cmSpdxElement const&) = default;
+  cmSpdxElement& operator=(cmSpdxElement&&) = default;
+
+  virtual ~cmSpdxElement() = default;
+  virtual void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxTool final : cmSpdxElement
+{
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxAgent : cmSpdxElement
+{
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxOrganization final : cmSpdxAgent
+{
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxPerson final : cmSpdxAgent
+{
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxSoftwareAgent final : cmSpdxAgent
+{
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxPositiveIntegerRange
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<std::string> BeginIntegerRange;
+  cm::optional<std::string> EndIntegerRange;
+
+  void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxRelationship : cmSpdxElement
+{
+  enum RelationshipTypeId
+  {
+    DESCRIBES,
+    CONTAINS,
+    DEPENDS_ON,
+    OTHER
+  };
+
+  cm::optional<cmSbomObject> From;
+  std::vector<cmSbomObject> To;
+  cm::optional<RelationshipTypeId> RelationshipType;
+  cm::optional<Datetime> StartTime;
+  cm::optional<Datetime> EndTime;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxLifecycleScopedRelationship final : cmSpdxRelationship
+{
+  enum ScopeId
+  {
+    BUILD,
+    DESIGN,
+    RUNTIME,
+    TEST
+  };
+
+  cm::optional<ScopeId> Scope;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxArtifact : cmSpdxElement
+{
+  enum SupportTypeId
+  {
+    COMMUNITY,
+    COMMERCIAL,
+    NONE
+  };
+
+  std::vector<cmSbomObject> OriginatedBy;
+  cm::optional<cmSbomObject> SuppliedBy;
+  cm::optional<Datetime> BuiltTime;
+  cm::optional<Datetime> ReleaseTime;
+  cm::optional<Datetime> ValidUntilTime;
+  cm::optional<std::string> StandardName;
+  cm::optional<SupportTypeId> Support;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxIndividualElement final : cmSpdxElement
+{
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxAnnotation final : cmSpdxElement
+{
+  enum AnnotationTypeId
+  {
+    REVIEW,
+    OTHER
+  };
+
+  cm::optional<AnnotationTypeId> AnnotationType;
+  cm::optional<MediaType> ContentType;
+  cm::optional<std::string> Statement;
+  cm::optional<cmSbomObject> Element;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxExternalMap
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<std::string> ExternalSpdxId;
+  cm::optional<cmSbomObject> VerifiedUsing;
+  cm::optional<std::string> LocationHistory;
+  cm::optional<cmSbomObject> DefiningArtifact;
+
+  void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxNamespaceMap
+{
+  cm::optional<std::string> SpdxId;
+  cm::optional<std::string> Prefix;
+  cm::optional<std::string> Namespace;
+
+  void Serialize(cmSbomSerializer&) const;
+};
+
+struct cmSpdxElementCollection : cmSpdxElement
+{
+  std::vector<cmSbomObject> Elements;
+  std::vector<cmSbomObject> RootElements;
+  std::vector<std::string> ProfileConformance;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxPackageVerificationCode final : cmSpdxIntegrityMethod
+{
+  cm::optional<HashAlgorithmId> Algorithm;
+  cm::optional<std::string> HashValue;
+  cm::optional<std::string> PackageVerificationCodeExcludedFile;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxHash final : cmSpdxIntegrityMethod
+{
+  HashAlgorithmId HashAlgorithm;
+  std::string HashValue;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxBundle : cmSpdxElementCollection
+{
+  cm::optional<std::string> Context;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxBom : cmSpdxBundle
+{
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxSbom final : cmSpdxBom
+{
+  enum TypeId
+  {
+    ANALYZED,
+    BUILD,
+    DEPLOYED,
+    DESIGN,
+    RUNTIME,
+    SOURCE,
+    TEST
+  };
+
+  cm::optional<std::vector<TypeId>> Types;
+  cm::optional<TypeId> LifecycleScope;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxSoftwareArtifact : cmSpdxArtifact
+{
+  enum PurposeId
+  {
+    APPLICATION,
+    ARCHIVE,
+    CONTAINER,
+    DATA,
+    DEVICE,
+    FIRMWARE,
+    FILE,
+    INSTALL,
+    LIBRARY,
+    MODULE,
+    OPERATING_SYSTEM,
+    SOURCE
+  };
+
+  cm::optional<PurposeId> PrimaryPurpose;
+  cm::optional<std::vector<PurposeId>> AdditionalPurpose;
+  cm::optional<std::string> CopyrightText;
+  cm::optional<std::string> AttributionText;
+  cm::optional<cmSbomObject> ContentIdentifier;
+  cm::optional<std::string> ArtifactSize;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxPackage final : cmSpdxSoftwareArtifact
+{
+  cm::optional<std::string> DownloadLocation;
+  cm::optional<std::string> Homepage;
+  cm::optional<std::string> PackageVersion;
+  cm::optional<std::string> PackageUrl;
+  cm::optional<std::string> SourceInfo;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxDocument final : cmSpdxElementCollection
+{
+  cm::optional<cmSbomObject> ExternalMap;
+  cm::optional<cmSbomObject> NamespaceMap;
+  std::string DataLicense;
+
+  void Serialize(cmSbomSerializer& serializer) const override;
+};
+
+struct cmSpdxContentIdentifier final : cmSpdxIntegrityMethod
+{
+  cm::optional<std::string> ContentIdentifierType;
+  cm::optional<std::string> ContentValue;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxFile final : cmSpdxArtifact
+{
+  enum FileKindId
+  {
+    DIRECTORY,
+    FILE
+  };
+  cm::optional<MediaType> ContentType;
+  cm::optional<FileKindId> FileType;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSpdxSnippet final : cmSpdxSoftwareArtifact
+{
+  cm::optional<std::string> ByteRange;
+  cm::optional<std::string> LineRange;
+  cm::optional<cmSbomObject> SnippetFromFile;
+
+  void Serialize(cmSbomSerializer&) const override;
+};
+
+struct cmSbomDocument
+{
+  cm::optional<std::string> Context;
+  std::vector<cmSbomObject> Graph;
+
+  void Serialize(cmSbomSerializer&) const;
+};
diff --git a/Source/cmSpdxSerializer.cxx b/Source/cmSpdxSerializer.cxx
new file mode 100644
index 0000000..47c22fd
--- /dev/null
+++ b/Source/cmSpdxSerializer.cxx
@@ -0,0 +1,119 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#include "cmSpdxSerializer.h"
+
+#include <utility>
+
+#include <cm3p/json/writer.h>
+
+#include "cmSbomObject.h"
+
+cmSpdxSerializer::cmSpdxSerializer()
+{
+  Json::StreamWriterBuilder builder = Json::StreamWriterBuilder();
+  builder["indentation"] = "  ";
+  Writer.reset(builder.newStreamWriter());
+}
+
+void cmSpdxSerializer::BeginObject()
+{
+  CurrentValue = Json::objectValue;
+}
+
+void cmSpdxSerializer::BeginArray()
+{
+  CurrentValue = Json::arrayValue;
+}
+
+void cmSpdxSerializer::EndObject()
+{
+}
+
+void cmSpdxSerializer::EndArray()
+{
+}
+
+void cmSpdxSerializer::AddReference(std::string const& id)
+{
+  CurrentValue = id;
+}
+
+void cmSpdxSerializer::AddString(std::string const& key,
+                                 std::string const& value)
+{
+  if (!value.empty()) {
+    CurrentValue[key] = value;
+  }
+}
+
+void cmSpdxSerializer::AddVisitable(std::string const& key,
+                                    cmSbomObject const& visitable)
+{
+  if (visitable.IsNull()) {
+    return;
+  }
+
+  Json::Value parentValue = std::move(CurrentValue);
+  visitable.Serialize(*this);
+  Json::Value childValue = std::move(CurrentValue);
+
+  CurrentValue = std::move(parentValue);
+  CurrentValue[key] = std::move(childValue);
+}
+
+void cmSpdxSerializer::AddVectorIfPresent(std::string const& key,
+                                          std::vector<cmSbomObject> const& vec)
+{
+  if (vec.empty()) {
+    return;
+  }
+  Json::Value parentValue = std::move(CurrentValue);
+  Json::Value childValue(Json::arrayValue);
+
+  for (auto const& item : vec) {
+    if (item.IsNull()) {
+      continue;
+    }
+
+    item.Serialize(*this);
+
+    if (!CurrentValue.isNull()) {
+      childValue.append(std::move(CurrentValue));
+    }
+  }
+
+  CurrentValue = std::move(parentValue);
+  CurrentValue[key] = std::move(childValue);
+}
+
+void cmSpdxSerializer::AddVectorIfPresent(std::string const& key,
+                                          std::vector<std::string> const& vec)
+{
+  if (vec.empty()) {
+    return;
+  }
+  Json::Value parentValue = std::move(CurrentValue);
+  Json::Value childValue(Json::arrayValue);
+
+  for (auto const& item : vec) {
+    if (item.empty()) {
+      continue;
+    }
+    childValue.append(item);
+  }
+
+  CurrentValue = std::move(parentValue);
+  CurrentValue[key] = std::move(childValue);
+}
+
+bool cmSpdxSerializer::WriteSbom(std::ostream& os,
+                                 cmSbomObject const& document)
+{
+  if (document.IsNull()) {
+    return false;
+  }
+
+  document.Serialize(*this);
+  Writer->write(CurrentValue, &os);
+  return os.good();
+}
diff --git a/Source/cmSpdxSerializer.h b/Source/cmSpdxSerializer.h
new file mode 100644
index 0000000..50ece9a
--- /dev/null
+++ b/Source/cmSpdxSerializer.h
@@ -0,0 +1,52 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <cm3p/json/value.h>
+#include <cm3p/json/writer.h>
+
+#include "cmSbomSerializer.h"
+
+class cmSbomObject;
+
+struct cmSpdxSerializer final : cmSbomSerializer
+{
+  cmSpdxSerializer();
+
+  ~cmSpdxSerializer() override = default;
+
+  void BeginObject() override;
+
+  void BeginArray() override;
+
+  void EndObject() override;
+
+  void EndArray() override;
+
+  void AddReference(std::string const& id) override;
+
+  void AddString(std::string const& key, std::string const& value) override;
+
+  void AddVisitable(std::string const& key,
+                    cmSbomObject const& visitable) override;
+
+  void AddVectorIfPresent(std::string const& key,
+                          std::vector<cmSbomObject> const& vec) override;
+
+  void AddVectorIfPresent(std::string const& key,
+                          std::vector<std::string> const& vec) override;
+
+  bool WriteSbom(std::ostream& os, cmSbomObject const& document) override;
+
+  Json::Value GetJson() { return CurrentValue; }
+
+private:
+  Json::Value CurrentValue;
+  std::string CurrentKey;
+  std::unique_ptr<Json::StreamWriter> Writer;
+};
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 85aba24..c362689 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -2034,7 +2034,7 @@
   if (in == top) {
     out = ".";
   } else if (cmSystemTools::IsSubDirectory(in, top)) {
-    out = in.substr(top.size() + 1);
+    out = in.substr(top.size() + (top.back() == '/' ? 0 : 1));
   } else {
     out = in;
   }
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index c15e288..b3a1f79 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -4509,10 +4509,14 @@
 
 std::string cmake::GetCMakeListFile(std::string const& dir) const
 {
-  std::string listFile = cmStrCat(dir, '/', this->CMakeListName);
-  if (this->CMakeListName.empty() ||
-      !cmSystemTools::FileExists(listFile, true)) {
-    return cmStrCat(dir, "/CMakeLists.txt");
+  cm::string_view const slash =
+    dir.empty() || dir.back() != '/' ? "/"_s : ""_s;
+  std::string listFile;
+  if (!this->CMakeListName.empty()) {
+    listFile = cmStrCat(dir, slash, this->CMakeListName);
+  }
+  if (listFile.empty() || !cmSystemTools::FileExists(listFile, true)) {
+    listFile = cmStrCat(dir, slash, "CMakeLists.txt");
   }
   return listFile;
 }
diff --git a/Source/kwsys/SystemInformation.cxx b/Source/kwsys/SystemInformation.cxx
index a11487d..5f67f16 100644
--- a/Source/kwsys/SystemInformation.cxx
+++ b/Source/kwsys/SystemInformation.cxx
@@ -4131,6 +4131,10 @@
   this->TotalPhysicalMemory = tp >> 10 >> 10;
   this->AvailableVirtualMemory = av >> 10 >> 10;
   this->AvailablePhysicalMemory = ap >> 10 >> 10;
+
+  // The virtual WinAPI memory contains both physical memory and page file.
+  this->TotalVirtualMemory -= this->TotalPhysicalMemory;
+  this->AvailableVirtualMemory -= this->AvailablePhysicalMemory;
   return true;
 #else
   return false;
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index a50936e..dee4731 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -25,7 +25,7 @@
   testRange.cxx
   testOptional.cxx
   testPathResolver.cxx
-  testSPDXSerializer.cxx
+  testSpdxSerializer.cxx
   testStdIo.cxx
   testString.cxx
   testStringAlgorithms.cxx
diff --git a/Tests/CMakeLib/testSPDXSerializer.cxx b/Tests/CMakeLib/testSPDXSerializer.cxx
deleted file mode 100644
index b8ab923..0000000
--- a/Tests/CMakeLib/testSPDXSerializer.cxx
+++ /dev/null
@@ -1,649 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file LICENSE.rst or https://cmake.org/licensing for details.  */
-#include <iostream>
-#include <string>
-#include <vector>
-
-#include <cm/optional>
-#include <cm/type_traits>
-
-#include <cm3p/json/reader.h>
-#include <cm3p/json/value.h>
-
-#include "cmSPDXSerializer.h"
-
-namespace {
-
-std::string const nonOptional(R"================({
-  "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
-  "@graph": [
-    {
-      "@id": "_:contentIdentifier_0",
-      "contentIdentifierType": "INVALID_CONTENT_IDENTIFIER_TYPE_ID",
-      "contentIdentifierValue": "",
-      "type": "ContentIdentifier"
-    },
-    {
-      "@id": "_:creationInfo_0",
-      "created": "",
-      "createdBy": [],
-      "type": "CreationInfo"
-    },
-    {
-      "@id": "_:creationInfo_1",
-      "created": "",
-      "createdBy": [],
-      "type": "CreationInfo"
-    },
-    {
-      "@id": "_:dictionaryEntry_0",
-      "key": "",
-      "type": "DictionaryEntry"
-    },
-    {
-      "@id": "_:externalIdentifier_0",
-      "externalIdentifierType": "INVALID_EXTERNAL_IDENTIFIER_TYPE_ID",
-      "identifier": "",
-      "type": "ExternalIdentifier"
-    },
-    {
-      "@id": "_:externalMap_0",
-      "externalSpdxId": "",
-      "type": "ExternalMap"
-    },
-    {
-      "@id": "_:externalRef_0",
-      "type": "ExternalRef"
-    },
-    {
-      "@id": "_:hash_0",
-      "algorithm": "INVALID_HASH_TYPE_ID",
-      "hashValue": "",
-      "type": "Hash"
-    },
-    {
-      "@id": "_:namespaceMap_0",
-      "namespace": "",
-      "prefix": "",
-      "type": "NamespaceMap"
-    },
-    {
-      "@id": "_:packageVerificationCode_0",
-      "algorithm": "INVALID_HASH_TYPE_ID",
-      "hashValue": "",
-      "type": "PackageVerificationCode"
-    },
-    {
-      "@id": "_:positiveIntegerRange_0",
-      "beginIntegerRange": 0,
-      "endIntegerRange": 0,
-      "type": "PositiveIntegerRange"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd0",
-      "type": "Agent"
-    },
-    {
-      "annotationType": "INVALID_ANNOTATION_TYPE_ID",
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd1",
-      "subject": "",
-      "type": "Annotation"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd10",
-      "type": "SpdxDocument"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd11",
-      "type": "Tool"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd12",
-      "type": "File"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd13",
-      "type": "Package"
-    },
-    {
-      "context": "",
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd14",
-      "type": "Sbom"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "snippetFromFile": "",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd15",
-      "type": "Snippet"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "licenseExpression": "",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd16",
-      "type": "LicenseExpression"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "licenseText": "",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd17",
-      "type": "SimpleLicensingText"
-    },
-    {
-      "context": "",
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd2",
-      "type": "Bom"
-    },
-    {
-      "context": "",
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd3",
-      "type": "Bundle"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd4",
-      "type": "IndividualElement"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "from": "",
-      "relationshipType": "INVALID_RELATIONSHIP_TYPE_ID",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd5",
-      "to": [],
-      "type": "LifecycleScopedRelationship"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd6",
-      "type": "Organization"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd7",
-      "type": "Person"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "from": "",
-      "relationshipType": "INVALID_RELATIONSHIP_TYPE_ID",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd8",
-      "to": [],
-      "type": "Relationship"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd9",
-      "type": "SoftwareAgent"
-    }
-  ]
-})================");
-
-std::string const Optional(R"================({
-  "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
-  "@graph": [
-    {
-      "@id": "_:contentIdentifier_0",
-      "contentIdentifierType": "gitoid",
-      "contentIdentifierValue": "ContentIdentifierValue",
-      "type": "ContentIdentifier"
-    },
-    {
-      "@id": "_:creationInfo_0",
-      "created": "",
-      "createdBy": [],
-      "type": "CreationInfo"
-    },
-    {
-      "@id": "_:creationInfo_1",
-      "Comment": "Comment",
-      "created": "Created",
-      "createdBy": [
-        "testRef"
-      ],
-      "createdUsing": [
-        "testRef"
-      ],
-      "type": "CreationInfo"
-    },
-    {
-      "@id": "_:dictionaryEntry_0",
-      "key": "Key",
-      "type": "DictionaryEntry",
-      "value": "Value"
-    },
-    {
-      "@id": "_:externalIdentifier_0",
-      "comment": "Comment",
-      "externalIdentifierType": "other",
-      "identifier": "Identifier",
-      "identifierLocator": [
-        "IdentifierLocator"
-      ],
-      "issuingAuthority": "IssuingAuthority",
-      "type": "ExternalIdentifier"
-    },
-    {
-      "@id": "_:externalMap_0",
-      "definingArtifact": "testRef",
-      "externalSpdxId": "ExternalSpdxId",
-      "integrityMethod": [
-        "testRef"
-      ],
-      "locationHint": "LocationHint",
-      "type": "ExternalMap"
-    },
-    {
-      "@id": "_:externalRef_0",
-      "comment": "Comment",
-      "contentType": "ContentType",
-      "externalRefType": "other",
-      "locator": [
-        "Locator"
-      ],
-      "type": "ExternalRef"
-    },
-    {
-      "@id": "_:hash_0",
-      "algorithm": "other",
-      "hashValue": "HashValue",
-      "type": "Hash"
-    },
-    {
-      "@id": "_:namespaceMap_0",
-      "namespace": "Namespace",
-      "prefix": "Namespace",
-      "type": "NamespaceMap"
-    },
-    {
-      "@id": "_:packageVerificationCode_0",
-      "algorithm": "other",
-      "hashValue": "HashValue",
-      "type": "PackageVerificationCode"
-    },
-    {
-      "@id": "_:positiveIntegerRange_0",
-      "beginIntegerRange": 1,
-      "endIntegerRange": 2,
-      "type": "PositiveIntegerRange"
-    },
-    {
-      "comment": "Comment",
-      "creationInfo": "_:creationInfo_0",
-      "description": "Description",
-      "extension": [
-        "testRef"
-      ],
-      "externalIdentifier": [
-        "testRef"
-      ],
-      "externalRef": [
-        "testRef"
-      ],
-      "name": "Name",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd0",
-      "summary": "Summary",
-      "type": "Agent",
-      "verifiedUsing": [
-        "testRef"
-      ]
-    },
-    {
-      "annotationType": "other",
-      "contentType": "ContentType",
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd1",
-      "statement": "Statement",
-      "subject": "testRef",
-      "type": "Annotation"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "dataLicense": "testRef",
-      "externalMap": "testRef",
-      "namespaceMap": "testRef",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd10",
-      "type": "SpdxDocument"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd11",
-      "type": "Tool"
-    },
-    {
-      "additionalPurpose": [
-        "other"
-      ],
-      "attributionText": "AttributionText",
-      "contentIdentifier": "testRef",
-      "contentType": "ContentType",
-      "copyrightText": "CopyrightText",
-      "creationInfo": "_:creationInfo_0",
-      "fileKind": "file",
-      "primaryPurpose": "file",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd12",
-      "type": "File"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "downloadLocation": "DownloadLocation",
-      "homePage": "HomePage",
-      "packageUrl": "PackageUrl",
-      "packageVersion": "PackageVersion",
-      "sourceInfo": "SourceInfo",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd13",
-      "type": "Package"
-    },
-    {
-      "context": "",
-      "creationInfo": "_:creationInfo_0",
-      "sbomType": [
-        "build"
-      ],
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd14",
-      "type": "Sbom"
-    },
-    {
-      "byteRange": "testRef",
-      "creationInfo": "_:creationInfo_0",
-      "lineRange": "testRef",
-      "snippetFromFile": "testRef",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd15",
-      "type": "Snippet"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "customIdToUri": [
-        "testRef"
-      ],
-      "licenseExpression": "LicenseExpression",
-      "licenseListVersion": "LicenseListVersion",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd16",
-      "type": "LicenseExpression"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "licenseText": "LicenseText",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd17",
-      "type": "SimpleLicensingText"
-    },
-    {
-      "context": "Context",
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd2",
-      "type": "Bom"
-    },
-    {
-      "context": "",
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd3",
-      "type": "Bundle"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd4",
-      "type": "IndividualElement"
-    },
-    {
-      "completeness": "noAssertion",
-      "creationInfo": "_:creationInfo_0",
-      "endTime": "EndTime",
-      "from": "testRef",
-      "relationshipType": "other",
-      "scope": "other",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd5",
-      "startTime": "StartTime",
-      "to": [
-        "testRef"
-      ],
-      "type": "LifecycleScopedRelationship"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd6",
-      "type": "Organization"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd7",
-      "type": "Person"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "from": "",
-      "relationshipType": "INVALID_RELATIONSHIP_TYPE_ID",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd8",
-      "to": [],
-      "type": "Relationship"
-    },
-    {
-      "creationInfo": "_:creationInfo_0",
-      "spdxId": "https://cmake.org/testSPDXSerialization-gnrtd9",
-      "type": "SoftwareAgent"
-    }
-  ]
-})================");
-}
-
-int testNonOptional()
-{
-  cmSPDXSimpleGraph graph("https://cmake.org/testSPDXSerialization-gnrtd");
-
-  // Core
-  graph.insert<cmSPDXAgent>();
-  graph.insert<cmSPDXAnnotation>();
-  graph.insert<cmSPDXBom>();
-  graph.insert<cmSPDXBundle>();
-  graph.insert<cmSPDXCreationInfo>();
-  graph.insert<cmSPDXDictionaryEntry>();
-  graph.insert<cmSPDXExternalIdentifier>();
-  graph.insert<cmSPDXExternalMap>();
-  graph.insert<cmSPDXExternalRef>();
-  graph.insert<cmSPDXHash>();
-  graph.insert<cmSPDXIndividualElement>();
-  graph.insert<cmSPDXLifecycleScopedRelationship>();
-  graph.insert<cmSPDXNamespaceMap>();
-  graph.insert<cmSPDXOrganization>();
-  graph.insert<cmSPDXPackageVerificationCode>();
-  graph.insert<cmSPDXPerson>();
-  graph.insert<cmSPDXPositiveIntegerRange>();
-  graph.insert<cmSPDXRelationship>();
-  graph.insert<cmSPDXSoftwareAgent>();
-  graph.insert<cmSPDXSpdxDocument>();
-  graph.insert<cmSPDXTool>();
-
-  // Software
-  graph.insert<cmSPDXContentIdentifier>();
-  graph.insert<cmSPDXFile>();
-  graph.insert<cmSPDXPackage>();
-  graph.insert<cmSPDXSbom>();
-  graph.insert<cmSPDXSnippet>();
-
-  // SimpleLicensing
-  graph.insert<cmSPDXLicenseExpression>();
-  graph.insert<cmSPDXSimpleLicensingText>();
-
-  Json::Value root;
-  Json::Reader().parse(nonOptional.c_str(), root);
-
-  std::cout << "NonOptional SPDX:";
-  std::cout << "\nConstructed Graph: " << graph.toJsonLD().toStyledString();
-  std::cout << "\nComparison Graph:" << root.toStyledString() << "\n";
-
-  // Convert to string to disregard differences in number signedness
-  return root.toStyledString() == graph.toJsonLD().toStyledString();
-};
-
-int testOptional()
-{
-  cmSPDXSimpleGraph graph("https://cmake.org/testSPDXSerialization-gnrtd");
-
-  cmSPDXIdentifierReference ident("testRef");
-
-  // Core
-  auto& agent = graph.insert<cmSPDXAgent>();
-  agent.Comment = "Comment";
-  agent.Description = "Description";
-  agent.Extension.emplace().push_back(ident);
-  agent.ExternalIdentifier.emplace().push_back(ident);
-  agent.ExternalRef.emplace().push_back(ident);
-  agent.Name = "Name";
-  agent.Summary = "Summary";
-  agent.VerifiedUsing.emplace().push_back(ident);
-
-  auto& annotation = graph.insert<cmSPDXAnnotation>();
-  annotation.AnnotationType = cmSPDXAnnotationType::OTHER;
-  annotation.ContentType = "ContentType";
-  annotation.Statement = "Statement";
-  annotation.Subject = ident;
-
-  auto& bom = graph.insert<cmSPDXBom>();
-  bom.Context = "Context";
-
-  graph.insert<cmSPDXBundle>();
-
-  auto& creationInfo = graph.insert<cmSPDXCreationInfo>();
-  creationInfo.Comment = "Comment";
-  creationInfo.Created = "Created";
-  creationInfo.CreatedBy.push_back(ident);
-  creationInfo.CreatedUsing.emplace().push_back(ident);
-
-  auto& dictionaryEntry = graph.insert<cmSPDXDictionaryEntry>();
-  dictionaryEntry.Key = "Key";
-  dictionaryEntry.Value = "Value";
-
-  auto& externalIdentifier = graph.insert<cmSPDXExternalIdentifier>();
-  externalIdentifier.Comment = "Comment";
-  externalIdentifier.ExternalIdentifierType =
-    cmSPDXExternalIdentifierType::OTHER;
-  externalIdentifier.Identifier = "Identifier";
-  externalIdentifier.IdentifierLocator.emplace().push_back(
-    "IdentifierLocator");
-  externalIdentifier.IssuingAuthority = "IssuingAuthority";
-
-  auto& externalMap = graph.insert<cmSPDXExternalMap>();
-  externalMap.DefiningArtifact = ident;
-  externalMap.ExternalSpdxId = "ExternalSpdxId";
-  externalMap.LocationHint = "LocationHint";
-  externalMap.IntegrityMethod.emplace().push_back(ident);
-
-  auto& externalRef = graph.insert<cmSPDXExternalRef>();
-  externalRef.Comment = "Comment";
-  externalRef.ContentType = "ContentType";
-  externalRef.ExternalRefType = cmSPDXExternalRefType::OTHER;
-  externalRef.Locator.emplace().push_back("Locator");
-
-  auto& hash = graph.insert<cmSPDXHash>();
-  hash.Algorithm = cmSPDXHashAlgorithm::OTHER;
-  hash.HashValue = "HashValue";
-
-  graph.insert<cmSPDXIndividualElement>();
-
-  auto& lifecycleScopedRelationship =
-    graph.insert<cmSPDXLifecycleScopedRelationship>();
-  lifecycleScopedRelationship.Completeness =
-    cmSPDXRelationshipCompletenessType::NO_ASSERTION;
-  lifecycleScopedRelationship.EndTime = "EndTime";
-  lifecycleScopedRelationship.From = ident;
-  lifecycleScopedRelationship.RelationshipType = cmSPDXRelationshipType::OTHER;
-  lifecycleScopedRelationship.StartTime = "StartTime";
-  lifecycleScopedRelationship.To.push_back(ident);
-  lifecycleScopedRelationship.Scope = cmSPDXLifecycleScopeType::OTHER;
-
-  auto& namespaceMap = graph.insert<cmSPDXNamespaceMap>();
-  namespaceMap.Namespace = "Namespace";
-  namespaceMap.Prefix = "Prefix";
-
-  graph.insert<cmSPDXOrganization>();
-
-  auto& packageVerificationCode =
-    graph.insert<cmSPDXPackageVerificationCode>();
-  packageVerificationCode.Algorithm = cmSPDXHashAlgorithm::OTHER;
-  packageVerificationCode.HashValue = "HashValue";
-  packageVerificationCode.PackageVerificationCodeExcludedFile.emplace()
-    .push_back("PacakgeVerificationCodeExcludeFile");
-
-  graph.insert<cmSPDXPerson>();
-
-  auto& positiveIntegerRange = graph.insert<cmSPDXPositiveIntegerRange>();
-  positiveIntegerRange.BeginIntegerRange = 1;
-  positiveIntegerRange.EndIntegerRange = 2;
-
-  graph.insert<cmSPDXRelationship>();
-
-  graph.insert<cmSPDXSoftwareAgent>();
-
-  auto& spdxDocument = graph.insert<cmSPDXSpdxDocument>();
-  spdxDocument.DataLicense = ident;
-  spdxDocument.ExternalMap = ident;
-  spdxDocument.NamespaceMap = ident;
-
-  graph.insert<cmSPDXTool>();
-
-  // Software
-  auto& contentIdentifier = graph.insert<cmSPDXContentIdentifier>();
-  contentIdentifier.ContentIdentifierType =
-    cmSPDXContentIdentifierType::GITOID;
-  contentIdentifier.ContentIdentifierValue = "ContentIdentifierValue";
-
-  auto& file = graph.insert<cmSPDXFile>();
-  file.AdditionalPurpose.emplace().push_back(cmSPDXSoftwarePurpose::OTHER);
-  file.AttributionText = "AttributionText";
-  file.ContentIdentifier = ident;
-  file.CopyrightText = "CopyrightText";
-  file.PrimaryPurpose = cmSPDXSoftwarePurpose::FILE;
-  file.ContentType = "ContentType";
-  file.FileKind = cmSPDXFileKindType::FILE;
-
-  auto& package = graph.insert<cmSPDXPackage>();
-  package.DownloadLocation = "DownloadLocation";
-  package.HomePage = "HomePage";
-  package.PackageUrl = "PackageUrl";
-  package.PackageVersion = "PackageVersion";
-  package.SourceInfo = "SourceInfo";
-
-  auto& sbom = graph.insert<cmSPDXSbom>();
-  sbom.SbomType.emplace().push_back(cmSPDXSbomType::BUILD);
-
-  auto& snippet = graph.insert<cmSPDXSnippet>();
-  snippet.ByteRange = ident;
-  snippet.LineRange = ident;
-  snippet.SnippetFromFile = ident;
-
-  // SimpleLicensing
-  auto& licenseExpression = graph.insert<cmSPDXLicenseExpression>();
-  licenseExpression.CustomIdToUri.emplace().push_back(ident);
-  licenseExpression.LicenseExpression = "LicenseExpression";
-  licenseExpression.LicenseListVersion = "LicenseListVersion";
-
-  auto& simpleLicensingText = graph.insert<cmSPDXSimpleLicensingText>();
-  simpleLicensingText.LicenseText = "LicenseText";
-
-  Json::Value root;
-  Json::Reader().parse(Optional.c_str(), root);
-
-  std::cout << "Optional SPDX:";
-  std::cout << "\nConstructed Graph: " << graph.toJsonLD().toStyledString();
-  std::cout << "\nComparison Graph:" << root.toStyledString() << "\n";
-
-  // Convert to string to disregard differences in number signedness
-  return root.toStyledString() == graph.toJsonLD().toStyledString();
-};
-
-int testSPDXSerializer(int /* argc */, char* /* argv */[])
-{
-  if (!testNonOptional())
-    return -1;
-
-  if (!testOptional())
-    return -1;
-
-  return 0;
-}
diff --git a/Tests/CMakeLib/testSpdxSerializer.cxx b/Tests/CMakeLib/testSpdxSerializer.cxx
new file mode 100644
index 0000000..ab394cb
--- /dev/null
+++ b/Tests/CMakeLib/testSpdxSerializer.cxx
@@ -0,0 +1,84 @@
+
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm/optional>
+
+#include <cm3p/json/value.h>
+
+#include "cmSbomObject.h"
+#include "cmSpdx.h"
+#include "cmSpdxSerializer.h"
+
+#include "testCommon.h"
+
+namespace {
+
+bool testSerializeSpdxJson()
+{
+
+  auto constexpr contextUrl = "https://spdx.org/rdf/3.0.1/spdx-context.jsonld";
+
+  cmSbomDocument doc;
+  doc.Context = contextUrl;
+
+  cmSpdxDocument spdxValue;
+  spdxValue.SpdxId = "_:SPDXRef-Document";
+  spdxValue.DataLicense = "CC0-1.0";
+  auto spdx = insert_back(doc.Graph, std::move(spdxValue));
+
+  {
+    cmSpdxCreationInfo ci;
+    ci.SpdxId = "_:SPDXRef-CreationInfo";
+    ci.SpecVersion = "3.0.1";
+    spdx->CreationInfo = std::move(ci);
+  }
+
+  {
+    cmSpdxPackage pkg;
+    pkg.Name = "sample-package";
+    pkg.SpdxId = "_:SPDXRef-Package";
+
+    spdx->RootElements.emplace_back(std::move(pkg));
+  }
+
+  {
+    cmSpdxSerializer serializer;
+    doc.Serialize(serializer);
+    auto value = serializer.GetJson();
+
+    ASSERT_TRUE(value.isObject());
+
+    Json::Value const& context = value["@context"];
+    ASSERT_EQUAL(context.asString(), contextUrl);
+
+    Json::Value const& graph = value["@graph"];
+    ASSERT_TRUE(graph.isArray());
+
+    Json::Value const& docValue = graph[0];
+    ASSERT_TRUE(docValue.isObject());
+    ASSERT_EQUAL(docValue["type"].asString(), "SpdxDocument");
+    ASSERT_EQUAL(docValue["@id"].asString(), "_:SPDXRef-Document");
+
+    auto const& creationInfo = docValue["creationInfo"];
+    ASSERT_EQUAL(creationInfo["@id"].asString(), "_:SPDXRef-CreationInfo");
+    ASSERT_EQUAL(creationInfo["type"].asString(), "CreationInfo");
+
+    auto const& package = docValue["rootElement"][0];
+    ASSERT_EQUAL(package["type"].asString(), "software_Package");
+    ASSERT_EQUAL(package["@id"].asString(), "_:SPDXRef-Package");
+  }
+
+  return true;
+}
+
+} // namespace
+
+int testSpdxSerializer(int /*unused*/, char* /*unused*/[])
+{
+  return runTests({ testSerializeSpdxJson });
+}
diff --git a/Tests/CMakeLib/testSystemTools.cxx b/Tests/CMakeLib/testSystemTools.cxx
index 3581474..d34f710 100644
--- a/Tests/CMakeLib/testSystemTools.cxx
+++ b/Tests/CMakeLib/testSystemTools.cxx
@@ -96,6 +96,50 @@
   return true;
 }
 
+static bool testRelativeIfUnder()
+{
+  std::cout << "testRelativeIfUnder()\n";
+
+  std::string result = cmSystemTools::RelativeIfUnder("/a/b", "/a/b");
+  if (result != ".") {
+    std::cout << "cmSystemTools::RelativeIfUnder failed on /a/b, /a/b: "
+              << result << "\n";
+    return false;
+  }
+  result = cmSystemTools::RelativeIfUnder("/a/b", "/a/b/c/d");
+  if (result != "c/d") {
+    std::cout << "cmSystemTools::RelativeIfUnder failed on /a/b, /a/b/c/d: "
+              << result << "\n";
+    return false;
+  }
+  result = cmSystemTools::RelativeIfUnder("/d/f/", "/a/b/c/d");
+  if (result != "/a/b/c/d") {
+    std::cout << "cmSystemTools::RelativeIfUnder failed on /d/f/, /a/b/c/d: "
+              << result << "\n";
+    return false;
+  }
+  result = cmSystemTools::RelativeIfUnder("/", "/a/b");
+  if (result != "a/b") {
+    std::cout << "cmSystemTools::RelativeIfUnder failed on /, /a/b: " << result
+              << "\n";
+    return false;
+  }
+  result = cmSystemTools::RelativeIfUnder("I:/", "I:/CMakeLists.txt");
+  if (result != "CMakeLists.txt") {
+    std::cout
+      << "cmSystemTools::RelativeIfUnder failed on I:/, I:/CMakeLists.txt: "
+      << result << "\n";
+    return false;
+  }
+  result = cmSystemTools::RelativeIfUnder("", "/a/b");
+  if (result != "/a/b") {
+    std::cout << "cmSystemTools::RelativeIfUnder failed on \"\", /a/b: "
+              << result << "\n";
+    return false;
+  }
+  return true;
+}
+
 static bool testMakeTempDirectory()
 {
   std::cout << "testMakeTempDirectory()\n";
@@ -124,6 +168,7 @@
     testUpperCase,
     testVersionCompare,
     testStrVersCmp,
+    testRelativeIfUnder,
     testMakeTempDirectory,
   });
 }
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 2ead810..22b99ba 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -3283,6 +3283,11 @@
     set(JavaExportImport_BUILD_OPTIONS -DCMake_TEST_NESTED_MAKE_PROGRAM:FILEPATH=${CMake_TEST_EXPLICIT_MAKE_PROGRAM})
     ADD_TEST_MACRO(JavaExportImport JavaExportImport)
 
+    if("${Java_VERSION}" VERSION_GREATER_EQUAL 9)
+      set(JavaModExportImport_BUILD_OPTIONS -DCMake_TEST_NESTED_MAKE_PROGRAM:FILEPATH=${CMake_TEST_EXPLICIT_MAKE_PROGRAM})
+      ADD_TEST_MACRO(JavaModExportImport JavaModExportImport)
+    endif()
+
     get_filename_component(JAVACPATH ${Java_JAVAC_EXECUTABLE} REALPATH)
     get_filename_component(JNIPATH ${JAVACPATH} PATH)
     find_file(JNI_H jni.h
diff --git a/Tests/JavaModExportImport/BuildExport/CMakeLists.txt b/Tests/JavaModExportImport/BuildExport/CMakeLists.txt
new file mode 100644
index 0000000..f818f44
--- /dev/null
+++ b/Tests/JavaModExportImport/BuildExport/CMakeLists.txt
@@ -0,0 +1,13 @@
+project(foo Java)
+
+cmake_minimum_required(VERSION 3.10)
+set(CMAKE_VERBOSE_MAKEFILE 1)
+
+find_package(Java COMPONENTS Development)
+include(UseJava)
+
+add_jar(${PROJECT_NAME} Foo.java module-info.java)
+export_jars(
+  TARGETS ${PROJECT_NAME}
+  NAMESPACE foo::
+  FILE JavaBuildExportTestConfig.cmake)
diff --git a/Tests/JavaModExportImport/BuildExport/Foo.java b/Tests/JavaModExportImport/BuildExport/Foo.java
new file mode 100644
index 0000000..c0e2a1a
--- /dev/null
+++ b/Tests/JavaModExportImport/BuildExport/Foo.java
@@ -0,0 +1,13 @@
+package org.foo;
+
+public class Foo
+{
+  public Foo()
+  {
+  }
+
+  public void printName()
+  {
+    System.out.println("Foo");
+  }
+}
diff --git a/Tests/JavaModExportImport/BuildExport/module-info.java b/Tests/JavaModExportImport/BuildExport/module-info.java
new file mode 100644
index 0000000..aeb0e17
--- /dev/null
+++ b/Tests/JavaModExportImport/BuildExport/module-info.java
@@ -0,0 +1,3 @@
+module mod_foo {
+    exports org.foo;
+}
diff --git a/Tests/JavaModExportImport/CMakeLists.txt b/Tests/JavaModExportImport/CMakeLists.txt
new file mode 100644
index 0000000..5326457
--- /dev/null
+++ b/Tests/JavaModExportImport/CMakeLists.txt
@@ -0,0 +1,105 @@
+cmake_minimum_required(VERSION 3.10)
+project(JavaModExportImport)
+if(NOT DEFINED CMake_TEST_NESTED_MAKE_PROGRAM AND NOT CMAKE_GENERATOR MATCHES "Visual Studio")
+  set(CMake_TEST_NESTED_MAKE_PROGRAM "${CMAKE_MAKE_PROGRAM}")
+endif()
+
+find_package(Java COMPONENTS Development)
+
+# Wipe out the install tree to make sure the exporter works.
+add_custom_command(
+  OUTPUT ${JavaModExportImport_BINARY_DIR}/CleanupProject
+  COMMAND ${CMAKE_COMMAND} -E rm -rf ${JavaModExportImport_BINARY_DIR}/Root
+  )
+add_custom_target(CleanupTarget ALL DEPENDS ${JavaModExportImport_BINARY_DIR}/CleanupProject)
+set_property(
+  SOURCE ${JavaModExportImport_BINARY_DIR}/CleanupProject
+  PROPERTY SYMBOLIC 1
+  )
+
+get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(_isMultiConfig)
+  set(NESTED_CONFIG_TYPE -C "${CMAKE_CFG_INTDIR}")
+else()
+  if(CMAKE_BUILD_TYPE)
+    set(NESTED_CONFIG_TYPE -C "${CMAKE_BUILD_TYPE}")
+  else()
+    set(NESTED_CONFIG_TYPE)
+  endif()
+endif()
+
+configure_file(${JavaModExportImport_SOURCE_DIR}/InitialCache.cmake.in
+               ${JavaModExportImport_BINARY_DIR}/InitialCache.cmake @ONLY)
+
+# Build the build exporter.
+add_custom_command(
+  OUTPUT ${JavaModExportImport_BINARY_DIR}/BuildExportProject
+  COMMAND ${CMAKE_CTEST_COMMAND} ${NESTED_CONFIG_TYPE}
+    --build-and-test
+    ${JavaModExportImport_SOURCE_DIR}/BuildExport
+    ${JavaModExportImport_BINARY_DIR}/BuildExport
+    --build-noclean
+    --build-project BuildExport
+    --build-generator ${CMAKE_GENERATOR}
+    --build-generator-platform "${CMAKE_GENERATOR_PLATFORM}"
+    --build-generator-toolset "${CMAKE_GENERATOR_TOOLSET}"
+    --build-options -C${JavaModExportImport_BINARY_DIR}/InitialCache.cmake
+  VERBATIM
+  )
+add_custom_target(BuildExportTarget ALL DEPENDS ${JavaModExportImport_BINARY_DIR}/BuildExportProject)
+add_dependencies(BuildExportTarget CleanupTarget)
+set_property(
+  SOURCE ${JavaModExportImport_BINARY_DIR}/BuildExportProject
+  PROPERTY SYMBOLIC 1
+  )
+
+# Build and install the install exporter.
+add_custom_command(
+  OUTPUT ${JavaModExportImport_BINARY_DIR}/InstallExportProject
+  COMMAND ${CMAKE_CTEST_COMMAND} ${NESTED_CONFIG_TYPE}
+    --build-and-test
+    ${JavaModExportImport_SOURCE_DIR}/InstallExport
+    ${JavaModExportImport_BINARY_DIR}/InstallExport
+    --build-noclean
+    --build-project InstallExport
+    --build-target install
+    --build-generator ${CMAKE_GENERATOR}
+    --build-generator-platform "${CMAKE_GENERATOR_PLATFORM}"
+    --build-generator-toolset "${CMAKE_GENERATOR_TOOLSET}"
+    --build-options -C${JavaModExportImport_BINARY_DIR}/InitialCache.cmake
+  VERBATIM
+  )
+add_custom_target(InstallExportTarget ALL DEPENDS ${JavaModExportImport_BINARY_DIR}/InstallExportProject)
+add_dependencies(InstallExportTarget CleanupTarget)
+set_property(
+  SOURCE ${JavaModExportImport_BINARY_DIR}/InstallExportProject
+  PROPERTY SYMBOLIC 1
+  )
+
+# Build and install the importer.
+add_custom_command(
+  OUTPUT ${JavaModExportImport_BINARY_DIR}/ImportProject
+  COMMAND ${CMAKE_CTEST_COMMAND} ${NESTED_CONFIG_TYPE}
+    --build-and-test
+    ${JavaModExportImport_SOURCE_DIR}/Import
+    ${JavaModExportImport_BINARY_DIR}/Import
+    --build-noclean
+    --build-project Import
+    --build-generator ${CMAKE_GENERATOR}
+    --build-generator-platform "${CMAKE_GENERATOR_PLATFORM}"
+    --build-generator-toolset "${CMAKE_GENERATOR_TOOLSET}"
+    --build-options
+      -C${JavaModExportImport_BINARY_DIR}/InitialCache.cmake
+      -DJavaBuildExportTest_DIR:PATH=${JavaModExportImport_BINARY_DIR}/BuildExport
+      -DJavaInstallExportTest_DIR:PATH=${JavaModExportImport_BINARY_DIR}/Root/share/cmake
+  VERBATIM
+  )
+add_custom_target(ImportTarget ALL DEPENDS ${JavaModExportImport_BINARY_DIR}/ImportProject)
+add_dependencies(ImportTarget BuildExportTarget InstallExportTarget)
+set_property(
+  SOURCE ${JavaModExportImport_BINARY_DIR}/ImportProject
+  PROPERTY SYMBOLIC 1
+  )
+
+add_executable(JavaModExportImport main.c)
+add_dependencies(JavaModExportImport ImportTarget)
diff --git a/Tests/JavaModExportImport/Import/CMakeLists.txt b/Tests/JavaModExportImport/Import/CMakeLists.txt
new file mode 100644
index 0000000..f8f773a
--- /dev/null
+++ b/Tests/JavaModExportImport/Import/CMakeLists.txt
@@ -0,0 +1,14 @@
+project(import Java)
+
+cmake_minimum_required(VERSION 3.10)
+set(CMAKE_VERBOSE_MAKEFILE 1)
+
+find_package(Java COMPONENTS Development)
+include(UseJava)
+
+find_package(JavaBuildExportTest REQUIRED)
+find_package(JavaInstallExportTest REQUIRED)
+
+add_jar(${PROJECT_NAME}
+  SOURCES Import.java module-info.java
+  INCLUDE_MODULES foo::foo bar::bar)
diff --git a/Tests/JavaModExportImport/Import/Import.java b/Tests/JavaModExportImport/Import/Import.java
new file mode 100644
index 0000000..46b1e9a
--- /dev/null
+++ b/Tests/JavaModExportImport/Import/Import.java
@@ -0,0 +1,13 @@
+import org.foo.Foo;
+import com.bar.Bar;
+
+class Import
+{
+  public static void main(String args[])
+  {
+    Foo foo = new Foo();
+    Bar bar = new Bar();
+    foo.printName();
+    bar.printName();
+  }
+}
diff --git a/Tests/JavaModExportImport/Import/module-info.java b/Tests/JavaModExportImport/Import/module-info.java
new file mode 100644
index 0000000..9d719cd
--- /dev/null
+++ b/Tests/JavaModExportImport/Import/module-info.java
@@ -0,0 +1,4 @@
+module client {
+    requires mod_foo;
+    requires mod_bar;
+}
diff --git a/Tests/JavaModExportImport/InitialCache.cmake.in b/Tests/JavaModExportImport/InitialCache.cmake.in
new file mode 100644
index 0000000..cc7871e
--- /dev/null
+++ b/Tests/JavaModExportImport/InitialCache.cmake.in
@@ -0,0 +1,5 @@
+set(CMAKE_MAKE_PROGRAM "@CMake_TEST_NESTED_MAKE_PROGRAM@" CACHE FILEPATH "Make Program")
+set(Java_JAVA_EXECUTABLE "@Java_JAVA_EXECUTABLE@" CACHE STRING "Java Interpreter")
+set(Java_JAVAC_EXECUTABLE "@Java_JAVAC_EXECUTABLE@" CACHE STRING "Java Compiler")
+set(Java_JAR_EXECUTABLE "@Java_JAR_EXECUTABLE@" CACHE STRING "Java Archive Tool")
+set(CMAKE_INSTALL_PREFIX "@JavaModExportImport_BINARY_DIR@/Root" CACHE STRING "Installation Prefix")
diff --git a/Tests/JavaModExportImport/InstallExport/Bar.java b/Tests/JavaModExportImport/InstallExport/Bar.java
new file mode 100644
index 0000000..dfa941c
--- /dev/null
+++ b/Tests/JavaModExportImport/InstallExport/Bar.java
@@ -0,0 +1,13 @@
+package com.bar;
+
+public class Bar
+{
+  public Bar()
+  {
+  }
+
+  public void printName()
+  {
+    System.out.println("Bar");
+  }
+}
diff --git a/Tests/JavaModExportImport/InstallExport/CMakeLists.txt b/Tests/JavaModExportImport/InstallExport/CMakeLists.txt
new file mode 100644
index 0000000..5997bdc
--- /dev/null
+++ b/Tests/JavaModExportImport/InstallExport/CMakeLists.txt
@@ -0,0 +1,15 @@
+project(bar Java)
+
+cmake_minimum_required(VERSION 3.10)
+set(CMAKE_VERBOSE_MAKEFILE 1)
+
+find_package(Java COMPONENTS Development)
+include(UseJava)
+
+add_jar(${PROJECT_NAME} Bar.java module-info.java)
+install_jar(${PROJECT_NAME} DESTINATION share/java)
+install_jar_exports(
+  TARGETS ${PROJECT_NAME}
+  NAMESPACE bar::
+  FILE JavaInstallExportTestConfig.cmake
+  DESTINATION share/cmake)
diff --git a/Tests/JavaModExportImport/InstallExport/module-info.java b/Tests/JavaModExportImport/InstallExport/module-info.java
new file mode 100644
index 0000000..7c8b62d
--- /dev/null
+++ b/Tests/JavaModExportImport/InstallExport/module-info.java
@@ -0,0 +1,3 @@
+module mod_bar {
+    exports com.bar;
+}
diff --git a/Tests/JavaModExportImport/main.c b/Tests/JavaModExportImport/main.c
new file mode 100644
index 0000000..8488f4e
--- /dev/null
+++ b/Tests/JavaModExportImport/main.c
@@ -0,0 +1,4 @@
+int main(void)
+{
+  return 0;
+}
diff --git a/Utilities/std/cm/type_traits b/Utilities/std/cm/type_traits
index 6bb6b5c..6126e71 100644
--- a/Utilities/std/cm/type_traits
+++ b/Utilities/std/cm/type_traits
@@ -11,27 +11,39 @@
 
 #if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
 
-// Miscellaneous transformations
 template <bool B, typename T = void>
 using enable_if_t = std::enable_if_t<B, T>;
 
+using std::conditional_t;
+using std::decay_t;
+using std::remove_cv_t;
+using std::remove_reference_t;
+
 #else
 
-// Miscellaneous transformations
 template <bool B, typename T = void>
 using enable_if_t = typename std::enable_if<B, T>::type;
 
-#endif
+template <bool B, typename T, typename F>
+using conditional_t = typename std::conditional<B, T, F>::type;
+
+template <typename T>
+using decay_t = typename std::decay<T>::type;
+
+template <typename T>
+using remove_cv_t = typename std::remove_cv<T>::type;
+
+template <typename T>
+using remove_reference_t = typename std::remove_reference<T>::type;
+
+#endif // C++14 (_t aliases)
 
 #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
 
-// Helper classes
 using std::bool_constant;
 
-// Miscellaneous transformations
 using std::invoke_result;
 using std::invoke_result_t;
-
 using std::void_t;
 
 #else
@@ -44,7 +56,7 @@
 template <typename F, typename... ArgTypes>
 using invoke_result = std::result_of<F(ArgTypes...)>;
 
-template <class F, typename... ArgTypes>
+template <typename F, typename... ArgTypes>
 using invoke_result_t = typename invoke_result<F, ArgTypes...>::type;
 
 template <typename... ArgTypes>
@@ -77,13 +89,13 @@
       !std::is_convertible<T, typename std::underlying_type<T>::type>::value>
 {
 };
-}
+} // namespace internals
 
 template <typename T>
 struct is_scoped_enum : public internals::is_scoped_enum_helper<T>
 {
 };
 
-#endif
+#endif // is_scoped_enum
 
 } // namespace cm