Merge branch 'release-4.1'
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cccd68c..4474ab9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -806,6 +806,13 @@
         CMAKE_CI_BUILD_NAME: oneapi2025.1.0_makefiles
         CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2025.1.0-rocky9
 
+t:oneapi2025.2.0-makefiles:
+    extends:
+        - .cmake_test_linux_inteloneapi_makefiles
+    variables:
+        CMAKE_CI_BUILD_NAME: oneapi2025.2.0_makefiles
+        CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2025.2.0-rocky9
+
 b:linux-x86_64-package:
     extends:
         - .linux_package
diff --git a/.gitlab/.gitignore b/.gitlab/.gitignore
index ef38d5f..848d72f 100644
--- a/.gitlab/.gitignore
+++ b/.gitlab/.gitignore
@@ -2,6 +2,7 @@
 /5.15.1-0-202009071110*
 /bcc*
 /cmake*
+/emsdk
 /iar
 /intel
 /ispc*
diff --git a/.gitlab/ci/configure_debian12_makefiles_clang.cmake b/.gitlab/ci/configure_debian12_makefiles_clang.cmake
index 9bd6275..fa92973 100644
--- a/.gitlab/ci/configure_debian12_makefiles_clang.cmake
+++ b/.gitlab/ci/configure_debian12_makefiles_clang.cmake
@@ -4,6 +4,7 @@
 if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
   set(CMake_TEST_IAR_TOOLCHAINS "/opt/iarsystems" CACHE PATH "")
   set(CMake_TEST_TICLANG_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/ticlang" CACHE PATH "")
+  set(CMake_TEST_Emscripten_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/emsdk/upstream/emscripten" CACHE PATH "")
 endif()
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_debian12_ninja_clang.cmake b/.gitlab/ci/configure_debian12_ninja_clang.cmake
index 1a8e192..7f45fa9 100644
--- a/.gitlab/ci/configure_debian12_ninja_clang.cmake
+++ b/.gitlab/ci/configure_debian12_ninja_clang.cmake
@@ -1,6 +1,7 @@
 if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
   set(CMake_TEST_IAR_TOOLCHAINS "/opt/iarsystems" CACHE PATH "")
   set(CMake_TEST_TICLANG_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/ticlang" CACHE PATH "")
+  set(CMake_TEST_Emscripten_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/emsdk/upstream/emscripten" CACHE PATH "")
 endif()
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/emsdk-env.sh b/.gitlab/ci/emsdk-env.sh
new file mode 100644
index 0000000..c62e0e7
--- /dev/null
+++ b/.gitlab/ci/emsdk-env.sh
@@ -0,0 +1,3 @@
+.gitlab/ci/emsdk.sh
+. .gitlab/emsdk/emsdk_env.sh
+em++ --version
diff --git a/.gitlab/ci/emsdk.sh b/.gitlab/ci/emsdk.sh
new file mode 100755
index 0000000..0a798a2
--- /dev/null
+++ b/.gitlab/ci/emsdk.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+set -e
+
+case "$(uname -s)-$(uname -m)" in
+    Linux-x86_64)
+        ;;
+    *)
+        echo "Unrecognized platform $(uname -s)-$(uname -m)"
+        exit 1
+        ;;
+esac
+
+cd .gitlab
+
+version=4.0.9
+dirname="emsdk-$version"
+filename="$dirname.tar.gz"
+curl -OJL "https://github.com/emscripten-core/emsdk/archive/refs/tags/$version.tar.gz"
+tar xzf "$filename"
+mv "$dirname" emsdk
+emsdk/emsdk install "$version"
+emsdk/emsdk activate "$version"
+
+rm -f "$filename"
diff --git a/.gitlab/ci/env_debian12_makefiles_clang.sh b/.gitlab/ci/env_debian12_makefiles_clang.sh
index e4ee249..11f0745 100644
--- a/.gitlab/ci/env_debian12_makefiles_clang.sh
+++ b/.gitlab/ci/env_debian12_makefiles_clang.sh
@@ -1,6 +1,7 @@
 if test "$CMAKE_CI_NIGHTLY" = "true"; then
   source .gitlab/ci/iar-env.sh
   source .gitlab/ci/ticlang-env.sh
+  source .gitlab/ci/emsdk-env.sh
 fi
 
 export CC=/usr/bin/clang-15
diff --git a/.gitlab/ci/env_debian12_ninja_clang.sh b/.gitlab/ci/env_debian12_ninja_clang.sh
index e4ee249..11f0745 100644
--- a/.gitlab/ci/env_debian12_ninja_clang.sh
+++ b/.gitlab/ci/env_debian12_ninja_clang.sh
@@ -1,6 +1,7 @@
 if test "$CMAKE_CI_NIGHTLY" = "true"; then
   source .gitlab/ci/iar-env.sh
   source .gitlab/ci/ticlang-env.sh
+  source .gitlab/ci/emsdk-env.sh
 fi
 
 export CC=/usr/bin/clang-15
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8b6d940..a9cb573 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -39,6 +39,7 @@
                 CMakeLib/testCTestResourceSpec_data/spec13
               | RunCMake/CTestResourceAllocation/invalid
               )\.json
+      - id: check-toml
       - id: check-yaml
       - id: end-of-file-fixer
         # NOTE Exclude tests directory: some test files have no
@@ -102,13 +103,14 @@
       - id: rst-inline-touching-normal
 
   - repo: https://github.com/codespell-project/codespell
-    rev: v2.4.0
+    rev: v2.4.1
     hooks:
       - id: codespell
         stages: [commit-msg, pre-commit]
 
-  - repo: https://github.com/crate-ci/typos
-    rev: v1.30.0
+    # NOTE See BUG https://github.com/crate-ci/typos/issues/390
+  - repo: https://github.com/adhtruong/mirrors-typos
+    rev: v1.33.1
     hooks:
       - id: typos
         # NOTE Override hook's default args to prevent automatic
diff --git a/.typos.toml b/.typos.toml
index 5ad2245..6838bb7 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -5,10 +5,10 @@
 check-file = true
 check-filename = true
 extend-ignore-re = [
-    # NOTE Allow to mark a block of text to exclude from spellchecking
-    "(?s)(#|/(/|\\*)|\\.\\.)\\s*(NOQA|noqa):? spellcheck(: *|=| +)off.*?\\n\\s*(#|/(/|\\*)|\\.\\.)\\s*(NOQA|noqa):? spellcheck(: *|=| +)on"
+    # NOTE Allow to mark block of text to exclude from spellchecking inside C++ or hash-style comments (CMake,Python,&etc.)
+    "(?s)(#|//)\\s*(NOQA|noqa):? spellcheck(: *|=| +)off.*?\\n\\s*(#|//)\\s*(NOQA|noqa):? spellcheck(: *|=| +)on"
     # NOTE Allow to mark a line to exclude from spellchecking
-  , "(?Rm)^.*(#|/(/|\\*)|\\.\\.)\\s*(NOQA|noqa):? spellcheck(: *|=| +)disable-line$"
+  , "(?Rm)^.*(#|//)\\s*(NOQA|noqa):? spellcheck(: *|=| +)disable-line$"
     # NOTE Stop checking from this line to the end of file
     # This line is a marker added by Git to the `COMMIT_EDITMSG`.
   , "(?sm)^# ------------------------ >8 ------------------------$.*"
@@ -34,7 +34,28 @@
 restat = "restat"
 # SpectreMitigation
 Spectre = "Spectre"
+# Identifier used in source code (`GlobalTargetInfo`)
+gti = "gti"
 
+[files]
+ignore-hidden = false
+ignore-dot = false
+extend-exclude = [
+    "CONTRIBUTORS.rst"
+    # Exclude third-party sources.
+  , "Source/CursesDialog/form/"
+  , "Source/kwsys/"
+  , "Source/bindexplib.cxx"
+  , "Source/cmcldeps.cxx"
+  , "Source/QtDialog/*.ui"
+  , "Utilities/cm*"
+  , "Utilities/ClangTidyModule"
+  , "Utilities/KWIML"
+    # FIXME: Fix spelling typos in tests.  Exclude for now.
+  , "Tests"
+  ]
+
+# BEGIN Type-specific settings
 [type.cmake.extend-identifiers]
 COMMANDs = "COMMANDs"
 xCOMMANDx = "xCOMMANDx"
@@ -60,20 +81,9 @@
 [type.py.extend-identifiers]
 typ = "typ"
 
-[files]
-ignore-hidden = false
-ignore-dot = false
-extend-exclude = [
-    "CONTRIBUTORS.rst"
-    # Exclude third-party sources.
-  , "Source/CursesDialog/form/"
-  , "Source/kwsys/"
-  , "Source/bindexplib.cxx"
-  , "Source/cmcldeps.cxx"
-  , "Source/QtDialog/*.ui"
-  , "Utilities/cm*"
-  , "Utilities/ClangTidyModule"
-  , "Utilities/KWIML"
-    # FIXME: Fix spelling typos in tests.  Exclude for now.
-  , "Tests"
+[type.rst]
+extend-ignore-re = [
+    # NOTE Allow to mark block of text to exclude from spellchecking as RST comments
+    "(?s)\\.\\.\\s+(NOQA|noqa):? spellcheck(: *|=| +)off.*?\\n\\.\\.\\s+(NOQA|noqa):? spellcheck(: *|=| +)on"
   ]
+# END Type-specific settings
diff --git a/Help/command/export.rst b/Help/command/export.rst
index 6668e97..9673869 100644
--- a/Help/command/export.rst
+++ b/Help/command/export.rst
@@ -141,7 +141,9 @@
           [VERSION_SCHEMA <string>]]
          [DEFAULT_TARGETS <target>...]
          [DEFAULT_CONFIGURATIONS <config>...]
-         [DESCRIPTION <project-description-string>]
+         [LICENSE <license-string>]
+         [DEFAULT_LICENSE <license-string>]
+         [DESCRIPTION <description-string>]
          [HOMEPAGE_URL <url-string>])
 
 .. versionadded:: 4.1
diff --git a/Help/command/find_package.rst b/Help/command/find_package.rst
index 4dc9696..4073345 100644
--- a/Help/command/find_package.rst
+++ b/Help/command/find_package.rst
@@ -581,12 +581,12 @@
 configuration file found is used, even if a newer version of the package
 resides later in the list of search paths.
 
-For search paths which contain glob expressions (``*``), the order in which
-directories matching the glob are searched is unspecified unless the
-:variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` variable is set.  This variable,
-along with the :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable,
-determines the order in which CMake considers glob matches.  For example, if
-the file system contains the package configuration files
+For search paths which contain glob expressions (``*``), directories matching
+the glob are searched in natural, descending order by default. This behavior
+can be overridden by setting variables :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`
+and :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` accordingly. Those variables
+determine the order in which CMake considers glob matches. For example, if the
+file system contains the package configuration files
 
 ::
 
@@ -594,21 +594,21 @@
   <prefix>/example-1.10/example-config.cmake
   <prefix>/share/example-2.0/example-config.cmake
 
-it is unspecified (when the aforementioned variables are unset) whether
-``find_package(example)`` will find ``example-1.2`` or ``example-1.10``
-(assuming that both are viable), but ``find_package`` will *not* find
-``example-2.0``, because one of the other two will be found first.
+then ``find_package(example)`` will (when the aforementioned variables are
+unset) pick ``example-1.10`` (assuming both ``example-1.2`` and ``example-1.10``
+are viable). Note however that ``find_package`` will *not* find ``example-2.0``,
+because one of the other two will be found first.
 
 To control the order in which ``find_package`` searches directories that match
 a glob expression, use :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` and
 :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION`.
-For instance, to cause the above example to select ``example-1.10``,
+For instance, to cause the above example to select ``example-1.2``,
 one can set
 
 .. code-block:: cmake
 
   set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
-  set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
+  set(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
 
 before calling ``find_package``.
 
@@ -624,6 +624,15 @@
    and ``<prefix>/<name>.framework/Versions/*/Resources/CMake``.  In previous
    versions of CMake, this order was unspecified.
 
+.. versionchanged:: 4.2
+   When encountering multiple viable matches, ``find_package`` now picks the
+   one with the most recent version by default. In previous versions of CMake,
+   the result was unspecified. Accordingly, the default of
+   :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` has changed from ``NONE`` to
+   ``NATURAL`` and :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION`
+   now defaults to ``DEC`` (descending) instead of ``ASC`` (ascending).
+
+
 .. include:: include/FIND_XXX_ROOT.rst
 .. include:: include/FIND_XXX_ORDER.rst
 
diff --git a/Help/command/install.rst b/Help/command/install.rst
index 001bee0..2ab9780 100644
--- a/Help/command/install.rst
+++ b/Help/command/install.rst
@@ -1001,7 +1001,9 @@
              [VERSION_SCHEMA <string>]]
             [DEFAULT_TARGETS <target>...]
             [DEFAULT_CONFIGURATIONS <config>...]
-            [DESCRIPTION <project-description-string>]
+            [LICENSE <license-string>]
+            [DEFAULT_LICENSE <license-string>]
+            [DESCRIPTION <description-string>]
             [HOMEPAGE_URL <url-string>]
             [PERMISSIONS <permission>...]
             [CONFIGURATIONS <config>...]
@@ -1059,7 +1061,25 @@
     configurations exists.  If not specified, CMake will fall back to the
     package's available configurations in an unspecified order.
 
-  ``DESCRIPTION <project-description-string>``
+  ``LICENSE <license-string>``
+    .. versionadded:: 4.2
+
+    A |SPDX|_ (SPDX) `License Expression`_ that describes the license(s) of the
+    project as a whole, including documentation, resources, or other materials
+    distributed with the project, in addition to software artifacts.  See the
+    SPDX `License List`_ for a list of commonly used licenses and their
+    identifiers.
+
+    The license of individual components is taken from the
+    :prop_tgt:`SPDX_LICENSE` property of their respective targets.
+
+  ``DEFAULT_LICENSE <license-string>``
+    .. versionadded:: 4.2
+
+    A |SPDX|_ (SPDX) `License Expression`_ that describes the license(s) of any
+    components which do not otherwise specify their license(s).
+
+  ``DESCRIPTION <description-string>``
     .. versionadded:: 4.1
 
     An informational description of the project.  It is recommended that this
@@ -1291,3 +1311,9 @@
 
 .. _cps-version_schema: https://cps-org.github.io/cps/schema.html#version-schema
 .. |cps-version_schema| replace:: ``version_schema``
+
+.. _SPDX: https://spdx.dev/
+.. |SPDX| replace:: System Package Data Exchange
+
+.. _License Expression: https://spdx.github.io/spdx-spec/v3.0.1/annexes/spdx-license-expressions/
+.. _License List: https://spdx.org/licenses/
diff --git a/Help/command/project.rst b/Help/command/project.rst
index bf0f171..a079bab 100644
--- a/Help/command/project.rst
+++ b/Help/command/project.rst
@@ -12,7 +12,8 @@
  project(<PROJECT-NAME>
          [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
          [COMPAT_VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
-         [DESCRIPTION <project-description-string>]
+         [SPDX_LICENSE <license-string>]
+         [DESCRIPTION <description-string>]
          [HOMEPAGE_URL <url-string>]
          [LANGUAGES <language-name>...])
 
@@ -106,7 +107,30 @@
     ``CMakeLists.txt``, then the compatibility version is also stored in the
     variable :variable:`CMAKE_PROJECT_COMPAT_VERSION`.
 
-``DESCRIPTION <project-description-string>``
+``SPDX_LICENSE <license-string>``
+  .. versionadded:: 4.2
+
+  Optional.
+  Sets the variables
+
+  * :variable:`PROJECT_SPDX_LICENSE`,
+    :variable:`<PROJECT-NAME>_SPDX_LICENSE`
+
+  to ``<license-string>``, which shall be a |SPDX|_ (SPDX)
+  `License Expression`_ that describes the license(s) of the project as a
+  whole, including documentation, resources, or other materials distributed
+  with the project, in addition to software artifacts. See the SPDX
+  `License List`_ for a list of commonly used licenses and their identifiers.
+  See the :prop_tgt:`SPDX_LICENSE` property for specifying the license(s) on
+  individual software artifacts.
+
+.. _SPDX: https://spdx.dev/
+.. |SPDX| replace:: System Package Data Exchange
+
+.. _License Expression: https://spdx.github.io/spdx-spec/v3.0.1/annexes/spdx-license-expressions/
+.. _License List: https://spdx.org/licenses/
+
+``DESCRIPTION <description-string>``
   .. versionadded:: 3.9
 
   Optional.
@@ -114,7 +138,7 @@
 
   * :variable:`PROJECT_DESCRIPTION`, :variable:`<PROJECT-NAME>_DESCRIPTION`
 
-  to ``<project-description-string>``.
+  to ``<description-string>``.
   It is recommended that this description is a relatively short string,
   usually no more than a few words.
 
@@ -149,10 +173,11 @@
 Specify language ``NONE``, or use the ``LANGUAGES`` keyword and list no languages,
 to skip enabling any languages.
 
-The variables set through the ``VERSION``, ``COMPAT_VERSION``, ``DESCRIPTION``
-and ``HOMEPAGE_URL`` options are intended for use as default values in package
-metadata and documentation. The :command:`export` and :command:`install`
-commands use these accordingly when generating |CPS| package descriptions.
+The variables set through the ``VERSION``, ``COMPAT_VERSION``,
+``SPDX_LICENSE``, ``DESCRIPTION`` and ``HOMEPAGE_URL`` options are
+intended for use as default values in package metadata and documentation.
+The :command:`export` and :command:`install` commands use these accordingly
+when generating |CPS| package descriptions.
 
 .. |CPS| replace:: Common Package Specification
 
diff --git a/Help/generator/Visual Studio 10 2010.rst b/Help/generator/Visual Studio 10 2010.rst
index 8b7e31d..08940d2 100644
--- a/Help/generator/Visual Studio 10 2010.rst
+++ b/Help/generator/Visual Studio 10 2010.rst
@@ -3,7 +3,7 @@
 
 Removed.  This once generated Visual Studio 10 2010 project files, but
 the generator has been removed since CMake 3.25.  It is still possible
-to build with the VS 10 2010 toolset by also installing VS 2015 (or above)
-and using the :generator:`Visual Studio 14 2015` (or above) generator with
+to build with the VS 10 2010 toolset by also installing VS 2017 (or above)
+and using the :generator:`Visual Studio 15 2017` (or above) generator with
 :variable:`CMAKE_GENERATOR_TOOLSET` set to ``v100``,
 or by using the :generator:`NMake Makefiles` generator.
diff --git a/Help/generator/Visual Studio 11 2012.rst b/Help/generator/Visual Studio 11 2012.rst
index 99048bd..8c0f37c 100644
--- a/Help/generator/Visual Studio 11 2012.rst
+++ b/Help/generator/Visual Studio 11 2012.rst
@@ -3,7 +3,7 @@
 
 Removed.  This once generated Visual Studio 11 2012 project files, but
 the generator has been removed since CMake 3.28.  It is still possible
-to build with the VS 11 2012 toolset by also installing VS 2015 (or above)
-and using the :generator:`Visual Studio 14 2015` (or above) generator with
+to build with the VS 11 2012 toolset by also installing VS 2017 (or above)
+and using the :generator:`Visual Studio 15 2017` (or above) generator with
 :variable:`CMAKE_GENERATOR_TOOLSET` set to ``v110``,
 or by using the :generator:`NMake Makefiles` generator.
diff --git a/Help/generator/Visual Studio 12 2013.rst b/Help/generator/Visual Studio 12 2013.rst
index 6589072..1724c0c 100644
--- a/Help/generator/Visual Studio 12 2013.rst
+++ b/Help/generator/Visual Studio 12 2013.rst
@@ -3,7 +3,7 @@
 
 Removed.  This once generated Visual Studio 12 2013 project files, but
 the generator has been removed since CMake 3.31.  It is still possible
-to build with the VS 12 2013 toolset by also installing VS 2015 (or above)
-and using the :generator:`Visual Studio 14 2015` (or above) generator with
+to build with the VS 12 2013 toolset by also installing VS 2017 (or above)
+and using the :generator:`Visual Studio 15 2017` (or above) generator with
 :variable:`CMAKE_GENERATOR_TOOLSET` set to ``v120``,
 or by using the :generator:`NMake Makefiles` generator.
diff --git a/Help/generator/Visual Studio 14 2015.rst b/Help/generator/Visual Studio 14 2015.rst
index a491193..7e9035d 100644
--- a/Help/generator/Visual Studio 14 2015.rst
+++ b/Help/generator/Visual Studio 14 2015.rst
@@ -1,6 +1,14 @@
 Visual Studio 14 2015
 ---------------------
 
+.. deprecated:: 4.2
+
+  This generator is deprecated and will be removed in a future version
+  of CMake.  It will still be possible to build with VS 14 2015 tools
+  using the :generator:`Visual Studio 15 2017` (or above) generator
+  with :variable:`CMAKE_GENERATOR_TOOLSET` set to ``v140``, or by
+  using the :generator:`NMake Makefiles` generator.
+
 .. versionadded:: 3.1
 
 Generates Visual Studio 14 (VS 2015) project files.
diff --git a/Help/generator/Visual Studio 9 2008.rst b/Help/generator/Visual Studio 9 2008.rst
index a5d953a..1e2730c 100644
--- a/Help/generator/Visual Studio 9 2008.rst
+++ b/Help/generator/Visual Studio 9 2008.rst
@@ -4,6 +4,6 @@
 Removed.  This once generated Visual Studio 9 2008 project files, but
 the generator has been removed since CMake 3.30.  It is still possible
 to build with the VS 9 2008 toolset by also installing VS 10 2010 and
-VS 2015 (or above) and using the :generator:`Visual Studio 14 2015`
+VS 2017 (or above) and using the :generator:`Visual Studio 15 2017`
 generator (or above) with :variable:`CMAKE_GENERATOR_TOOLSET` set to ``v90``,
 or by using the :generator:`NMake Makefiles` generator.
diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst
index 4bc0435..b031246 100644
--- a/Help/manual/cmake-generator-expressions.7.rst
+++ b/Help/manual/cmake-generator-expressions.7.rst
@@ -1420,8 +1420,8 @@
   Note that for proper evaluation of this expression requires policy :policy:`CMP0099`
   to be set to ``NEW``.
 
-Linker Language And ID
-^^^^^^^^^^^^^^^^^^^^^^
+Link Language and ID
+^^^^^^^^^^^^^^^^^^^^
 
 .. genex:: $<LINK_LANGUAGE>
 
@@ -1827,6 +1827,209 @@
   (see :genex:`$<DEVICE_LINK:list>` generator expression). This expression can
   only be used to specify link options.
 
+Linker ID and Frontend-Variant
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+See also the :variable:`CMAKE_<LANG>_COMPILER_LINKER_ID` and
+:variable:`CMAKE_<LANG>_COMPILER_LINKER_FRONTEND_VARIANT` variables, which are
+closely related to most of the expressions in this sub-section.
+
+.. genex:: $<C_COMPILER_LINKER_ID>
+
+  .. versionadded:: 4.2
+
+  CMake's linker id of the C linker used.
+
+.. genex:: $<C_COMPILER_LINKER_ID:linker_ids>
+
+  .. versionadded:: 4.2
+
+  where ``linker_ids`` is a comma-separated list.
+  ``1`` if CMake's linker id of the C linker matches any one
+  of the entries in ``linker_ids``, otherwise ``0``.
+
+.. genex:: $<CXX_COMPILER_LINKER_ID>
+
+  .. versionadded:: 4.2
+
+  CMake's linker id of the C++ linker used.
+
+.. genex:: $<CXX_COMPILER_LINKER_ID:linker_ids>
+
+  .. versionadded:: 4.2
+
+  where ``linker_ids`` is a comma-separated list.
+  ``1`` if CMake's linker id of the C++ linker matches any one
+  of the entries in ``linker_ids``, otherwise ``0``.
+
+.. genex:: $<CUDA_COMPILER_LINKER_ID>
+
+  .. versionadded:: 4.2
+
+  CMake's linker id of the CUDA linker used.
+
+.. genex:: $<CUDA_COMPILER_LINKER_ID:linker_ids>
+
+  .. versionadded:: 4.2
+
+  where ``linker_ids`` is a comma-separated list.
+  ``1`` if CMake's linker id of the CUDA linker matches any one
+  of the entries in ``linker_ids``, otherwise ``0``.
+
+.. genex:: $<OBJC_COMPILER_LINKER_ID>
+
+  .. versionadded:: 4.2
+
+  CMake's linker id of the Objective-C linker used.
+
+.. genex:: $<OBJC_COMPILER_LINKER_ID:linker_ids>
+
+  .. versionadded:: 4.2
+
+  where ``linker_ids`` is a comma-separated list.
+  ``1`` if CMake's linker id of the Objective-C linker matches any one
+  of the entries in ``linker_ids``, otherwise ``0``.
+
+.. genex:: $<OBJCXX_COMPILER_LINKER_ID>
+
+  .. versionadded:: 4.2
+
+  CMake's linker id of the Objective-C++ linker used.
+
+.. genex:: $<OBJCXX_COMPILER_LINKER_ID:linker_ids>
+
+  .. versionadded:: 4.2
+
+  where ``linker_ids`` is a comma-separated list.
+  ``1`` if CMake's linker id of the Objective-C++ linker matches any one
+  of the entries in ``linker_ids``, otherwise ``0``.
+
+.. genex:: $<Fortran_COMPILER_LINKER_ID>
+
+  .. versionadded:: 4.2
+
+  CMake's linker id of the Fortran linker used.
+
+.. genex:: $<Fortran_COMPILER_LINKER_ID:linker_ids>
+
+  .. versionadded:: 4.2
+
+  where ``linker_ids`` is a comma-separated list.
+  ``1`` if CMake's linker id of the Fortran linker matches any one
+  of the entries in ``linker_ids``, otherwise ``0``.
+
+.. genex:: $<HIP_COMPILER_LINKER_ID>
+
+  .. versionadded:: 4.2
+
+  CMake's linker id of the HIP linker used.
+
+.. genex:: $<HIP_COMPILER_LINKER_ID:linker_ids>
+
+  .. versionadded:: 4.2
+
+  where ``linker_ids`` is a comma-separated list.
+  ``1`` if CMake's linker id of the HIP linker matches any one
+  of the entries in ``linker_ids``, otherwise ``0``.
+
+.. genex:: $<C_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  .. versionadded:: 4.2
+
+  CMake's linker frontend variant of the C linker used.
+
+.. genex:: $<C_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids>
+
+  .. versionadded:: 4.2
+
+  where ``variant_ids`` is a comma-separated list.
+  ``1`` if CMake's linker frontend variant of the C linker matches any one
+  of the entries in ``variant_ids``, otherwise ``0``.
+
+.. genex:: $<CXX_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  .. versionadded:: 4.2
+
+  CMake's linker frontend variant of the C++ linker used.
+
+.. genex:: $<CXX_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids>
+
+  .. versionadded:: 4.2
+
+  where ``variant_ids`` is a comma-separated list.
+  ``1`` if CMake's linker frontend variant of the C++ linker matches any one
+  of the entries in ``variant_ids``, otherwise ``0``.
+
+.. genex:: $<CUDA_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  .. versionadded:: 4.2
+
+  CMake's linker frontend variant of the CUDA linker used.
+
+.. genex:: $<CUDA_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids>
+
+  .. versionadded:: 4.2
+
+  where ``variant_ids`` is a comma-separated list.
+  ``1`` if CMake's linker frontend variant of the CUDA linker matches any one
+  of the entries in ``variant_ids``, otherwise ``0``.
+
+.. genex:: $<OBJC_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  .. versionadded:: 4.2
+
+  CMake's linker frontend variant of the Objective-C linker used.
+
+.. genex:: $<OBJC_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids>
+
+  .. versionadded:: 4.2
+
+  where ``variant_ids`` is a comma-separated list.
+  ``1`` if CMake's linker frontend variant of the Objective-C linker matches
+  any one of the entries in ``variant_ids``, otherwise ``0``.
+
+.. genex:: $<OBJCXX_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  .. versionadded:: 4.2
+
+  CMake's linker frontend variant of the Objective-C++ linker used.
+
+.. genex:: $<OBJCXX_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids>
+
+  .. versionadded:: 4.2
+
+  where ``variant_ids`` is a comma-separated list.
+  ``1`` if CMake's linker frontend variant of the Objective-C++ linker matches
+  any one of the entries in ``variant_ids``, otherwise ``0``.
+
+.. genex:: $<Fortran_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  .. versionadded:: 4.2
+
+  CMake's linker frontend variant of the Fortran linker used.
+
+.. genex:: $<Fortran_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids>
+
+  .. versionadded:: 4.2
+
+  where ``variant_ids`` is a comma-separated list.
+  ``1`` if CMake's linker frontend variant of the Fortran linker matches
+  any one of the entries in ``variant_ids``, otherwise ``0``.
+
+.. genex:: $<HIP_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  .. versionadded:: 4.2
+
+  CMake's linker frontend variant of the HIP linker used.
+
+.. genex:: $<HIP_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids>
+
+  .. versionadded:: 4.2
+
+  where ``variant_ids`` is a comma-separated list.
+  ``1`` if CMake's linker frontend variant of the HIP linker matches
+  any one of the entries in ``variant_ids``, otherwise ``0``.
+
 
 .. _`Target-Dependent Expressions`:
 
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index 45e0ca0..21cc92a 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.0)
+  cmake_minimum_required(VERSION 3.10...4.1)
 
 This uses the ``<min>...<max>`` syntax to enable the ``NEW`` behaviors
-of policies introduced in CMake 4.0 and earlier while only requiring a
+of policies introduced in CMake 4.1 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/manual/cmake-toolchains.7.rst b/Help/manual/cmake-toolchains.7.rst
index 9eee219..cbd1f74 100644
--- a/Help/manual/cmake-toolchains.7.rst
+++ b/Help/manual/cmake-toolchains.7.rst
@@ -704,3 +704,22 @@
 
 - Use :command:`find_package` only for libraries installed with
   :variable:`CMAKE_IOS_INSTALL_COMBINED` feature
+
+.. _`Cross Compiling for Emscripten`:
+
+Cross Compiling for Emscripten
+------------------------------
+
+.. versionadded:: 4.2
+
+A toolchain file may configure cross-compiling for `Emscripten`_ by
+setting the :variable:`CMAKE_SYSTEM_NAME` variable to ``Emscripten``.
+For example, a toolchain file might contain:
+
+.. code-block:: cmake
+
+  set(CMAKE_SYSTEM_NAME Emscripten)
+  set(CMAKE_C_COMPILER /path/to/emcc)
+  set(CMAKE_CXX_COMPILER /path/to/em++)
+
+.. _`Emscripten`: https://emscripten.org/
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index d3ff681..1b8d0a7 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -98,15 +98,16 @@
    /variable/CMAKE_OBJDUMP
    /variable/CMAKE_PARENT_LIST_FILE
    /variable/CMAKE_PATCH_VERSION
+   /variable/CMAKE_PROJECT_COMPAT_VERSION
    /variable/CMAKE_PROJECT_DESCRIPTION
    /variable/CMAKE_PROJECT_HOMEPAGE_URL
    /variable/CMAKE_PROJECT_NAME
+   /variable/CMAKE_PROJECT_SPDX_LICENSE
    /variable/CMAKE_PROJECT_VERSION
    /variable/CMAKE_PROJECT_VERSION_MAJOR
    /variable/CMAKE_PROJECT_VERSION_MINOR
    /variable/CMAKE_PROJECT_VERSION_PATCH
    /variable/CMAKE_PROJECT_VERSION_TWEAK
-   /variable/CMAKE_PROJECT_COMPAT_VERSION
    /variable/CMAKE_RANLIB
    /variable/CMAKE_ROOT
    /variable/CMAKE_RULE_MESSAGES
@@ -154,28 +155,30 @@
    /variable/CMAKE_XCODE_BUILD_SYSTEM
    /variable/CMAKE_XCODE_PLATFORM_TOOLSET
    /variable/PROJECT-NAME_BINARY_DIR
+   /variable/PROJECT-NAME_COMPAT_VERSION
    /variable/PROJECT-NAME_DESCRIPTION
    /variable/PROJECT-NAME_HOMEPAGE_URL
    /variable/PROJECT-NAME_IS_TOP_LEVEL
    /variable/PROJECT-NAME_SOURCE_DIR
+   /variable/PROJECT-NAME_SPDX_LICENSE
    /variable/PROJECT-NAME_VERSION
    /variable/PROJECT-NAME_VERSION_MAJOR
    /variable/PROJECT-NAME_VERSION_MINOR
    /variable/PROJECT-NAME_VERSION_PATCH
    /variable/PROJECT-NAME_VERSION_TWEAK
-   /variable/PROJECT-NAME_COMPAT_VERSION
    /variable/PROJECT_BINARY_DIR
+   /variable/PROJECT_COMPAT_VERSION
    /variable/PROJECT_DESCRIPTION
    /variable/PROJECT_HOMEPAGE_URL
    /variable/PROJECT_IS_TOP_LEVEL
    /variable/PROJECT_NAME
    /variable/PROJECT_SOURCE_DIR
+   /variable/PROJECT_SPDX_LICENSE
    /variable/PROJECT_VERSION
    /variable/PROJECT_VERSION_MAJOR
    /variable/PROJECT_VERSION_MINOR
    /variable/PROJECT_VERSION_PATCH
    /variable/PROJECT_VERSION_TWEAK
-   /variable/PROJECT_COMPAT_VERSION
 
 Variables that Change Behavior
 ==============================
diff --git a/Help/prop_tgt/SPDX_LICENSE.rst b/Help/prop_tgt/SPDX_LICENSE.rst
index 056c860..a030e6c 100644
--- a/Help/prop_tgt/SPDX_LICENSE.rst
+++ b/Help/prop_tgt/SPDX_LICENSE.rst
@@ -3,9 +3,9 @@
 
 .. versionadded:: 4.1
 
-Specify the license of a target using a |SPDX|_ (SPDX) `License Expression`_.
-See the SPDX `License List`_ for a list of commonly used licenses and their
-identifiers.
+Specify the license(s) of a target using a |SPDX|_ (SPDX)
+`License Expression`_. See the SPDX `License List`_ for a list of commonly used
+licenses and their identifiers.
 
 .. _SPDX: https://spdx.dev/
 .. |SPDX| replace:: System Package Data Exchange
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/GenEx-COMPILER_LINKER.rst b/Help/release/dev/GenEx-COMPILER_LINKER.rst
new file mode 100644
index 0000000..d0a1896
--- /dev/null
+++ b/Help/release/dev/GenEx-COMPILER_LINKER.rst
@@ -0,0 +1,8 @@
+GenEx-COMPILER_LINKER
+---------------------
+
+* The :genex:`<LANG>_COMPILER_LINKER_ID <C_COMPILER_LINKER_ID>` and
+  :genex:`<LANG>_COMPILER_LINKER_FRONTEND_VARIANT <C_COMPILER_LINKER_FRONTEND_VARIANT>`
+  families of generator expressions were added to access the value of the
+  associated :variable:`CMAKE_<LANG>_COMPILER_LINKER_ID` and
+  :variable:`CMAKE_<LANG>_COMPILER_LINKER_FRONTEND_VARIANT` variables.
diff --git a/Help/release/dev/emscripten-platform.rst b/Help/release/dev/emscripten-platform.rst
new file mode 100644
index 0000000..4757227
--- /dev/null
+++ b/Help/release/dev/emscripten-platform.rst
@@ -0,0 +1,5 @@
+emscripten-platform
+-------------------
+
+* CMake now supports :ref:`Cross Compiling for Emscripten` with simple
+  toolchain files.
diff --git a/Help/release/dev/vs14-deprecate.rst b/Help/release/dev/vs14-deprecate.rst
new file mode 100644
index 0000000..f3aab73
--- /dev/null
+++ b/Help/release/dev/vs14-deprecate.rst
@@ -0,0 +1,5 @@
+vs14-deprecate
+--------------
+
+* The :generator:`Visual Studio 14 2015` generator is now deprecated
+  and will be removed in a future version of CMake.
diff --git a/Help/release/index.rst b/Help/release/index.rst
index 400e1f2..64b933a 100644
--- a/Help/release/index.rst
+++ b/Help/release/index.rst
@@ -7,6 +7,8 @@
   This file should include the adjacent "dev.txt" file
   in development versions but not in release versions.
 
+.. include:: dev.txt
+
 Releases
 ========
 
diff --git a/Help/variable/CMAKE_BINARY_DIR.rst b/Help/variable/CMAKE_BINARY_DIR.rst
index 96c6319..b046037 100644
--- a/Help/variable/CMAKE_BINARY_DIR.rst
+++ b/Help/variable/CMAKE_BINARY_DIR.rst
@@ -11,3 +11,5 @@
 ``CMAKE_BINARY_DIR``, :variable:`CMAKE_SOURCE_DIR`,
 :variable:`CMAKE_CURRENT_BINARY_DIR` and
 :variable:`CMAKE_CURRENT_SOURCE_DIR` to the current working directory.
+
+Modifying ``CMAKE_BINARY_DIR`` has undefined behavior.
diff --git a/Help/variable/CMAKE_CURRENT_BINARY_DIR.rst b/Help/variable/CMAKE_CURRENT_BINARY_DIR.rst
index 1d7a111..1eabaef 100644
--- a/Help/variable/CMAKE_CURRENT_BINARY_DIR.rst
+++ b/Help/variable/CMAKE_CURRENT_BINARY_DIR.rst
@@ -13,3 +13,5 @@
 :variable:`CMAKE_BINARY_DIR`, :variable:`CMAKE_SOURCE_DIR`,
 ``CMAKE_CURRENT_BINARY_DIR`` and
 :variable:`CMAKE_CURRENT_SOURCE_DIR` to the current working directory.
+
+Modifying ``CMAKE_CURRENT_BINARY_DIR`` has undefined behavior.
diff --git a/Help/variable/CMAKE_CURRENT_SOURCE_DIR.rst b/Help/variable/CMAKE_CURRENT_SOURCE_DIR.rst
index 4205efb..7a08892 100644
--- a/Help/variable/CMAKE_CURRENT_SOURCE_DIR.rst
+++ b/Help/variable/CMAKE_CURRENT_SOURCE_DIR.rst
@@ -10,3 +10,5 @@
 :variable:`CMAKE_BINARY_DIR`, :variable:`CMAKE_SOURCE_DIR`,
 :variable:`CMAKE_CURRENT_BINARY_DIR` and
 ``CMAKE_CURRENT_SOURCE_DIR`` to the current working directory.
+
+Modifying ``CMAKE_CURRENT_SOURCE_DIR`` has undefined behavior.
diff --git a/Help/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION.rst b/Help/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION.rst
index 92758fd..033748a 100644
--- a/Help/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION.rst
+++ b/Help/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION.rst
@@ -3,16 +3,21 @@
 
 .. versionadded:: 3.7
 
+.. versionchanged:: 4.2
+
+  The default sort direction has changed from ``DEC`` to ``ASC``.
+
+
 The sorting direction used by :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER`.
 It can assume one of the following values:
 
 ``ASC``
-  Default.  Ordering is done in ascending mode.
+  Ordering is done in ascending mode.
   The lowest folder found will be tested first.
 
 ``DEC``
-  Ordering is done in descending mode.
+  Default. Ordering is done in descending mode.
   The highest folder found will be tested first.
 
-If :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` is not set or is set to ``NONE``
-this variable has no effect.
+If :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` is set to ``NONE`` this variable
+has no effect.
diff --git a/Help/variable/CMAKE_FIND_PACKAGE_SORT_ORDER.rst b/Help/variable/CMAKE_FIND_PACKAGE_SORT_ORDER.rst
index f1016d9..524b2ef 100644
--- a/Help/variable/CMAKE_FIND_PACKAGE_SORT_ORDER.rst
+++ b/Help/variable/CMAKE_FIND_PACKAGE_SORT_ORDER.rst
@@ -3,39 +3,40 @@
 
 .. versionadded:: 3.7
 
+.. versionchanged:: 4.2
+
+  The default sort order has changed from ``NONE`` to ``NATURAL``.
+
+
 The default order for sorting directories which match a search path containing
 a glob expression found using :command:`find_package`.  It can assume one of
 the following values:
 
 ``NONE``
-  Default.  No attempt is done to sort directories.
+  No attempt is done to sort directories.
   The first valid package found will be selected.
 
 ``NAME``
   Sort directories lexicographically before searching.
 
 ``NATURAL``
-  Sort directories using natural order (see ``strverscmp(3)`` manual),
+  Default. Sort directories using natural order (see ``strverscmp(3)`` manual),
   i.e. such that contiguous digits are compared as whole numbers.
 
-Natural sorting can be employed to return the highest version when multiple
-versions of the same library are available to be found by
-:command:`find_package`.  For example suppose that the following libraries
-have package configuration files on disk, in a directory of the same name,
-with all such directories residing in the same parent directory:
+Natural sorting is employed by default to return the highest version when
+multiple versions of the same library are available to be found by
+:command:`find_package`.  For example suppose that the following libraries have
+package configuration files on disk, in a directory of the same name, with all
+such directories residing in the same parent directory:
 
-* libX-1.1.0
-* libX-1.2.9
-* libX-1.2.10
+* ``libX-1.1.0``
+* ``libX-1.2.9``
+* ``libX-1.2.10``
 
-By setting ``NATURAL`` order we can select the one with the highest
-version number ``libX-1.2.10``.
-
-.. code-block:: cmake
-
-  set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
-  find_package(libX CONFIG)
+The default order of ``NATURAL`` will select the one with the highest version
+number, i.e. ``libX-1.2.10``.
 
 The sort direction can be controlled using the
-:variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable
-(by default descending, e.g. lib-B will be tested before lib-A).
+:variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable (by default descending,
+i.e. ``libX-1.2`` will be tested before ``libX-1.0`` and  ``lib-B`` will be
+tested before ``lib-A``).
diff --git a/Help/variable/CMAKE_PROJECT_SPDX_LICENSE.rst b/Help/variable/CMAKE_PROJECT_SPDX_LICENSE.rst
new file mode 100644
index 0000000..0cbde7c
--- /dev/null
+++ b/Help/variable/CMAKE_PROJECT_SPDX_LICENSE.rst
@@ -0,0 +1,41 @@
+CMAKE_PROJECT_SPDX_LICENSE
+--------------------------
+
+.. versionadded:: 4.2
+
+.. note::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
+
+The license(s) of the top level project.
+
+This variable holds the license expression of the project as specified in the
+top level CMakeLists.txt file by a :command:`project` command.  In the event
+that the top level CMakeLists.txt contains multiple :command:`project` calls,
+the most recently called one from that top level CMakeLists.txt will determine
+the value that ``CMAKE_PROJECT_SPDX_LICENSE`` contains.  For example, consider
+the following top level CMakeLists.txt:
+
+.. code-block:: cmake
+
+  cmake_minimum_required(VERSION 4.2)
+  project(First SPDX_LICENSE "BSD-3-Clause")
+  project(Second SPDX_LICENSE "BSD-3-Clause AND CC-BY-SA-4.0")
+  add_subdirectory(sub)
+  project(Third SPDX_LICENSE "BSD-3-Clause AND CC0-1.0")
+
+And ``sub/CMakeLists.txt`` with the following contents:
+
+.. code-block:: cmake
+
+  project(SubProj SPDX_LICENSE Apache-2.0)
+  message("CMAKE_PROJECT_SPDX_LICENSE = ${CMAKE_PROJECT_SPDX_LICENSE}")
+
+The most recently seen :command:`project` command from the top level
+CMakeLists.txt would be ``project(Second ...)``, so this will print::
+
+  CMAKE_PROJECT_SPDX_LICENSE = BSD-3-Clause AND CC-BY-SA-4.0
+
+To obtain the version from the most recent call to :command:`project` in
+the current directory scope or above, see the :variable:`PROJECT_SPDX_LICENSE`
+variable.
diff --git a/Help/variable/CMAKE_SOURCE_DIR.rst b/Help/variable/CMAKE_SOURCE_DIR.rst
index f1d1bee..be28f4d 100644
--- a/Help/variable/CMAKE_SOURCE_DIR.rst
+++ b/Help/variable/CMAKE_SOURCE_DIR.rst
@@ -11,3 +11,5 @@
 :variable:`CMAKE_BINARY_DIR`, ``CMAKE_SOURCE_DIR``,
 :variable:`CMAKE_CURRENT_BINARY_DIR` and
 :variable:`CMAKE_CURRENT_SOURCE_DIR` to the current working directory.
+
+Modifying ``CMAKE_SOURCE_DIR`` has undefined behavior.
diff --git a/Help/variable/PROJECT-NAME_SPDX_LICENSE.rst b/Help/variable/PROJECT-NAME_SPDX_LICENSE.rst
new file mode 100644
index 0000000..06c395d
--- /dev/null
+++ b/Help/variable/PROJECT-NAME_SPDX_LICENSE.rst
@@ -0,0 +1,11 @@
+<PROJECT-NAME>_SPDX_LICENSE
+---------------------------
+
+.. versionadded:: 4.2
+
+.. note::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
+
+Value given to the ``SPDX_LICENSE`` option of the most recent call to the
+:command:`project` command with project name ``<PROJECT-NAME>``, if any.
diff --git a/Help/variable/PROJECT_SPDX_LICENSE.rst b/Help/variable/PROJECT_SPDX_LICENSE.rst
new file mode 100644
index 0000000..4f62e94
--- /dev/null
+++ b/Help/variable/PROJECT_SPDX_LICENSE.rst
@@ -0,0 +1,12 @@
+PROJECT_SPDX_LICENSE
+--------------------
+
+.. versionadded:: 4.2
+
+.. note::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
+
+Value given to the ``SPDX_LICENSE`` option of the most recent call to the
+:command:`project` command, if any. To obtain the compatibility version of the
+top level project, see the :variable:`CMAKE_PROJECT_SPDX_LICENSE` variable.
diff --git a/Modules/CMakeFindBinUtils.cmake b/Modules/CMakeFindBinUtils.cmake
index 1948c63..fb63d8d 100644
--- a/Modules/CMakeFindBinUtils.cmake
+++ b/Modules/CMakeFindBinUtils.cmake
@@ -219,6 +219,11 @@
     list(PREPEND _CMAKE_LINKER_NAMES "armlink")
   endif()
 
+  if(EMSCRIPTEN)
+    list(PREPEND _CMAKE_AR_NAMES "emar")
+    list(PREPEND _CMAKE_RANLIB_NAMES "emranlib")
+  endif()
+
   list(APPEND _CMAKE_TOOL_VARS AR RANLIB STRIP LINKER NM OBJDUMP OBJCOPY READELF DLLTOOL ADDR2LINE TAPI)
 endif()
 
diff --git a/Modules/Platform/Emscripten-Clang-C.cmake b/Modules/Platform/Emscripten-Clang-C.cmake
new file mode 100644
index 0000000..1b6b2ab
--- /dev/null
+++ b/Modules/Platform/Emscripten-Clang-C.cmake
@@ -0,0 +1,3 @@
+include(Platform/Emscripten-Clang)
+
+__emscripten_clang(C)
diff --git a/Modules/Platform/Emscripten-Clang-CXX.cmake b/Modules/Platform/Emscripten-Clang-CXX.cmake
new file mode 100644
index 0000000..32b913e
--- /dev/null
+++ b/Modules/Platform/Emscripten-Clang-CXX.cmake
@@ -0,0 +1,3 @@
+include(Platform/Emscripten-Clang)
+
+__emscripten_clang(CXX)
diff --git a/Modules/Platform/Emscripten-Clang.cmake b/Modules/Platform/Emscripten-Clang.cmake
new file mode 100644
index 0000000..88cb197
--- /dev/null
+++ b/Modules/Platform/Emscripten-Clang.cmake
@@ -0,0 +1,17 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+include_guard()
+
+macro(__emscripten_clang lang)
+  set(CMAKE_SHARED_LIBRARY_SONAME_${lang}_FLAG "-Wl,-soname,")
+  set(CMAKE_SHARED_LIBRARY_CREATE_${lang}_FLAGS "-sSIDE_MODULE")
+
+  set(CMAKE_${lang}_USE_RESPONSE_FILE_FOR_LIBRARIES 1)
+  set(CMAKE_${lang}_USE_RESPONSE_FILE_FOR_OBJECTS 1)
+  set(CMAKE_${lang}_USE_RESPONSE_FILE_FOR_INCLUDES 1)
+  set(CMAKE_${lang}_COMPILE_OBJECT
+    "<CMAKE_${lang}_COMPILER> -c <SOURCE> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> -fPIC")
+  set(CMAKE_${lang}_LINK_EXECUTABLE
+    "<CMAKE_${lang}_COMPILER> <FLAGS> <CMAKE_${lang}_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES> -sMAIN_MODULE")
+endmacro()
diff --git a/Modules/Platform/Emscripten-Initialize.cmake b/Modules/Platform/Emscripten-Initialize.cmake
new file mode 100644
index 0000000..3117e05
--- /dev/null
+++ b/Modules/Platform/Emscripten-Initialize.cmake
@@ -0,0 +1,2 @@
+set(EMSCRIPTEN 1)
+set(UNIX 1)
diff --git a/Modules/Platform/Emscripten.cmake b/Modules/Platform/Emscripten.cmake
new file mode 100644
index 0000000..8157d47
--- /dev/null
+++ b/Modules/Platform/Emscripten.cmake
@@ -0,0 +1,6 @@
+set(CMAKE_SHARED_LIBRARY_LINK_C_WITH_RUNTIME_PATH ON)
+
+set(CMAKE_SHARED_LIBRARY_SUFFIX ".wasm")
+set(CMAKE_EXECUTABLE_SUFFIX ".js")
+
+set(CMAKE_DL_LIBS "")
diff --git a/Modules/Platform/Linker/Emscripten-C.cmake b/Modules/Platform/Linker/Emscripten-C.cmake
new file mode 100644
index 0000000..dfef6a3
--- /dev/null
+++ b/Modules/Platform/Linker/Emscripten-C.cmake
@@ -0,0 +1,4 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+include(Platform/Linker/Emscripten-LLD-C)
diff --git a/Modules/Platform/Linker/Emscripten-CXX.cmake b/Modules/Platform/Linker/Emscripten-CXX.cmake
new file mode 100644
index 0000000..90bb820
--- /dev/null
+++ b/Modules/Platform/Linker/Emscripten-CXX.cmake
@@ -0,0 +1,4 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+include(Platform/Linker/Emscripten-LLD-CXX)
diff --git a/Modules/Platform/Linker/Emscripten-LLD-C.cmake b/Modules/Platform/Linker/Emscripten-LLD-C.cmake
new file mode 100644
index 0000000..448d7cc
--- /dev/null
+++ b/Modules/Platform/Linker/Emscripten-LLD-C.cmake
@@ -0,0 +1,6 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+include(Platform/Linker/Emscripten-LLD)
+
+__emscripten_linker_lld(C)
diff --git a/Modules/Platform/Linker/Emscripten-LLD-CXX.cmake b/Modules/Platform/Linker/Emscripten-LLD-CXX.cmake
new file mode 100644
index 0000000..63d71b4
--- /dev/null
+++ b/Modules/Platform/Linker/Emscripten-LLD-CXX.cmake
@@ -0,0 +1,6 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+include(Platform/Linker/Emscripten-LLD)
+
+__emscripten_linker_lld(CXX)
diff --git a/Modules/Platform/Linker/Emscripten-LLD.cmake b/Modules/Platform/Linker/Emscripten-LLD.cmake
new file mode 100644
index 0000000..4c02cf7
--- /dev/null
+++ b/Modules/Platform/Linker/Emscripten-LLD.cmake
@@ -0,0 +1,11 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+# This module is shared by multiple languages; use include blocker.
+include_guard()
+
+macro(__emscripten_linker_lld lang)
+  set(CMAKE_${lang}_LINK_LIBRARY_USING_WHOLE_ARCHIVE "-Wl,--whole-archive" "<LINK_ITEM>" "-Wl,--no-whole-archive")
+  set(CMAKE_${lang}_LINK_LIBRARY_USING_WHOLE_ARCHIVE_SUPPORTED TRUE)
+  set(CMAKE_${lang}_LINK_LIBRARY_WHOLE_ARCHIVE_ATTRIBUTES LIBRARY_TYPE=STATIC DEDUPLICATION=YES OVERRIDE=DEFAULT)
+endmacro()
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index b1c8290..7885af0 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -717,6 +717,9 @@
   cmSiteNameCommand.h
   cmSourceGroupCommand.cxx
   cmSourceGroupCommand.h
+  cmSPDXSerializer.cxx
+  cmSPDXSerializer.h
+  cmSPDXTypes.def
   cmString.cxx
   cmString.hxx
   cmStringReplaceHelper.cxx
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 2103501..363e6a6 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 1)
-set(CMake_VERSION_PATCH 0)
-set(CMake_VERSION_RC 1)
+set(CMake_VERSION_PATCH 20250701)
+#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/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx
index eb8f616..4a2f163 100644
--- a/Source/cmCoreTryCompile.cxx
+++ b/Source/cmCoreTryCompile.cxx
@@ -1386,6 +1386,14 @@
     return;
   }
 
+  if (cmHasLiteralSuffix(outputFileLocation, ".js")) {
+    std::string wasmOutputLocation = cmStrCat(
+      outputFileLocation.substr(0, outputFileLocation.length() - 3), ".wasm");
+    if (cmSystemTools::FileExists(wasmOutputLocation)) {
+      outputFileLocation = wasmOutputLocation;
+    }
+  }
+
   this->OutputFile = cmSystemTools::CollapseFullPath(outputFileLocation);
 }
 
diff --git a/Source/cmExportPackageInfoGenerator.cxx b/Source/cmExportPackageInfoGenerator.cxx
index f2fbc28..d38c640 100644
--- a/Source/cmExportPackageInfoGenerator.cxx
+++ b/Source/cmExportPackageInfoGenerator.cxx
@@ -39,6 +39,8 @@
   , PackageVersionSchema(std::move(arguments.VersionSchema))
   , PackageDescription(std::move(arguments.Description))
   , PackageWebsite(std::move(arguments.Website))
+  , PackageLicense(std::move(arguments.License))
+  , DefaultLicense(std::move(arguments.DefaultLicense))
   , DefaultTargets(std::move(arguments.DefaultTargets))
   , DefaultConfigurations(std::move(arguments.DefaultConfigs))
 {
@@ -127,7 +129,8 @@
 
   SetProperty(package, "description", this->PackageDescription);
   SetProperty(package, "website", this->PackageWebsite);
-  // TODO: license
+  SetProperty(package, "license", this->PackageLicense);
+  SetProperty(package, "default_license", this->DefaultLicense);
 
   return package;
 }
diff --git a/Source/cmExportPackageInfoGenerator.h b/Source/cmExportPackageInfoGenerator.h
index fb7d79f..83cb6ad 100644
--- a/Source/cmExportPackageInfoGenerator.h
+++ b/Source/cmExportPackageInfoGenerator.h
@@ -110,6 +110,8 @@
   std::string const PackageVersionSchema;
   std::string const PackageDescription;
   std::string const PackageWebsite;
+  std::string const PackageLicense;
+  std::string const DefaultLicense;
   std::vector<std::string> DefaultTargets;
   std::vector<std::string> DefaultConfigurations;
 
diff --git a/Source/cmFindPackageCommand.h b/Source/cmFindPackageCommand.h
index 10448a8..4d5da75 100644
--- a/Source/cmFindPackageCommand.h
+++ b/Source/cmFindPackageCommand.h
@@ -334,10 +334,10 @@
 
   class FlushDebugBufferOnExit;
 
-  /*! the selected sortOrder (None by default)*/
-  SortOrderType SortOrder = None;
-  /*! the selected sortDirection (Asc by default)*/
-  SortDirectionType SortDirection = Asc;
+  /*! the selected sortOrder (Natural by default)*/
+  SortOrderType SortOrder = Natural;
+  /*! the selected sortDirection (Dec by default)*/
+  SortDirectionType SortDirection = Dec;
 
   struct ConfigFileInfo
   {
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index 90bb06b..3c2e7ec 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/Source/cmGeneratorExpressionNode.cxx
@@ -2561,6 +2561,146 @@
   }
 } linkLanguageAndIdNode;
 
+struct CompilerLinkerIdNode : public cmGeneratorExpressionNode
+{
+  CompilerLinkerIdNode(char const* lang)
+    : Language(lang)
+  {
+  }
+
+  int NumExpectedParameters() const override { return ZeroOrMoreParameters; }
+
+  std::string Evaluate(
+    std::vector<std::string> const& parameters,
+    cmGeneratorExpressionContext* context,
+    GeneratorExpressionContent const* content,
+    cmGeneratorExpressionDAGChecker* dagChecker) const override
+  {
+    if (!context->HeadTarget) {
+      reportError(
+        context, content->GetOriginalExpression(),
+        cmStrCat(
+          "$<", this->Language,
+          "_COMPILER_LINKER_ID> may only be used with binary targets. It may "
+          "not be used with add_custom_command or add_custom_target."));
+      return {};
+    }
+    return this->EvaluateWithLanguage(parameters, context, content, dagChecker,
+                                      this->Language);
+  }
+
+  std::string EvaluateWithLanguage(std::vector<std::string> const& parameters,
+                                   cmGeneratorExpressionContext* context,
+                                   GeneratorExpressionContent const* content,
+                                   cmGeneratorExpressionDAGChecker* /*unused*/,
+                                   std::string const& lang) const
+  {
+    std::string const& compilerLinkerId =
+      context->LG->GetMakefile()->GetSafeDefinition(
+        cmStrCat("CMAKE_", lang, "_COMPILER_LINKER_ID"));
+    if (parameters.empty()) {
+      return compilerLinkerId;
+    }
+    if (compilerLinkerId.empty()) {
+      return parameters.front().empty() ? "1" : "0";
+    }
+    static cmsys::RegularExpression compilerLinkerIdValidator(
+      "^[A-Za-z0-9_]*$");
+
+    for (auto const& param : parameters) {
+      if (!compilerLinkerIdValidator.find(param)) {
+        reportError(context, content->GetOriginalExpression(),
+                    "Expression syntax not recognized.");
+        return std::string();
+      }
+
+      if (param == compilerLinkerId) {
+        return "1";
+      }
+    }
+    return "0";
+  }
+
+  char const* const Language;
+};
+
+static CompilerLinkerIdNode const cCompilerLinkerIdNode("C"),
+  cxxCompilerLinkerIdNode("CXX"), cudaCompilerLinkerIdNode("CUDA"),
+  objcCompilerLinkerIdNode("OBJC"), objcxxCompilerLinkerIdNode("OBJCXX"),
+  fortranCompilerLinkerIdNode("Fortran"), hipCompilerLinkerIdNode("HIP");
+
+struct CompilerLinkerFrontendVariantNode : public cmGeneratorExpressionNode
+{
+  CompilerLinkerFrontendVariantNode(char const* lang)
+    : Language(lang)
+  {
+  }
+
+  int NumExpectedParameters() const override { return ZeroOrMoreParameters; }
+
+  std::string Evaluate(
+    std::vector<std::string> const& parameters,
+    cmGeneratorExpressionContext* context,
+    GeneratorExpressionContent const* content,
+    cmGeneratorExpressionDAGChecker* dagChecker) const override
+  {
+    if (!context->HeadTarget) {
+      reportError(
+        context, content->GetOriginalExpression(),
+        cmStrCat(
+          "$<", this->Language,
+          "_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary "
+          "targets. It may not be used with add_custom_command or "
+          "add_custom_target."));
+      return {};
+    }
+    return this->EvaluateWithLanguage(parameters, context, content, dagChecker,
+                                      this->Language);
+  }
+
+  std::string EvaluateWithLanguage(std::vector<std::string> const& parameters,
+                                   cmGeneratorExpressionContext* context,
+                                   GeneratorExpressionContent const* content,
+                                   cmGeneratorExpressionDAGChecker* /*unused*/,
+                                   std::string const& lang) const
+  {
+    std::string const& compilerLinkerFrontendVariant =
+      context->LG->GetMakefile()->GetSafeDefinition(
+        cmStrCat("CMAKE_", lang, "_COMPILER_LINKER_FRONTEND_VARIANT"));
+    if (parameters.empty()) {
+      return compilerLinkerFrontendVariant;
+    }
+    if (compilerLinkerFrontendVariant.empty()) {
+      return parameters.front().empty() ? "1" : "0";
+    }
+    static cmsys::RegularExpression compilerLinkerFrontendVariantValidator(
+      "^[A-Za-z0-9_]*$");
+
+    for (auto const& param : parameters) {
+      if (!compilerLinkerFrontendVariantValidator.find(param)) {
+        reportError(context, content->GetOriginalExpression(),
+                    "Expression syntax not recognized.");
+        return {};
+      }
+      if (param == compilerLinkerFrontendVariant) {
+        return "1";
+      }
+    }
+    return "0";
+  }
+
+  char const* const Language;
+};
+
+static CompilerLinkerFrontendVariantNode const
+  cCompilerLinkerFrontendVariantNode("C"),
+  cxxCompilerLinkerFrontendVariantNode("CXX"),
+  cudaCompilerLinkerFrontendVariantNode("CUDA"),
+  objcCompilerLinkerFrontendVariantNode("OBJC"),
+  objcxxCompilerLinkerFrontendVariantNode("OBJCXX"),
+  fortranCompilerLinkerFrontendVariantNode("Fortran"),
+  hipCompilerLinkerFrontendVariantNode("HIP");
+
 static const struct LinkLibraryNode : public cmGeneratorExpressionNode
 {
   LinkLibraryNode() {} // NOLINT(modernize-use-equals-default)
@@ -4641,6 +4781,27 @@
     { "COMPILE_LANGUAGE", &languageNode },
     { "LINK_LANG_AND_ID", &linkLanguageAndIdNode },
     { "LINK_LANGUAGE", &linkLanguageNode },
+    { "C_COMPILER_LINKER_ID", &cCompilerLinkerIdNode },
+    { "CXX_COMPILER_LINKER_ID", &cxxCompilerLinkerIdNode },
+    { "OBJC_COMPILER_LINKER_ID", &objcCompilerLinkerIdNode },
+    { "OBJCXX_COMPILER_LINKER_ID", &objcxxCompilerLinkerIdNode },
+    { "CUDA_COMPILER_LINKER_ID", &cudaCompilerLinkerIdNode },
+    { "Fortran_COMPILER_LINKER_ID", &fortranCompilerLinkerIdNode },
+    { "HIP_COMPILER_LINKER_ID", &hipCompilerLinkerIdNode },
+    { "C_COMPILER_LINKER_FRONTEND_VARIANT",
+      &cCompilerLinkerFrontendVariantNode },
+    { "CXX_COMPILER_LINKER_FRONTEND_VARIANT",
+      &cxxCompilerLinkerFrontendVariantNode },
+    { "CUDA_COMPILER_LINKER_FRONTEND_VARIANT",
+      &cudaCompilerLinkerFrontendVariantNode },
+    { "OBJC_COMPILER_LINKER_FRONTEND_VARIANT",
+      &objcCompilerLinkerFrontendVariantNode },
+    { "OBJCXX_COMPILER_LINKER_FRONTEND_VARIANT",
+      &objcxxCompilerLinkerFrontendVariantNode },
+    { "Fortran_COMPILER_LINKER_FRONTEND_VARIANT",
+      &fortranCompilerLinkerFrontendVariantNode },
+    { "HIP_COMPILER_LINKER_FRONTEND_VARIANT",
+      &hipCompilerLinkerFrontendVariantNode },
     { "LINK_LIBRARY", &linkLibraryNode },
     { "LINK_GROUP", &linkGroupNode },
     { "HOST_LINK", &hostLinkNode },
diff --git a/Source/cmGeneratorTarget_Sources.cxx b/Source/cmGeneratorTarget_Sources.cxx
index fa27998..ed002e4 100644
--- a/Source/cmGeneratorTarget_Sources.cxx
+++ b/Source/cmGeneratorTarget_Sources.cxx
@@ -96,10 +96,11 @@
   }
   cmake* cm = headTarget->GetLocalGenerator()->GetCMakeInstance();
   for (auto& entryCge : fileSet->CompileFileEntries()) {
-    auto tpe = cmGeneratorTarget::TargetPropertyEntry::CreateFileSet(
-      dirs, contextSensitiveDirs, std::move(entryCge), fileSet);
-    entries.Entries.emplace_back(
-      EvaluateTargetPropertyEntry(headTarget, config, "", dagChecker, *tpe));
+    auto targetPropEntry =
+      cmGeneratorTarget::TargetPropertyEntry::CreateFileSet(
+        dirs, contextSensitiveDirs, std::move(entryCge), fileSet);
+    entries.Entries.emplace_back(EvaluateTargetPropertyEntry(
+      headTarget, config, "", dagChecker, *targetPropEntry));
     EvaluatedTargetPropertyEntry const& entry = entries.Entries.back();
     for (auto const& file : entry.Values) {
       auto* sf = headTarget->Makefile->GetOrCreateSource(file);
diff --git a/Source/cmGlobalVisualStudio14Generator.cxx b/Source/cmGlobalVisualStudio14Generator.cxx
index 5d4ebbd..55dcf05 100644
--- a/Source/cmGlobalVisualStudio14Generator.cxx
+++ b/Source/cmGlobalVisualStudio14Generator.cxx
@@ -57,7 +57,7 @@
   cmDocumentationEntry GetDocumentation() const override
   {
     return { std::string(vs14generatorName),
-             "Generates Visual Studio 2015 project files.  "
+             "Deprecated.  Generates Visual Studio 2015 project files.  "
              "Use -A option to specify architecture." };
   }
 
diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx
index c875be0..83eff0d 100644
--- a/Source/cmGlobalVisualStudio7Generator.cxx
+++ b/Source/cmGlobalVisualStudio7Generator.cxx
@@ -308,6 +308,26 @@
     this->CallVisualStudioMacro(MacroReload,
                                 GetSLNFile(this->LocalGenerators[0].get()));
   }
+
+  if (this->Version == VSVersion::VS14 &&
+      !this->CMakeInstance->GetIsInTryCompile()) {
+    std::string cmakeWarnVS14;
+    if (cmValue cached = this->CMakeInstance->GetState()->GetCacheEntryValue(
+          "CMAKE_WARN_VS14")) {
+      this->CMakeInstance->MarkCliAsUsed("CMAKE_WARN_VS14");
+      cmakeWarnVS14 = *cached;
+    } else {
+      cmSystemTools::GetEnv("CMAKE_WARN_VS14", cmakeWarnVS14);
+    }
+    if (cmakeWarnVS14.empty() || !cmIsOff(cmakeWarnVS14)) {
+      this->CMakeInstance->IssueMessage(
+        MessageType::WARNING,
+        "The \"Visual Studio 14 2015\" generator is deprecated "
+        "and will be removed in a future version of CMake."
+        "\n"
+        "Add CMAKE_WARN_VS14=OFF to the cache to disable this warning.");
+    }
+  }
 }
 
 void cmGlobalVisualStudio7Generator::OutputSLNFile(
diff --git a/Source/cmPackageInfoArguments.cxx b/Source/cmPackageInfoArguments.cxx
index 078f63d..d60e08c 100644
--- a/Source/cmPackageInfoArguments.cxx
+++ b/Source/cmPackageInfoArguments.cxx
@@ -60,6 +60,8 @@
     ENFORCE_REQUIRES("PACKAGE_INFO", this->LowerCase, "LOWER_CASE_FILE");
     ENFORCE_REQUIRES("PACKAGE_INFO", this->Appendix, "APPENDIX");
     ENFORCE_REQUIRES("PACKAGE_INFO", this->Version, "VERSION");
+    ENFORCE_REQUIRES("PACKAGE_INFO", this->License, "LICENSE");
+    ENFORCE_REQUIRES("PACKAGE_INFO", this->DefaultLicense, "DEFAULT_LICENSE");
     ENFORCE_REQUIRES("PACKAGE_INFO", this->Description, "DESCRIPTION");
     ENFORCE_REQUIRES("PACKAGE_INFO", this->Website, "HOMEPAGE_URL");
     ENFORCE_REQUIRES("PACKAGE_INFO", this->DefaultTargets, "DEFAULT_TARGETS");
@@ -73,6 +75,7 @@
   // Check for incompatible options.
   if (!this->Appendix.empty()) {
     ENFORCE_EXCLUSIVE("APPENDIX", this->Version, "VERSION");
+    ENFORCE_EXCLUSIVE("APPENDIX", this->License, "LICENSE");
     ENFORCE_EXCLUSIVE("APPENDIX", this->Description, "DESCRIPTION");
     ENFORCE_EXCLUSIVE("APPENDIX", this->Website, "HOMEPAGE_URL");
     ENFORCE_EXCLUSIVE("APPENDIX", this->DefaultTargets, "DEFAULT_TARGETS");
@@ -136,6 +139,10 @@
     }
   }
 
+  if (this->License.empty()) {
+    mapProjectValue(this->License, "SPDX_LICENSE"_s);
+  }
+
   if (this->Description.empty()) {
     mapProjectValue(this->Description, "DESCRIPTION"_s);
   }
diff --git a/Source/cmPackageInfoArguments.h b/Source/cmPackageInfoArguments.h
index a002197..9134bc1 100644
--- a/Source/cmPackageInfoArguments.h
+++ b/Source/cmPackageInfoArguments.h
@@ -56,6 +56,8 @@
   ArgumentParser::NonEmpty<std::string> Version;
   ArgumentParser::NonEmpty<std::string> VersionCompat;
   ArgumentParser::NonEmpty<std::string> VersionSchema;
+  ArgumentParser::NonEmpty<std::string> License;
+  ArgumentParser::NonEmpty<std::string> DefaultLicense;
   ArgumentParser::NonEmpty<std::string> Description;
   ArgumentParser::NonEmpty<std::string> Website;
   ArgumentParser::NonEmpty<std::vector<std::string>> DefaultTargets;
@@ -84,6 +86,9 @@
          &cmPackageInfoArguments::DefaultTargets);
     Bind(self, parser, "DEFAULT_CONFIGURATIONS"_s,
          &cmPackageInfoArguments::DefaultConfigs);
+    Bind(self, parser, "LICENSE"_s, &cmPackageInfoArguments::License);
+    Bind(self, parser, "DEFAULT_LICENSE"_s,
+         &cmPackageInfoArguments::DefaultLicense);
     Bind(self, parser, "DESCRIPTION"_s, &cmPackageInfoArguments::Description);
     Bind(self, parser, "HOMEPAGE_URL"_s, &cmPackageInfoArguments::Website);
 
diff --git a/Source/cmPackageInfoReader.cxx b/Source/cmPackageInfoReader.cxx
index dd6a13c..cffd9be 100644
--- a/Source/cmPackageInfoReader.cxx
+++ b/Source/cmPackageInfoReader.cxx
@@ -479,6 +479,14 @@
     }
   }
 
+  // Check for a default license.
+  Json::Value const& defaultLicense = reader->Data["default_license"];
+  if (!defaultLicense.isNull()) {
+    reader->DefaultLicense = defaultLicense.asString();
+  } else if (parent) {
+    reader->DefaultLicense = parent->DefaultLicense;
+  }
+
   return reader;
 }
 
@@ -582,12 +590,14 @@
   }
 }
 
-void cmPackageInfoReader::SetMetaProperty(cmTarget* target,
-                                          cm::string_view property,
-                                          Json::Value const& value) const
+void cmPackageInfoReader::SetMetaProperty(
+  cmTarget* target, cm::string_view property, Json::Value const& value,
+  std::string const& defaultValue) const
 {
   if (!value.isNull()) {
     target->SetProperty(property.data(), value.asString());
+  } else if (!defaultValue.empty()) {
+    target->SetProperty(property.data(), defaultValue);
   }
 }
 
@@ -671,7 +681,8 @@
 
   // Add other information.
   if (configuration.empty()) {
-    this->SetMetaProperty(target, "SPDX_LICENSE"_s, data["license"]);
+    this->SetMetaProperty(target, "SPDX_LICENSE"_s, data["license"],
+                          this->DefaultLicense);
   }
 }
 
diff --git a/Source/cmPackageInfoReader.h b/Source/cmPackageInfoReader.h
index 3173f66..4597e83 100644
--- a/Source/cmPackageInfoReader.h
+++ b/Source/cmPackageInfoReader.h
@@ -98,7 +98,8 @@
                          cm::string_view configuration,
                          Json::Value const& value) const;
   void SetMetaProperty(cmTarget* target, cm::string_view property,
-                       Json::Value const& value) const;
+                       Json::Value const& value,
+                       std::string const& defaultValue = {}) const;
 
   std::string ResolvePath(std::string path) const;
 
@@ -108,4 +109,5 @@
 
   std::map<std::string, cmTarget*> ComponentTargets;
   std::vector<std::string> DefaultConfigurations;
+  std::string DefaultLicense;
 };
diff --git a/Source/cmProjectCommand.cxx b/Source/cmProjectCommand.cxx
index 45efaa5..0835b12 100644
--- a/Source/cmProjectCommand.cxx
+++ b/Source/cmProjectCommand.cxx
@@ -38,6 +38,7 @@
   cm::optional<std::string> ProjectName;
   cm::optional<std::string> Version;
   cm::optional<std::string> CompatVersion;
+  cm::optional<std::string> License;
   cm::optional<std::string> Description;
   cm::optional<std::string> HomepageURL;
   cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Languages;
@@ -73,11 +74,12 @@
     .Bind("LANGUAGES"_s, prArgs.Languages);
 
   cmMakefile& mf = status.GetMakefile();
-  bool enableCompatVersion = cmExperimental::HasSupportEnabled(
+  bool enablePackageInfo = cmExperimental::HasSupportEnabled(
     mf, cmExperimental::Feature::ExportPackageInfo);
 
-  if (enableCompatVersion) {
+  if (enablePackageInfo) {
     parser.Bind("COMPAT_VERSION"_s, prArgs.CompatVersion);
+    parser.Bind("SPDX_LICENSE"_s, prArgs.License);
   }
 
   parser.Parse(args, &unparsedArgs, 0);
@@ -263,6 +265,7 @@
   createVariables("VERSION_PATCH"_s, version_components[2]);
   createVariables("VERSION_TWEAK"_s, version_components[3]);
   createVariables("COMPAT_VERSION"_s, prArgs.CompatVersion.value_or(""));
+  createVariables("SPDX_LICENSE"_s, prArgs.License.value_or(""));
   createVariables("DESCRIPTION"_s, prArgs.Description.value_or(""));
   createVariables("HOMEPAGE_URL"_s, prArgs.HomepageURL.value_or(""));
 
diff --git a/Source/cmSPDXSerializer.cxx b/Source/cmSPDXSerializer.cxx
new file mode 100644
index 0000000..7d22103
--- /dev/null
+++ b/Source/cmSPDXSerializer.cxx
@@ -0,0 +1,1375 @@
+/* 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
new file mode 100644
index 0000000..65bf214
--- /dev/null
+++ b/Source/cmSPDXSerializer.h
@@ -0,0 +1,1008 @@
+/* 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
new file mode 100644
index 0000000..88d8043
--- /dev/null
+++ b/Source/cmSPDXTypes.def
@@ -0,0 +1,54 @@
+/* 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/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 3f6ae04..7f35f04 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -24,6 +24,7 @@
   testRange.cxx
   testOptional.cxx
   testPathResolver.cxx
+  testSPDXSerializer.cxx
   testStdIo.cxx
   testString.cxx
   testStringAlgorithms.cxx
diff --git a/Tests/CMakeLib/testSPDXSerializer.cxx b/Tests/CMakeLib/testSPDXSerializer.cxx
new file mode 100644
index 0000000..b8ab923
--- /dev/null
+++ b/Tests/CMakeLib/testSPDXSerializer.cxx
@@ -0,0 +1,649 @@
+/* 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/CMakeLists.txt b/Tests/CMakeLists.txt
index 15a1e2c..2d40fd0 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -50,6 +50,13 @@
 ")
 endif()
 
+# Suppress generator deprecation warnings in test suite.
+if(CMAKE_GENERATOR MATCHES "^Visual Studio 14 2015")
+  set(TEST_WARN_VS_CODE "set(ENV{CMAKE_WARN_VS14} OFF)")
+else()
+  set(TEST_WARN_VS_CODE "")
+endif()
+
 # 3.9 or later provides a definitive answer to whether we are multi-config
 # through a global property. Prior to 3.9, CMAKE_CONFIGURATION_TYPES being set
 # is assumed to mean multi-config, but developers might modify it so it is
diff --git a/Tests/CTestUpdateGIT.cmake.in b/Tests/CTestUpdateGIT.cmake.in
index 5e3448b..4921304 100644
--- a/Tests/CTestUpdateGIT.cmake.in
+++ b/Tests/CTestUpdateGIT.cmake.in
@@ -19,6 +19,8 @@
 set(AUTHOR_CONFIG "[user]
 \tname = Test Author
 \temail = testauthor@cmake.org
+[commit]
+\tgpgsign = false
 ")
 
 #-----------------------------------------------------------------------------
diff --git a/Tests/ExternalProjectUpdate/CMakeLists.txt b/Tests/ExternalProjectUpdate/CMakeLists.txt
index cbddd2f..2339d30 100644
--- a/Tests/ExternalProjectUpdate/CMakeLists.txt
+++ b/Tests/ExternalProjectUpdate/CMakeLists.txt
@@ -95,6 +95,7 @@
     GIT_TAG ${TEST_GIT_TAG}
     GIT_CONFIG "user.email=testauthor@cmake.org"
                "user.name=testauthor"
+               "commit.gpgsign=false"
     CMAKE_GENERATOR "${CMAKE_GENERATOR}"
     CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
     INSTALL_COMMAND ""
diff --git a/Tests/FindPackageCMakeTest/CMakeLists.txt b/Tests/FindPackageCMakeTest/CMakeLists.txt
index bd1887a..37a63eb 100644
--- a/Tests/FindPackageCMakeTest/CMakeLists.txt
+++ b/Tests/FindPackageCMakeTest/CMakeLists.txt
@@ -117,7 +117,7 @@
 endforeach()
 
 # Enable framework and bundle searching.  Make sure bundles are found
-# before unix-syle packages.
+# before unix-style packages.
 set(CMAKE_FIND_FRAMEWORK LAST)
 set(CMAKE_FIND_APPBUNDLE FIRST)
 
@@ -584,6 +584,17 @@
 unset(SortLib_VERSION)
 
 
+set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+# Expected to default to 'NATURAL' and 'DEC'
+unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
+unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
+FIND_PACKAGE(SortLib CONFIG)
+IF (NOT "${SortLib_VERSION}" STREQUAL "3.10.1")
+  message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Default! ${SortLib_VERSION}")
+endif()
+unset(SortLib_VERSION)
+
+
 set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 SET(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
 SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
diff --git a/Tests/FindPackageCpsTest/CMakeLists.txt b/Tests/FindPackageCpsTest/CMakeLists.txt
index 574f5a7..43caab4 100644
--- a/Tests/FindPackageCpsTest/CMakeLists.txt
+++ b/Tests/FindPackageCpsTest/CMakeLists.txt
@@ -90,6 +90,7 @@
   unset(SortLib_VERSION)
 endforeach()
 
+
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
 set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
@@ -100,6 +101,7 @@
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 unset(SortLib_VERSION)
 
+
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 FIND_PACKAGE(SortLib 4.0 CONFIG)
 IF (NOT "${SortLib_VERSION}" STREQUAL "4.0.0")
@@ -107,6 +109,18 @@
 endif()
 unset(SortLib_VERSION)
 
+
+set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+# Expected to default to 'NATURAL' and 'DEC'
+unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
+unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
+FIND_PACKAGE(SortLib CONFIG)
+IF (NOT "${SortLib_VERSION}" STREQUAL "3.10.1")
+  message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Default! ${SortLib_VERSION}")
+endif()
+unset(SortLib_VERSION)
+
+
 set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 set(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
 set(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
@@ -117,6 +131,7 @@
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 unset(SortFramework_VERSION)
 
+
 set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
 set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
@@ -127,6 +142,7 @@
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
 unset(SortFramework_VERSION)
 
+
 unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
 unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
 
diff --git a/Tests/RunCMake/CMP0150/CMakeLists.txt b/Tests/RunCMake/CMP0150/CMakeLists.txt
index 371dccc..29a2e04 100644
--- a/Tests/RunCMake/CMP0150/CMakeLists.txt
+++ b/Tests/RunCMake/CMP0150/CMakeLists.txt
@@ -20,6 +20,7 @@
   execGitCommand("${workDir}" config user.email "testauthor@cmake.org")
   execGitCommand("${workDir}" config user.name testauthor)
   execGitCommand("${workDir}" config core.autocrlf false)
+  execGitCommand("${workDir}" config commit.gpgsign false)
   execGitCommand("${workDir}" add CMakeLists.txt)
   execGitCommand("${workDir}" commit -m "Initial commit")
 endfunction()
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index c92aac1..b2eb35c 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -521,7 +521,8 @@
 add_RunCMake_test(GenEx-PATH)
 add_RunCMake_test(GenEx-PATH_EQUAL)
 add_RunCMake_test(GenEx-LIST)
-add_RunCMake_test(GeneratorExpression)
+add_RunCMake_test(GeneratorExpression -DCMake_TEST_OBJC=${CMake_TEST_OBJC} -DCMake_TEST_Fortran=${CMake_TEST_Fortran}
+                                      -DCMake_TEST_CUDA=${CMake_TEST_CUDA} -DCMake_TEST_HIP=${CMake_TEST_HIP})
 add_RunCMake_test(GeneratorExpressionShortCircuit)
 add_RunCMake_test(GeneratorInstance)
 add_RunCMake_test(GeneratorPlatform)
@@ -1402,3 +1403,7 @@
   add_RunCMake_test(Renesas -DCMake_TEST_Renesas_TOOLCHAINS=${CMake_TEST_Renesas_TOOLCHAINS})
   set_property(TEST RunCMake.Renesas APPEND PROPERTY LABELS "Renesas")
 endif()
+if(CMake_TEST_Emscripten_TOOLCHAINS)
+  add_RunCMake_test(Emscripten -DCMake_TEST_Emscripten_TOOLCHAINS=${CMake_TEST_Emscripten_TOOLCHAINS})
+  set_property(TEST RunCMake.Emscripten APPEND PROPERTY LABELS "Emscripten")
+endif()
diff --git a/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-OFF.cmake b/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-OFF.cmake
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-OFF.cmake
diff --git a/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-ON-stderr.txt b/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-ON-stderr.txt
new file mode 100644
index 0000000..47caf4e
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-ON-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Warning:
+  The "Visual Studio 14 2015" generator is deprecated and will be removed in
+  a future version of CMake.
+
+  Add CMAKE_WARN_VS14=OFF to the cache to disable this warning.$
diff --git a/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-ON.cmake b/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-ON.cmake
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DeprecateVS14-WARN-ON.cmake
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 9fda9eb..944f902 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -1167,3 +1167,10 @@
 endif()
 set(ENV{CMAKE_CONFIG_DIR} cmake_config_dir)
 run_cmake_command(print-config-dir-env ${CMAKE_COMMAND} "--print-config-dir")
+
+if(RunCMake_GENERATOR MATCHES "^Visual Studio 14 2015")
+  run_cmake_with_options(DeprecateVS14-WARN-ON -DCMAKE_WARN_VS14=ON)
+  unset(ENV{CMAKE_WARN_VS14})
+  run_cmake(DeprecateVS14-WARN-ON)
+  run_cmake_with_options(DeprecateVS14-WARN-OFF -DCMAKE_WARN_VS14=OFF)
+endif()
diff --git a/Tests/RunCMake/Emscripten/CMakeLists.txt b/Tests/RunCMake/Emscripten/CMakeLists.txt
new file mode 100644
index 0000000..955802c
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 4.0)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/Emscripten/RunCMakeTest.cmake b/Tests/RunCMake/Emscripten/RunCMakeTest.cmake
new file mode 100644
index 0000000..efce630
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/RunCMakeTest.cmake
@@ -0,0 +1,66 @@
+include(RunCMake)
+
+# Locate Emscripten toolchain
+if(RunCMake_GENERATOR MATCHES "Makefile|Ninja")
+  file(GLOB _emscripten_toolchains
+    "${CMake_TEST_Emscripten_TOOLCHAINS}/emcc" )
+  if(_emscripten_toolchains STREQUAL "")
+    message(FATAL_ERROR "Could not find any Emscripten toolchains at: ${CMake_TEST_Emscripten_TOOLCHAINS}.")
+  endif()
+endif()
+
+function(run_toolchain case)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
+  run_cmake_with_options(${case} ${ARGN})
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(${case}-build ${CMAKE_COMMAND} --build .)
+endfunction()
+
+macro(run_cmake_target test subtest target)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(${test}-${subtest}-build ${CMAKE_COMMAND} --build . --target ${target} --config Release --verbose ${ARGN})
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+endmacro()
+
+foreach(_emscripten_toolchain IN LISTS _emscripten_toolchains)
+  message(STATUS "Found Emscripten toolchain: ${_emscripten_toolchain}")
+  cmake_path(GET _emscripten_toolchain PARENT_PATH BIN_DIR)
+
+  if (WIN32)
+    set(EMCC_SUFFIX ".bat")
+  else()
+    set(EMCC_SUFFIX "")
+  endif()
+
+  set(c_comp ${BIN_DIR}/emcc${EMCC_SUFFIX})
+  set(cxx_comp ${BIN_DIR}/em++${EMCC_SUFFIX})
+  set(comp_ar ${BIN_DIR}/emar${EMCC_SUFFIX})
+
+  # Create an executable from .c sources only.
+  run_toolchain(emscripten-c
+    -DCMAKE_SYSTEM_NAME=Emscripten
+    -DCMAKE_C_COMPILER=${c_comp}
+  )
+
+  # Create an executable from .c and .cxx sources.
+  run_toolchain(emscripten-cxx
+    -DCMAKE_SYSTEM_NAME=Emscripten
+    -DCMAKE_C_COMPILER=${c_comp}
+    -DCMAKE_CXX_COMPILER=${cxx_comp}
+  )
+
+  # Create a library and executable from .c sources.
+  run_toolchain(emscripten-lib
+    -DCMAKE_SYSTEM_NAME=Emscripten
+    -DCMAKE_C_COMPILER=${c_comp}
+  )
+
+  run_cmake_with_options(emscripten-WHOLE_ARCHIVE
+    -DCMAKE_SYSTEM_NAME=Emscripten
+    -DCMAKE_C_COMPILER=${c_comp}
+  )
+  run_cmake_target(emscripten-WHOLE_ARCHIVE link-exe main)
+  run_cmake_target(emscripten-WHOLE_ARCHIVE circular-exe main_circular)
+endforeach()
diff --git a/Tests/RunCMake/Emscripten/base.c b/Tests/RunCMake/Emscripten/base.c
new file mode 100644
index 0000000..ed769a0
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/base.c
@@ -0,0 +1,9 @@
+
+#if !defined(STATIC_BASE)
+#  if defined(_WIN32)
+__declspec(dllexport)
+#  endif
+#endif
+  void base(void)
+{
+}
diff --git a/Tests/RunCMake/Emscripten/circular1.c b/Tests/RunCMake/Emscripten/circular1.c
new file mode 100644
index 0000000..80ee413
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/circular1.c
@@ -0,0 +1,6 @@
+void circular2(void);
+
+void circular1(void)
+{
+  circular2();
+}
diff --git a/Tests/RunCMake/Emscripten/circular2.c b/Tests/RunCMake/Emscripten/circular2.c
new file mode 100644
index 0000000..751bab5
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/circular2.c
@@ -0,0 +1,7 @@
+
+void circular1(void);
+
+void circular2(void)
+{
+  circular1();
+}
diff --git a/Tests/RunCMake/Emscripten/emscripten-WHOLE_ARCHIVE.cmake b/Tests/RunCMake/Emscripten/emscripten-WHOLE_ARCHIVE.cmake
new file mode 100644
index 0000000..e826eae
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/emscripten-WHOLE_ARCHIVE.cmake
@@ -0,0 +1,20 @@
+
+enable_language(C)
+
+add_library(base STATIC base.c unref.c)
+target_compile_definitions(base PUBLIC STATIC_BASE)
+
+add_library(lib SHARED lib.c)
+target_link_libraries(lib PRIVATE "$<LINK_LIBRARY:WHOLE_ARCHIVE,base>")
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE lib)
+
+add_library(circular1 STATIC circular1.c)
+add_library(circular2 STATIC circular2.c)
+
+target_link_libraries(circular1 PRIVATE circular2)
+target_link_libraries(circular2 PRIVATE circular1)
+
+add_executable(main_circular main_circular.c)
+target_link_libraries(main_circular PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,circular1>)
diff --git a/Tests/RunCMake/Emscripten/emscripten-c.cmake b/Tests/RunCMake/Emscripten/emscripten-c.cmake
new file mode 100644
index 0000000..39325a7
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/emscripten-c.cmake
@@ -0,0 +1,2 @@
+enable_language(C)
+add_executable(exec-c module.c)
diff --git a/Tests/RunCMake/Emscripten/emscripten-cxx.cmake b/Tests/RunCMake/Emscripten/emscripten-cxx.cmake
new file mode 100644
index 0000000..9e00aa3
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/emscripten-cxx.cmake
@@ -0,0 +1,2 @@
+enable_language(CXX)
+add_executable(exec-cxx module.cxx)
diff --git a/Tests/RunCMake/Emscripten/emscripten-lib.cmake b/Tests/RunCMake/Emscripten/emscripten-lib.cmake
new file mode 100644
index 0000000..a215d02
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/emscripten-lib.cmake
@@ -0,0 +1,6 @@
+enable_language(C)
+add_library(emscripten-test-lib libmod.c)
+
+add_executable(exec-lib-c module.c)
+target_compile_definitions(exec-lib-c PRIVATE __USE_LIBFUN)
+target_link_libraries(exec-lib-c PRIVATE emscripten-test-lib)
diff --git a/Tests/RunCMake/Emscripten/lib.c b/Tests/RunCMake/Emscripten/lib.c
new file mode 100644
index 0000000..21f559c
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/lib.c
@@ -0,0 +1,15 @@
+
+#if !defined(STATIC_BASE)
+#  if defined(_WIN32)
+__declspec(dllimport)
+#  endif
+#endif
+  void base(void);
+
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+  void lib(void)
+{
+  base();
+}
diff --git a/Tests/RunCMake/Emscripten/libmod.c b/Tests/RunCMake/Emscripten/libmod.c
new file mode 100644
index 0000000..b0d3952
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/libmod.c
@@ -0,0 +1,4 @@
+int emscripten_libfun()
+{
+  return 42;
+}
diff --git a/Tests/RunCMake/Emscripten/main.c b/Tests/RunCMake/Emscripten/main.c
new file mode 100644
index 0000000..2e39bce
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/main.c
@@ -0,0 +1,18 @@
+
+#if defined(_WIN32)
+__declspec(dllimport)
+#endif
+  void lib(void);
+
+#if defined(_WIN32)
+__declspec(dllimport)
+#endif
+  void unref(void);
+
+int main(void)
+{
+  lib();
+  unref();
+
+  return 0;
+}
diff --git a/Tests/RunCMake/Emscripten/main_circular.c b/Tests/RunCMake/Emscripten/main_circular.c
new file mode 100644
index 0000000..16ebee6
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/main_circular.c
@@ -0,0 +1,9 @@
+
+void circular1(void);
+
+int main(void)
+{
+  circular1();
+
+  return 0;
+}
diff --git a/Tests/RunCMake/Emscripten/module.c b/Tests/RunCMake/Emscripten/module.c
new file mode 100644
index 0000000..62014fa
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/module.c
@@ -0,0 +1,15 @@
+#include "module.h"
+#if defined(__USE_LIBFUN)
+extern int emscripten_libfun();
+#endif
+
+int i;
+int main()
+{
+#if defined(__USE_LIBFUN)
+  i = emscripten_libfun();
+#else
+  i = INTERNAL;
+#endif
+  return i;
+}
diff --git a/Tests/RunCMake/Emscripten/module.cxx b/Tests/RunCMake/Emscripten/module.cxx
new file mode 100644
index 0000000..b4d46b1
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/module.cxx
@@ -0,0 +1,7 @@
+#include "module.h"
+int i;
+int main()
+{
+  i = INTERNAL;
+  return i;
+}
diff --git a/Tests/RunCMake/Emscripten/module.h b/Tests/RunCMake/Emscripten/module.h
new file mode 100644
index 0000000..a8a85a6
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/module.h
@@ -0,0 +1,12 @@
+#ifndef __MODULE_H__
+#define __MODULE_H__
+
+#if defined(__cplusplus)
+#  define INTERNAL 64
+#elif !defined(__cplusplus)
+#  define INTERNAL 32
+#else
+#  error "Unable to determine INTERNAL symbol."
+#endif
+
+#endif /* __MODULE_H__ */
diff --git a/Tests/RunCMake/Emscripten/unref.c b/Tests/RunCMake/Emscripten/unref.c
new file mode 100644
index 0000000..11922de
--- /dev/null
+++ b/Tests/RunCMake/Emscripten/unref.c
@@ -0,0 +1,8 @@
+
+
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+  void unref(void)
+{
+}
diff --git a/Tests/RunCMake/ExportPackageInfo/Appendix-check.cmake b/Tests/RunCMake/ExportPackageInfo/Appendix-check.cmake
index c4f617f..8d55625 100644
--- a/Tests/RunCMake/ExportPackageInfo/Appendix-check.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/Appendix-check.cmake
@@ -5,11 +5,13 @@
 file(READ "${out_dir}/foo.cps" content)
 expect_value("${content}" "foo" "name")
 expect_value("${content}" "interface" "components" "mammal" "type")
+expect_value("${content}" "LGPL-3.0-or-later" "default_license")
 expect_value("${content}" "1.0" "version")
 
 file(READ "${out_dir}/foo-dog.cps" content)
 expect_value("${content}" "foo" "name")
 expect_value("${content}" "interface" "components" "canine" "type")
+expect_value("${content}" "GPL-3.0-or-later" "default_license")
 expect_missing("${content}" "version")
 
 expect_array("${content}" 1 "components" "canine" "requires")
diff --git a/Tests/RunCMake/ExportPackageInfo/Appendix.cmake b/Tests/RunCMake/ExportPackageInfo/Appendix.cmake
index 153231f..51a3c62 100644
--- a/Tests/RunCMake/ExportPackageInfo/Appendix.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/Appendix.cmake
@@ -5,5 +5,14 @@
 install(TARGETS mammal EXPORT mammal DESTINATION .)
 install(TARGETS canine EXPORT canine DESTINATION .)
 
-export(EXPORT mammal PACKAGE_INFO foo VERSION 1.0)
-export(EXPORT canine PACKAGE_INFO foo APPENDIX dog)
+export(
+  EXPORT mammal
+  PACKAGE_INFO foo
+  VERSION 1.0
+  DEFAULT_LICENSE "LGPL-3.0-or-later")
+
+export(
+  EXPORT canine
+  PACKAGE_INFO foo
+  APPENDIX dog
+  DEFAULT_LICENSE "GPL-3.0-or-later")
diff --git a/Tests/RunCMake/ExportPackageInfo/BadArgs2-stderr.txt b/Tests/RunCMake/ExportPackageInfo/BadArgs2-stderr.txt
index cc81af4..a811229 100644
--- a/Tests/RunCMake/ExportPackageInfo/BadArgs2-stderr.txt
+++ b/Tests/RunCMake/ExportPackageInfo/BadArgs2-stderr.txt
@@ -5,6 +5,12 @@
 
 
 CMake Error at BadArgs2\.cmake:[0-9]+ \(export\):
+  export APPENDIX and LICENSE are mutually exclusive\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs2\.cmake:[0-9]+ \(export\):
   export APPENDIX and DESCRIPTION are mutually exclusive\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:3 \(include\)
diff --git a/Tests/RunCMake/ExportPackageInfo/BadArgs2.cmake b/Tests/RunCMake/ExportPackageInfo/BadArgs2.cmake
index 5f4c949..6058c7f 100644
--- a/Tests/RunCMake/ExportPackageInfo/BadArgs2.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/BadArgs2.cmake
@@ -1,8 +1,10 @@
 add_library(foo INTERFACE)
 install(TARGETS foo EXPORT foo DESTINATION .)
-export(EXPORT foo PACKAGE_INFO foo APPENDIX test VERSION 1.0)
-export(EXPORT foo PACKAGE_INFO foo APPENDIX test DESCRIPTION "Test")
-export(EXPORT foo PACKAGE_INFO foo APPENDIX test HOMEPAGE_URL "example.com")
-export(EXPORT foo PACKAGE_INFO foo APPENDIX test DEFAULT_TARGETS foo)
-export(EXPORT foo PACKAGE_INFO foo APPENDIX test DEFAULT_CONFIGURATIONS Release)
-export(EXPORT foo PACKAGE_INFO foo APPENDIX test PROJECT foo)
+set(args EXPORT foo PACKAGE_INFO foo APPENDIX test)
+export(${args} VERSION 1.0)
+export(${args} LICENSE "BSD-3-Clause AND CC-BY-SA-4.0")
+export(${args} DESCRIPTION "Test")
+export(${args} HOMEPAGE_URL "example.com")
+export(${args} DEFAULT_TARGETS foo)
+export(${args} DEFAULT_CONFIGURATIONS Release)
+export(${args} PROJECT foo)
diff --git a/Tests/RunCMake/ExportPackageInfo/BadArgs4-stderr.txt b/Tests/RunCMake/ExportPackageInfo/BadArgs4-stderr.txt
index e21b87e..2471497 100644
--- a/Tests/RunCMake/ExportPackageInfo/BadArgs4-stderr.txt
+++ b/Tests/RunCMake/ExportPackageInfo/BadArgs4-stderr.txt
@@ -17,6 +17,18 @@
 
 
 CMake Error at BadArgs4\.cmake:[0-9]+ \(export\):
+  export LICENSE requires PACKAGE_INFO\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs4\.cmake:[0-9]+ \(export\):
+  export DEFAULT_LICENSE requires PACKAGE_INFO\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs4\.cmake:[0-9]+ \(export\):
   export DESCRIPTION requires PACKAGE_INFO\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:3 \(include\)
diff --git a/Tests/RunCMake/ExportPackageInfo/BadArgs4.cmake b/Tests/RunCMake/ExportPackageInfo/BadArgs4.cmake
index d8cb163..2405a63 100644
--- a/Tests/RunCMake/ExportPackageInfo/BadArgs4.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/BadArgs4.cmake
@@ -1,11 +1,14 @@
 add_library(foo INTERFACE)
 install(TARGETS foo EXPORT foo DESTINATION .)
-export(EXPORT foo LOWER_CASE_FILE)
-export(EXPORT foo APPENDIX test)
-export(EXPORT foo VERSION 1.0)
-export(EXPORT foo DESCRIPTION "Test")
-export(EXPORT foo HOMEPAGE_URL "example.com")
-export(EXPORT foo DEFAULT_TARGETS foo)
-export(EXPORT foo DEFAULT_CONFIGURATIONS Release)
-export(EXPORT foo PROJECT foo)
-export(EXPORT foo NO_PROJECT_METADATA)
+set(args EXPORT foo)
+export(${args} LOWER_CASE_FILE)
+export(${args} APPENDIX test)
+export(${args} VERSION 1.0)
+export(${args} LICENSE "BSD-3-Clause AND CC-BY-SA-4.0")
+export(${args} DEFAULT_LICENSE "BSD-3-Clause")
+export(${args} DESCRIPTION "Test")
+export(${args} HOMEPAGE_URL "example.com")
+export(${args} DEFAULT_TARGETS foo)
+export(${args} DEFAULT_CONFIGURATIONS Release)
+export(${args} PROJECT foo)
+export(${args} NO_PROJECT_METADATA)
diff --git a/Tests/RunCMake/ExportPackageInfo/Metadata-check.cmake b/Tests/RunCMake/ExportPackageInfo/Metadata-check.cmake
index f9b3cc7..438a76c 100644
--- a/Tests/RunCMake/ExportPackageInfo/Metadata-check.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/Metadata-check.cmake
@@ -15,5 +15,7 @@
 expect_value("${content}" "release" "configurations" 0)
 expect_value("${content}" "debug" "configurations" 1)
 
+expect_value("${content}" "BSD-3-Clause" "default_license")
+expect_value("${content}" "BSD-3-Clause AND CC-BY-SA-4.0" "license")
 expect_value("${content}" "Sample package" "description")
 expect_value("${content}" "https://www.example.com/package/foo" "website")
diff --git a/Tests/RunCMake/ExportPackageInfo/Metadata.cmake b/Tests/RunCMake/ExportPackageInfo/Metadata.cmake
index 2311695..4f3bd16 100644
--- a/Tests/RunCMake/ExportPackageInfo/Metadata.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/Metadata.cmake
@@ -8,6 +8,8 @@
   COMPAT_VERSION 1.2.0
   DEFAULT_TARGETS foo
   DEFAULT_CONFIGURATIONS release debug
+  LICENSE "BSD-3-Clause AND CC-BY-SA-4.0"
+  DEFAULT_LICENSE "BSD-3-Clause"
   DESCRIPTION "Sample package"
   HOMEPAGE_URL "https://www.example.com/package/foo"
   )
diff --git a/Tests/RunCMake/ExportPackageInfo/ProjectMetadata-check.cmake b/Tests/RunCMake/ExportPackageInfo/ProjectMetadata-check.cmake
index 1330099..c7201f8 100644
--- a/Tests/RunCMake/ExportPackageInfo/ProjectMetadata-check.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/ProjectMetadata-check.cmake
@@ -6,6 +6,7 @@
 expect_value("${content}" "foo" "name")
 expect_value("${content}" "1.2.3" "version")
 expect_value("${content}" "1.1.0" "compat_version")
+expect_value("${content}" "BSD-3-Clause" "license")
 expect_value("${content}" "Sample package" "description")
 expect_value("${content}" "https://www.example.com/package/foo" "website")
 
@@ -13,6 +14,7 @@
 expect_value("${content}" "test1" "name")
 expect_value("${content}" "1.2.3" "version")
 expect_value("${content}" "1.1.0" "compat_version")
+expect_value("${content}" "BSD-3-Clause" "license")
 expect_value("${content}" "Sample package" "description")
 expect_value("${content}" "https://www.example.com/package/foo" "website")
 
@@ -20,5 +22,6 @@
 expect_value("${content}" "test2" "name")
 expect_value("${content}" "1.4.7" "version")
 expect_missing("${content}" "compat_version")
+expect_value("${content}" "Apache-2.0" "license")
 expect_value("${content}" "Don't inherit" "description")
 expect_value("${content}" "https://www.example.com/package/bar" "website")
diff --git a/Tests/RunCMake/ExportPackageInfo/ProjectMetadata.cmake b/Tests/RunCMake/ExportPackageInfo/ProjectMetadata.cmake
index f53ba0e..e8f29af 100644
--- a/Tests/RunCMake/ExportPackageInfo/ProjectMetadata.cmake
+++ b/Tests/RunCMake/ExportPackageInfo/ProjectMetadata.cmake
@@ -1,6 +1,7 @@
 project(foo
   VERSION 1.2.3
   COMPAT_VERSION 1.1.0
+  SPDX_LICENSE "BSD-3-Clause"
   DESCRIPTION "Sample package"
   HOMEPAGE_URL "https://www.example.com/package/foo"
   )
@@ -27,6 +28,7 @@
   PROJECT foo
   PACKAGE_INFO test2
   VERSION 1.4.7
+  LICENSE "Apache-2.0"
   DESCRIPTION "Don't inherit"
   HOMEPAGE_URL "https://www.example.com/package/bar"
   )
diff --git a/Tests/RunCMake/ExternalProject/FetchGitRefs.cmake b/Tests/RunCMake/ExternalProject/FetchGitRefs.cmake
index a00908b..6f3db4d 100644
--- a/Tests/RunCMake/ExternalProject/FetchGitRefs.cmake
+++ b/Tests/RunCMake/ExternalProject/FetchGitRefs.cmake
@@ -47,6 +47,7 @@
 execGitCommand(-c init.defaultBranch=master init)
 execGitCommand(config --add user.email "testauthor@cmake.org")
 execGitCommand(config --add user.name testauthor)
+execGitCommand(config commit.gpgsign "false")
 
 # Create the initial repo structure
 execGitCommand(add firstFile.txt)
diff --git a/Tests/RunCMake/GeneratorExpression/COMPILER_LINKER.cmake b/Tests/RunCMake/GeneratorExpression/COMPILER_LINKER.cmake
new file mode 100644
index 0000000..aedf6e4
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/COMPILER_LINKER.cmake
@@ -0,0 +1,19 @@
+
+set(languages C ${LANG})
+list(REMOVE_DUPLICATES languages)
+
+enable_language(${languages})
+
+include(CTest)
+
+set(VAR "${CMAKE_${LANG}_COMPILER_LINKER_${TYPE}}")
+if(NOT VAR)
+  set(VAR "UNDEF")
+endif()
+
+add_executable(COMPILER_LINKER compiler_linker.c)
+target_compile_definitions(COMPILER_LINKER PRIVATE "VAR=${VAR}"
+                                                   "GENEX=$<IF:$<BOOL:$<${LANG}_COMPILER_LINKER_${TYPE}>>,$<${LANG}_COMPILER_LINKER_${TYPE}>,UNDEF>")
+
+add_test(NAME COMPILER_LINKER.${LANG}.${TYPE}
+         COMMAND COMPILER_LINKER)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.C.FRONTEND_VARIANT-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.C.FRONTEND_VARIANT-stderr.txt
new file mode 100644
index 0000000..35285fc
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.C.FRONTEND_VARIANT-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<C_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  \$<C_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary targets.
+  It may not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.C.ID-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.C.ID-stderr.txt
new file mode 100644
index 0000000..b68ff04
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.C.ID-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<C_COMPILER_LINKER_ID>
+
+  \$<C_COMPILER_LINKER_ID> may only be used with binary targets.  It may not
+  be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CUDA.FRONTEND_VARIANT-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CUDA.FRONTEND_VARIANT-stderr.txt
new file mode 100644
index 0000000..796707f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CUDA.FRONTEND_VARIANT-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<CUDA_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  \$<CUDA_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary
+  targets.  It may not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CUDA.ID-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CUDA.ID-stderr.txt
new file mode 100644
index 0000000..d5a67a6
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CUDA.ID-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<CUDA_COMPILER_LINKER_ID>
+
+  \$<CUDA_COMPILER_LINKER_ID> may only be used with binary targets.  It may
+  not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CXX.FRONTEND_VARIANT-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CXX.FRONTEND_VARIANT-stderr.txt
new file mode 100644
index 0000000..818c506
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CXX.FRONTEND_VARIANT-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<CXX_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  \$<CXX_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary
+  targets.  It may not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CXX.ID-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CXX.ID-stderr.txt
new file mode 100644
index 0000000..b3e9636
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.CXX.ID-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<CXX_COMPILER_LINKER_ID>
+
+  \$<CXX_COMPILER_LINKER_ID> may only be used with binary targets.  It may not
+  be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.Fortran.FRONTEND_VARIANT-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.Fortran.FRONTEND_VARIANT-stderr.txt
new file mode 100644
index 0000000..5271aa2
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.Fortran.FRONTEND_VARIANT-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<Fortran_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  \$<Fortran_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary
+  targets.  It may not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.Fortran.ID-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.Fortran.ID-stderr.txt
new file mode 100644
index 0000000..fba2c28
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.Fortran.ID-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<Fortran_COMPILER_LINKER_ID>
+
+  \$<Fortran_COMPILER_LINKER_ID> may only be used with binary targets.  It may
+  not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.HIP.FRONTEND_VARIANT-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.HIP.FRONTEND_VARIANT-stderr.txt
new file mode 100644
index 0000000..a260a01
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.HIP.FRONTEND_VARIANT-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<HIP_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  \$<HIP_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary
+  targets.  It may not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.HIP.ID-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.HIP.ID-stderr.txt
new file mode 100644
index 0000000..da64b4e
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.HIP.ID-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<HIP_COMPILER_LINKER_ID>
+
+  \$<HIP_COMPILER_LINKER_ID> may only be used with binary targets.  It may not
+  be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJC.FRONTEND_VARIANT-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJC.FRONTEND_VARIANT-stderr.txt
new file mode 100644
index 0000000..80aee3e
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJC.FRONTEND_VARIANT-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<OBJC_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  \$<OBJC_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary
+  targets.  It may not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJC.ID-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJC.ID-stderr.txt
new file mode 100644
index 0000000..c23c539
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJC.ID-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<OBJC_COMPILER_LINKER_ID>
+
+  \$<OBJC_COMPILER_LINKER_ID> may only be used with binary targets.  It may
+  not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJCXX.FRONTEND_VARIANT-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJCXX.FRONTEND_VARIANT-stderr.txt
new file mode 100644
index 0000000..506a48b
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJCXX.FRONTEND_VARIANT-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<OBJCXX_COMPILER_LINKER_FRONTEND_VARIANT>
+
+  \$<OBJCXX_COMPILER_LINKER_FRONTEND_VARIANT> may only be used with binary
+  targets.  It may not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJCXX.ID-stderr.txt b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJCXX.ID-stderr.txt
new file mode 100644
index 0000000..7160ef7
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.OBJCXX.ID-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at NonValidTarget-COMPILER_LINKER.cmake:[0-9]+ \(add_custom_command\):
+  Error evaluating generator expression:
+
+    \$<OBJCXX_COMPILER_LINKER_ID>
+
+  \$<OBJCXX_COMPILER_LINKER_ID> may only be used with binary targets.  It may
+  not be used with add_custom_command or add_custom_target.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.cmake b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.cmake
new file mode 100644
index 0000000..0853e72
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/NonValidTarget-COMPILER_LINKER.cmake
@@ -0,0 +1,4 @@
+add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c"
+  COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/empty.c" "${CMAKE_CURRENT_BINARY_DIR}/copied_file$<${LANG}_COMPILER_LINKER_${TYPE}>.c"
+)
+add_custom_target(drive DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/copied_file.c")
diff --git a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
index 985d91b..fe7b64a 100644
--- a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
@@ -48,6 +48,49 @@
 run_cmake(FILTER-Exclude)
 run_cmake(FILTER-Include)
 
+function(run_linker_genex test lang type)
+  set(options_args CHECK_RESULT EXECUTE)
+  cmake_parse_arguments(PARSE_ARGV 3 RLG "${options_args}" "" "")
+
+  set(RunCMake_TEST_VARIANT_DESCRIPTION ".${lang}.${type}")
+  set(test_name ${test}${RunCMake_TEST_VARIANT_DESCRIPTION})
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${test_name}-build")
+  if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    list(APPEND options -DCMAKE_BUILD_TYPE=Release)
+  endif()
+  if(RLG_CHECK_RESULT)
+    set(RunCMake_TEST_EXPECT_RESULT 1)
+    file(READ "${RunCMake_SOURCE_DIR}/${test_name}-stderr.txt" RunCMake_TEST_EXPECT_stderr)
+  endif()
+  run_cmake_with_options(${test} -DLANG=${lang} -DTYPE=${type})
+  set(RunCMake_TEST_NO_CLEAN 1)
+  unset(RunCMake_TEST_VARIANT_DESCRIPTION)
+  if(RLG_EXECUTE)
+    run_cmake_command(${test_name}-build "${CMAKE_COMMAND}" --build . --config Release)
+    run_cmake_command(${test_name}-run "${CMAKE_CTEST_COMMAND}" -C Release -V)
+  endif()
+endfunction()
+function(exec_linker_genex test lang type)
+  run_linker_genex(${ARGV} EXECUTE)
+endfunction()
+
+set(languages C CXX)
+foreach(lang IN ITEMS OBJC Fortran CUDA HIP)
+  if(CMake_TEST_${lang})
+    list(APPEND languages ${lang})
+    if(lang STREQUAL OBJC)
+    list(APPEND languages OBJCXX)
+    endif()
+  endif()
+endforeach()
+
+foreach(lang IN LISTS languages)
+  foreach(type IN ITEMS ID FRONTEND_VARIANT)
+    run_linker_genex(NonValidTarget-COMPILER_LINKER ${lang} ${type} CHECK_RESULT)
+    exec_linker_genex(COMPILER_LINKER ${lang} ${type})
+  endforeach()
+endforeach()
+
 if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
   set(RunCMake_TEST_OPTIONS [==[-DCMAKE_CONFIGURATION_TYPES=CustomConfig]==])
 else()
diff --git a/Tests/RunCMake/GeneratorExpression/compiler_linker.c b/Tests/RunCMake/GeneratorExpression/compiler_linker.c
new file mode 100644
index 0000000..db9a5f4
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/compiler_linker.c
@@ -0,0 +1,10 @@
+
+#include <string.h>
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+int main(void)
+{
+  return strcmp(xstr(VAR), xstr(GENEX)) == 0 ? 0 : 1;
+}
diff --git a/Tests/RunCMake/InstallPackageInfo/Appendix-check.cmake b/Tests/RunCMake/InstallPackageInfo/Appendix-check.cmake
index 864e731..92925d8 100644
--- a/Tests/RunCMake/InstallPackageInfo/Appendix-check.cmake
+++ b/Tests/RunCMake/InstallPackageInfo/Appendix-check.cmake
@@ -5,11 +5,13 @@
 file(READ "${out_dir}/foo.cps" content)
 expect_value("${content}" "foo" "name")
 expect_value("${content}" "interface" "components" "mammal" "type")
+expect_value("${content}" "LGPL-3.0-or-later" "default_license")
 expect_value("${content}" "1.0" "version")
 
 file(READ "${out_dir}/foo-dog.cps" content)
 expect_value("${content}" "foo" "name")
 expect_value("${content}" "interface" "components" "canine" "type")
+expect_value("${content}" "GPL-3.0-or-later" "default_license")
 expect_missing("${content}" "version")
 
 expect_array("${content}" 1 "components" "canine" "requires")
diff --git a/Tests/RunCMake/InstallPackageInfo/Appendix.cmake b/Tests/RunCMake/InstallPackageInfo/Appendix.cmake
index fe67778..5f4222c 100644
--- a/Tests/RunCMake/InstallPackageInfo/Appendix.cmake
+++ b/Tests/RunCMake/InstallPackageInfo/Appendix.cmake
@@ -5,5 +5,16 @@
 install(TARGETS mammal EXPORT mammal DESTINATION .)
 install(TARGETS canine EXPORT canine DESTINATION .)
 
-install(PACKAGE_INFO foo DESTINATION cps EXPORT mammal VERSION 1.0)
-install(PACKAGE_INFO foo DESTINATION cps EXPORT canine APPENDIX dog)
+install(
+  PACKAGE_INFO foo
+  DESTINATION cps
+  EXPORT mammal
+  VERSION 1.0
+  DEFAULT_LICENSE "LGPL-3.0-or-later")
+
+install(
+  PACKAGE_INFO foo
+  DESTINATION cps
+  EXPORT canine
+  APPENDIX dog
+  DEFAULT_LICENSE "GPL-3.0-or-later")
diff --git a/Tests/RunCMake/InstallPackageInfo/BadArgs2-stderr.txt b/Tests/RunCMake/InstallPackageInfo/BadArgs2-stderr.txt
index a50f759..1df7278 100644
--- a/Tests/RunCMake/InstallPackageInfo/BadArgs2-stderr.txt
+++ b/Tests/RunCMake/InstallPackageInfo/BadArgs2-stderr.txt
@@ -5,6 +5,12 @@
 
 
 CMake Error at BadArgs2.cmake:[0-9]+ \(install\):
+  install APPENDIX and LICENSE are mutually exclusive.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs2.cmake:[0-9]+ \(install\):
   install APPENDIX and DESCRIPTION are mutually exclusive.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:3 \(include\)
diff --git a/Tests/RunCMake/InstallPackageInfo/BadArgs2.cmake b/Tests/RunCMake/InstallPackageInfo/BadArgs2.cmake
index b4d405f..4462907 100644
--- a/Tests/RunCMake/InstallPackageInfo/BadArgs2.cmake
+++ b/Tests/RunCMake/InstallPackageInfo/BadArgs2.cmake
@@ -1,8 +1,10 @@
 add_library(foo INTERFACE)
 install(TARGETS foo EXPORT foo DESTINATION .)
-install(PACKAGE_INFO test EXPORT foo APPENDIX test VERSION 1.0)
-install(PACKAGE_INFO test EXPORT foo APPENDIX test DESCRIPTION "Test")
-install(PACKAGE_INFO test EXPORT foo APPENDIX test HOMEPAGE_URL "example.com")
-install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_TARGETS foo)
-install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_CONFIGURATIONS test)
-install(PACKAGE_INFO test EXPORT foo APPENDIX test PROJECT foo)
+set(args PACKAGE_INFO test EXPORT foo APPENDIX test)
+install(${args} VERSION 1.0)
+install(${args} LICENSE "BSD-3-Clause AND CC-BY-SA-4.0")
+install(${args} DESCRIPTION "Test")
+install(${args} HOMEPAGE_URL "example.com")
+install(${args} DEFAULT_TARGETS foo)
+install(${args} DEFAULT_CONFIGURATIONS test)
+install(${args} PROJECT foo)
diff --git a/Tests/RunCMake/InstallPackageInfo/Metadata-check.cmake b/Tests/RunCMake/InstallPackageInfo/Metadata-check.cmake
index 7eca3b0..75516d5 100644
--- a/Tests/RunCMake/InstallPackageInfo/Metadata-check.cmake
+++ b/Tests/RunCMake/InstallPackageInfo/Metadata-check.cmake
@@ -15,5 +15,7 @@
 expect_value("${content}" "release" "configurations" 0)
 expect_value("${content}" "debug" "configurations" 1)
 
+expect_value("${content}" "BSD-3-Clause" "default_license")
+expect_value("${content}" "BSD-3-Clause AND CC-BY-SA-4.0" "license")
 expect_value("${content}" "Sample package" "description")
 expect_value("${content}" "https://www.example.com/package/foo" "website")
diff --git a/Tests/RunCMake/InstallPackageInfo/Metadata.cmake b/Tests/RunCMake/InstallPackageInfo/Metadata.cmake
index d551f3b..f14fe93 100644
--- a/Tests/RunCMake/InstallPackageInfo/Metadata.cmake
+++ b/Tests/RunCMake/InstallPackageInfo/Metadata.cmake
@@ -9,6 +9,8 @@
   COMPAT_VERSION 1.2.0
   DEFAULT_TARGETS foo
   DEFAULT_CONFIGURATIONS release debug
+  LICENSE "BSD-3-Clause AND CC-BY-SA-4.0"
+  DEFAULT_LICENSE "BSD-3-Clause"
   DESCRIPTION "Sample package"
   HOMEPAGE_URL "https://www.example.com/package/foo"
   )
diff --git a/Tests/RunCMake/InstallPackageInfo/ProjectMetadata-check.cmake b/Tests/RunCMake/InstallPackageInfo/ProjectMetadata-check.cmake
index 5c39b3f..b09b065 100644
--- a/Tests/RunCMake/InstallPackageInfo/ProjectMetadata-check.cmake
+++ b/Tests/RunCMake/InstallPackageInfo/ProjectMetadata-check.cmake
@@ -6,6 +6,7 @@
 expect_value("${content}" "foo" "name")
 expect_value("${content}" "1.2.3" "version")
 expect_value("${content}" "1.1.0" "compat_version")
+expect_value("${content}" "BSD-3-Clause" "license")
 expect_value("${content}" "Sample package" "description")
 expect_value("${content}" "https://www.example.com/package/foo" "website")
 
@@ -13,6 +14,7 @@
 expect_value("${content}" "test1" "name")
 expect_value("${content}" "1.2.3" "version")
 expect_value("${content}" "1.1.0" "compat_version")
+expect_value("${content}" "BSD-3-Clause" "license")
 expect_value("${content}" "Sample package" "description")
 expect_value("${content}" "https://www.example.com/package/foo" "website")
 
@@ -20,5 +22,6 @@
 expect_value("${content}" "test2" "name")
 expect_value("${content}" "1.4.7" "version")
 expect_missing("${content}" "compat_version")
+expect_value("${content}" "Apache-2.0" "license")
 expect_value("${content}" "Don't inherit" "description")
 expect_value("${content}" "https://www.example.com/package/bar" "website")
diff --git a/Tests/RunCMake/InstallPackageInfo/ProjectMetadata.cmake b/Tests/RunCMake/InstallPackageInfo/ProjectMetadata.cmake
index e86372f..3111150 100644
--- a/Tests/RunCMake/InstallPackageInfo/ProjectMetadata.cmake
+++ b/Tests/RunCMake/InstallPackageInfo/ProjectMetadata.cmake
@@ -1,6 +1,7 @@
 project(foo
   VERSION 1.2.3
   COMPAT_VERSION 1.1.0
+  SPDX_LICENSE "BSD-3-Clause"
   DESCRIPTION "Sample package"
   HOMEPAGE_URL "https://www.example.com/package/foo"
   )
@@ -30,6 +31,7 @@
   EXPORT foo
   PROJECT foo
   VERSION 1.4.7
+  LICENSE "Apache-2.0"
   DESCRIPTION "Don't inherit"
   HOMEPAGE_URL "https://www.example.com/package/bar"
   )
diff --git a/Tests/RunCMake/find_package-CPS/License.cmake b/Tests/RunCMake/find_package-CPS/License.cmake
new file mode 100644
index 0000000..623dbc7
--- /dev/null
+++ b/Tests/RunCMake/find_package-CPS/License.cmake
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 4.0)
+
+include(Setup.cmake)
+
+set(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
+set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
+
+find_package(LicenseTest REQUIRED)
+
+function(expect_license COMPONENT EXPECTED)
+  set(target LicenseTest::${COMPONENT})
+  if(TARGET ${target})
+    get_target_property(license ${target} "SPDX_LICENSE")
+    if (NOT "${license}" STREQUAL "${EXPECTED}")
+      message(SEND_ERROR
+        "Target ${target} has wrong license '${license}'"
+        " (expected '${EXPECTED}') !")
+    endif()
+  else()
+    message(SEND_ERROR "Expected target ${target} was not found !")
+  endif()
+endfunction()
+
+expect_license(SpecifiedOnTarget "Apache-2.0")
+expect_license(InheritFromRoot "BSD-3-Clause")
+expect_license(InheritFromAppendix "Apache-2.0")
+expect_license(DisableInheritance "license-NOTFOUND")
diff --git a/Tests/RunCMake/find_package-CPS/RunCMakeTest.cmake b/Tests/RunCMake/find_package-CPS/RunCMakeTest.cmake
index 1bcdc8a..cbc636a 100644
--- a/Tests/RunCMake/find_package-CPS/RunCMakeTest.cmake
+++ b/Tests/RunCMake/find_package-CPS/RunCMakeTest.cmake
@@ -18,7 +18,7 @@
 run_cmake(CustomVersion)
 
 # Metadata Tests
-run_cmake(SupplementalAttributes)
+run_cmake(License)
 
 # Version-matching failure tests
 run_cmake(MissingVersion1)
diff --git a/Tests/RunCMake/find_package-CPS/SupplementalAttributes.cmake b/Tests/RunCMake/find_package-CPS/SupplementalAttributes.cmake
deleted file mode 100644
index 3d6d567..0000000
--- a/Tests/RunCMake/find_package-CPS/SupplementalAttributes.cmake
+++ /dev/null
@@ -1,13 +0,0 @@
-cmake_minimum_required(VERSION 4.0)
-
-include(Setup.cmake)
-
-set(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
-set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
-
-find_package(SupplementalAttributesTest REQUIRED COMPONENTS Sample)
-
-get_target_property(license SupplementalAttributesTest::Sample "SPDX_LICENSE")
-if (NOT "${license}" STREQUAL "BSD-3-Clause")
-  message(SEND_ERROR "SupplementalAttributesTest wrong license ${license} !")
-endif()
diff --git a/Tests/RunCMake/find_package-CPS/cps/licensetest-notinherited.cps b/Tests/RunCMake/find_package-CPS/cps/licensetest-notinherited.cps
new file mode 100644
index 0000000..0ba7dd1
--- /dev/null
+++ b/Tests/RunCMake/find_package-CPS/cps/licensetest-notinherited.cps
@@ -0,0 +1,11 @@
+{
+  "cps_version": "0.13",
+  "name": "LicenseTest",
+  "default_license": "",
+  "cps_path": "@prefix@/cps",
+  "components": {
+    "DisableInheritance": {
+      "type": "interface"
+    }
+  }
+}
diff --git a/Tests/RunCMake/find_package-CPS/cps/licensetest-override.cps b/Tests/RunCMake/find_package-CPS/cps/licensetest-override.cps
new file mode 100644
index 0000000..01d8900
--- /dev/null
+++ b/Tests/RunCMake/find_package-CPS/cps/licensetest-override.cps
@@ -0,0 +1,11 @@
+{
+  "cps_version": "0.13",
+  "name": "LicenseTest",
+  "default_license": "Apache-2.0",
+  "cps_path": "@prefix@/cps",
+  "components": {
+    "InheritFromAppendix": {
+      "type": "interface"
+    }
+  }
+}
diff --git a/Tests/RunCMake/find_package-CPS/cps/licensetest.cps b/Tests/RunCMake/find_package-CPS/cps/licensetest.cps
new file mode 100644
index 0000000..f376933
--- /dev/null
+++ b/Tests/RunCMake/find_package-CPS/cps/licensetest.cps
@@ -0,0 +1,16 @@
+{
+  "cps_version": "0.13",
+  "name": "LicenseTest",
+  "version": "1.0",
+  "default_license": "BSD-3-Clause",
+  "cps_path": "@prefix@/cps",
+  "components": {
+    "SpecifiedOnTarget": {
+      "type": "interface",
+      "license": "Apache-2.0"
+    },
+    "InheritFromRoot": {
+      "type": "interface"
+    }
+  }
+}
diff --git a/Tests/RunCMake/find_package-CPS/cps/supplementalattributestest.cps b/Tests/RunCMake/find_package-CPS/cps/supplementalattributestest.cps
deleted file mode 100644
index c7f3ce4..0000000
--- a/Tests/RunCMake/find_package-CPS/cps/supplementalattributestest.cps
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "cps_version": "0.13",
-  "name": "SupplementalAttributesTest",
-  "version": "1.0",
-  "cps_path": "@prefix@/cps",
-  "components": {
-    "Sample": {
-      "type": "interface",
-      "license": "BSD-3-Clause"
-    }
-  }
-}
diff --git a/Tests/RunCMake/find_package/ConfigureLog.cmake b/Tests/RunCMake/find_package/ConfigureLog.cmake
index 49e785c..3b304a9 100644
--- a/Tests/RunCMake/find_package/ConfigureLog.cmake
+++ b/Tests/RunCMake/find_package/ConfigureLog.cmake
@@ -6,6 +6,7 @@
 set(CMAKE_FIND_DEBUG_MODE 1)
 # Stable sorting for predictable behaviors.
 set(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
+set(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASCENDING)
 
 # Unset search variables for more predictable output.
 unset(CMAKE_FRAMEWORK_PATH)
diff --git a/Tests/RunCMake/project/Omissions.cmake b/Tests/RunCMake/project/Omissions.cmake
index 79f695f..88f8e82 100644
--- a/Tests/RunCMake/project/Omissions.cmake
+++ b/Tests/RunCMake/project/Omissions.cmake
@@ -20,5 +20,6 @@
 expect_empty(VERSION_PATCH)
 expect_empty(VERSION_TWEAK)
 expect_empty(COMPAT_VERSION)
+expect_empty(SPDX_LICENSE)
 expect_empty(DESCRIPTION)
 expect_empty(HOMEPAGE_URL)
diff --git a/Tests/RunCMake/project/ProjectLicense-stdout.txt b/Tests/RunCMake/project/ProjectLicense-stdout.txt
new file mode 100644
index 0000000..ef08658
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicense-stdout.txt
@@ -0,0 +1 @@
+PROJECT_SPDX_LICENSE=BSD-3-Clause
diff --git a/Tests/RunCMake/project/ProjectLicense.cmake b/Tests/RunCMake/project/ProjectLicense.cmake
new file mode 100644
index 0000000..847ee64
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicense.cmake
@@ -0,0 +1,5 @@
+project(ProjectLicenseTest SPDX_LICENSE "BSD-3-Clause" LANGUAGES)
+if(NOT PROJECT_SPDX_LICENSE)
+  message(FATAL_ERROR "PROJECT_SPDX_LICENSE expected to be set")
+endif()
+message(STATUS "PROJECT_SPDX_LICENSE=${PROJECT_SPDX_LICENSE}")
diff --git a/Tests/RunCMake/project/ProjectLicense2-result.txt b/Tests/RunCMake/project/ProjectLicense2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicense2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/project/ProjectLicense2-stderr.txt b/Tests/RunCMake/project/ProjectLicense2-stderr.txt
new file mode 100644
index 0000000..b3be849
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicense2-stderr.txt
@@ -0,0 +1 @@
+  SPDX_LICENSE may be specified at most once.
diff --git a/Tests/RunCMake/project/ProjectLicense2.cmake b/Tests/RunCMake/project/ProjectLicense2.cmake
new file mode 100644
index 0000000..c3ef751
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicense2.cmake
@@ -0,0 +1 @@
+project(ProjectLicenseTest SPDX_LICENSE "BSD-3-Clause" SPDX_LICENSE "Apache-2.0" LANGUAGES)
diff --git a/Tests/RunCMake/project/ProjectLicenseNoArg-stderr.txt b/Tests/RunCMake/project/ProjectLicenseNoArg-stderr.txt
new file mode 100644
index 0000000..5cb13fa
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicenseNoArg-stderr.txt
@@ -0,0 +1,2 @@
+  SPDX_LICENSE keyword not followed by a value or was followed by a value
+  that expanded to nothing.
diff --git a/Tests/RunCMake/project/ProjectLicenseNoArg.cmake b/Tests/RunCMake/project/ProjectLicenseNoArg.cmake
new file mode 100644
index 0000000..7404e4d
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicenseNoArg.cmake
@@ -0,0 +1 @@
+project(ProjectLicenseTest LANGUAGES NONE SPDX_LICENSE)
diff --git a/Tests/RunCMake/project/ProjectLicenseNoArg2-stderr.txt b/Tests/RunCMake/project/ProjectLicenseNoArg2-stderr.txt
new file mode 100644
index 0000000..5cb13fa
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicenseNoArg2-stderr.txt
@@ -0,0 +1,2 @@
+  SPDX_LICENSE keyword not followed by a value or was followed by a value
+  that expanded to nothing.
diff --git a/Tests/RunCMake/project/ProjectLicenseNoArg2.cmake b/Tests/RunCMake/project/ProjectLicenseNoArg2.cmake
new file mode 100644
index 0000000..4370d5b
--- /dev/null
+++ b/Tests/RunCMake/project/ProjectLicenseNoArg2.cmake
@@ -0,0 +1 @@
+project(ProjectLicenseTest SPDX_LICENSE LANGUAGES NONE)
diff --git a/Tests/RunCMake/project/RunCMakeTest.cmake b/Tests/RunCMake/project/RunCMakeTest.cmake
index 8ffc645..31579e9 100644
--- a/Tests/RunCMake/project/RunCMakeTest.cmake
+++ b/Tests/RunCMake/project/RunCMakeTest.cmake
@@ -66,6 +66,10 @@
 run_cmake_with_options(ProjectCompatVersionMissingVersion ${opts})
 run_cmake_with_options(ProjectCompatVersionNewer ${opts})
 run_cmake_with_options(ProjectCompatVersionNoArg ${opts})
+run_cmake_with_options(ProjectLicense ${opts})
+run_cmake_with_options(ProjectLicense2 ${opts})
+run_cmake_with_options(ProjectLicenseNoArg ${opts})
+run_cmake_with_options(ProjectLicenseNoArg2 ${opts})
 
 run_cmake(CMP0048-NEW)