浏览代码

cmTarget: add support for C++ module fileset types

C++ modules have two variants which are of importance to CMake:

  - `CXX_MODULES`: interface modules (those using `export module M;`,
    `export module M:part;`, or `module M:internal_part;`)
  - `CXX_MODULE_HEADER_UNITS`: importable header units

Creating C++ modules or partitions are *not* supported in any other
source listing. This is because the source files must be installed (so
their scope matters), but not part of usage requirements (what it means
for a module source to be injected into a consumer is not clear at this
moment). Due to the way `FILE_SET` works with scopes, they are a perfect
fit as long as `INTERFACE` is not allowed (which it is not).
Ben Boeckel 3 年之前
父节点
当前提交
386465bf83
共有 82 个文件被更改,包括 1290 次插入74 次删除
  1. 62 15
      Help/command/target_sources.rst
  2. 0 5
      Help/dev/experimental.rst
  3. 12 0
      Help/manual/cmake-properties.7.rst
  4. 17 0
      Help/prop_tgt/CXX_MODULE_DIRS.rst
  5. 17 0
      Help/prop_tgt/CXX_MODULE_DIRS_NAME.rst
  6. 17 0
      Help/prop_tgt/CXX_MODULE_HEADER_UNIT_DIRS.rst
  7. 19 0
      Help/prop_tgt/CXX_MODULE_HEADER_UNIT_DIRS_NAME.rst
  8. 18 0
      Help/prop_tgt/CXX_MODULE_HEADER_UNIT_SET.rst
  9. 18 0
      Help/prop_tgt/CXX_MODULE_HEADER_UNIT_SETS.rst
  10. 19 0
      Help/prop_tgt/CXX_MODULE_HEADER_UNIT_SET_NAME.rst
  11. 18 0
      Help/prop_tgt/CXX_MODULE_SET.rst
  12. 16 0
      Help/prop_tgt/CXX_MODULE_SETS.rst
  13. 18 0
      Help/prop_tgt/CXX_MODULE_SET_NAME.rst
  14. 16 0
      Help/prop_tgt/INTERFACE_CXX_MODULE_HEADER_UNIT_SETS.rst
  15. 16 0
      Help/prop_tgt/INTERFACE_CXX_MODULE_SETS.rst
  16. 32 0
      Source/cmExportBuildFileGenerator.cxx
  17. 34 0
      Source/cmExportInstallFileGenerator.cxx
  18. 89 1
      Source/cmGeneratorTarget.cxx
  19. 30 0
      Source/cmGeneratorTarget.h
  20. 15 0
      Source/cmGlobalVisualStudio7Generator.cxx
  21. 2 0
      Source/cmGlobalVisualStudio7Generator.h
  22. 12 0
      Source/cmGlobalXCodeGenerator.cxx
  23. 65 0
      Source/cmMakefileTargetGenerator.cxx
  24. 45 47
      Source/cmNinjaTargetGenerator.cxx
  25. 79 0
      Source/cmTarget.cxx
  26. 4 0
      Source/cmTarget.h
  27. 26 4
      Source/cmTargetSourcesCommand.cxx
  28. 12 0
      Source/cmVisualStudio10TargetGenerator.cxx
  29. 3 0
      Tests/RunCMake/CMakeLists.txt
  30. 6 0
      Tests/RunCMake/CXXModules/CMakeLists.txt
  31. 1 0
      Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsInterface-result.txt
  32. 12 0
      Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsInterface-stderr.txt
  33. 8 0
      Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsInterface.cmake
  34. 11 0
      Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPrivate-stderr.txt
  35. 13 0
      Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPrivate.cmake
  36. 11 0
      Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPublic-stderr.txt
  37. 13 0
      Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPublic.cmake
  38. 1 0
      Tests/RunCMake/CXXModules/FileSetModulesInterface-result.txt
  39. 12 0
      Tests/RunCMake/CXXModules/FileSetModulesInterface-stderr.txt
  40. 8 0
      Tests/RunCMake/CXXModules/FileSetModulesInterface.cmake
  41. 11 0
      Tests/RunCMake/CXXModules/FileSetModulesPrivate-stderr.txt
  42. 12 0
      Tests/RunCMake/CXXModules/FileSetModulesPrivate.cmake
  43. 11 0
      Tests/RunCMake/CXXModules/FileSetModulesPublic-stderr.txt
  44. 12 0
      Tests/RunCMake/CXXModules/FileSetModulesPublic.cmake
  45. 1 0
      Tests/RunCMake/CXXModules/NoCXX-result.txt
  46. 20 0
      Tests/RunCMake/CXXModules/NoCXX-stderr.txt
  47. 9 0
      Tests/RunCMake/CXXModules/NoCXX.cmake
  48. 1 0
      Tests/RunCMake/CXXModules/NoCXX20-result.txt
  49. 20 0
      Tests/RunCMake/CXXModules/NoCXX20-stderr.txt
  50. 10 0
      Tests/RunCMake/CXXModules/NoCXX20.cmake
  51. 1 0
      Tests/RunCMake/CXXModules/NoCXX20ModuleFlag-result.txt
  52. 20 0
      Tests/RunCMake/CXXModules/NoCXX20ModuleFlag-stderr.txt
  53. 10 0
      Tests/RunCMake/CXXModules/NoCXX20ModuleFlag.cmake
  54. 1 0
      Tests/RunCMake/CXXModules/NoDyndepSupport-result.txt
  55. 30 0
      Tests/RunCMake/CXXModules/NoDyndepSupport-stderr.txt
  56. 16 0
      Tests/RunCMake/CXXModules/NoDyndepSupport.cmake
  57. 1 0
      Tests/RunCMake/CXXModules/NotCXXSourceModuleHeaderUnits-result.txt
  58. 22 0
      Tests/RunCMake/CXXModules/NotCXXSourceModuleHeaderUnits-stderr.txt
  59. 15 0
      Tests/RunCMake/CXXModules/NotCXXSourceModuleHeaderUnits.cmake
  60. 1 0
      Tests/RunCMake/CXXModules/NotCXXSourceModules-result.txt
  61. 17 0
      Tests/RunCMake/CXXModules/NotCXXSourceModules-stderr.txt
  62. 13 0
      Tests/RunCMake/CXXModules/NotCXXSourceModules.cmake
  63. 67 0
      Tests/RunCMake/CXXModules/RunCMakeTest.cmake
  64. 25 0
      Tests/RunCMake/CXXModules/compiler_introspection.cmake
  65. 4 0
      Tests/RunCMake/CXXModules/sources/c-anchor.c
  66. 4 0
      Tests/RunCMake/CXXModules/sources/cxx-anchor.cxx
  67. 9 0
      Tests/RunCMake/CXXModules/sources/module-header.h
  68. 6 0
      Tests/RunCMake/CXXModules/sources/module-impl.cxx
  69. 11 0
      Tests/RunCMake/CXXModules/sources/module-internal-part-impl.cxx
  70. 3 0
      Tests/RunCMake/CXXModules/sources/module-internal-part.cxx
  71. 13 0
      Tests/RunCMake/CXXModules/sources/module-part-impl.cxx
  72. 3 0
      Tests/RunCMake/CXXModules/sources/module-part.cxx
  73. 6 0
      Tests/RunCMake/CXXModules/sources/module-use.cxx
  74. 5 0
      Tests/RunCMake/CXXModules/sources/module.cxx
  75. 1 0
      Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental-result.txt
  76. 12 0
      Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental-stderr.txt
  77. 6 0
      Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental.cmake
  78. 1 0
      Tests/RunCMake/target_sources/FileSetWrongTypeExperimental-result.txt
  79. 12 0
      Tests/RunCMake/target_sources/FileSetWrongTypeExperimental-stderr.txt
  80. 6 0
      Tests/RunCMake/target_sources/FileSetWrongTypeExperimental.cmake
  81. 2 0
      Tests/RunCMake/target_sources/RunCMakeTest.cmake
  82. 13 2
      Tests/RunCMake/try_compile/CxxStandard-stderr.txt

+ 62 - 15
Help/command/target_sources.rst

@@ -75,9 +75,33 @@ File Sets
 Adds a file set to a target, or adds files to an existing file set. Targets
 have zero or more named file sets. Each file set has a name, a type, a scope of
 ``INTERFACE``, ``PUBLIC``, or ``PRIVATE``, one or more base directories, and
-files within those directories. The only acceptable type is ``HEADERS``. The
-optional default file sets are named after their type. The target may not be a
-custom target or :prop_tgt:`FRAMEWORK` target.
+files within those directories. The acceptable types include:
+
+``HEADERS``
+
+  Sources intended to be used via a language's ``#include`` mechanism.
+
+``CXX_MODULES``
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+  Sources which contain C++ interface module or partition units (i.e., those
+  using the ``export`` keyword). This file set type may not have an
+  ``INTERFACE`` scope.
+
+``CXX_MODULE_HEADER_UNITS``
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+  C++ header sources which may be imported by other C++ source code. This file
+  set type may not have an ``INTERFACE`` scope.
+
+The optional default file sets are named after their type. The target may not
+be a custom target or :prop_tgt:`FRAMEWORK` target.
 
 Files in a ``PRIVATE`` or ``PUBLIC`` file set are marked as source files for
 the purposes of IDE integration. Additionally, files in ``HEADERS`` file sets
@@ -93,16 +117,17 @@ Each ``target_sources(FILE_SET)`` entry starts with ``INTERFACE``, ``PUBLIC``, o
 
   The name of the file set to create or add to. It must contain only letters,
   numbers and underscores. Names starting with a capital letter are reserved
-  for built-in file sets predefined by CMake. The only predefined set name is
-  ``HEADERS``. All other set names must not start with a capital letter or
+  for built-in file sets predefined by CMake. The only predefined set names
+  are those matching the acceptable types. All other set names must not start
+  with a capital letter or
   underscore.
 
 ``TYPE <type>``
 
-  Every file set is associated with a particular type of file. ``HEADERS``
-  is currently the only defined type and it is an error to specify anything
-  else. As a special case, if the name of the file set is ``HEADERS``, the
-  type does not need to be specified and the ``TYPE <type>`` arguments can be
+  Every file set is associated with a particular type of file. Only types
+  specified above may be used and it is an error to specify anything else. As
+  a special case, if the name of the file set is one of the types, the type
+  does not need to be specified and the ``TYPE <type>`` arguments can be
   omitted. For all other file set names, ``TYPE`` is required.
 
 ``BASE_DIRS <dirs>...``
@@ -134,6 +159,8 @@ Each ``target_sources(FILE_SET)`` entry starts with ``INTERFACE``, ``PUBLIC``, o
 The following target properties are set by ``target_sources(FILE_SET)``,
 but they should not generally be manipulated directly:
 
+For file sets of type ``HEADERS``:
+
 * :prop_tgt:`HEADER_SETS`
 * :prop_tgt:`INTERFACE_HEADER_SETS`
 * :prop_tgt:`HEADER_SET`
@@ -141,17 +168,37 @@ but they should not generally be manipulated directly:
 * :prop_tgt:`HEADER_DIRS`
 * :prop_tgt:`HEADER_DIRS_<NAME>`
 
+For file sets of type ``CXX_MODULES``:
+
+* :prop_tgt:`CXX_MODULE_SETS`
+* :prop_tgt:`INTERFACE_CXX_MODULE_SETS`
+* :prop_tgt:`CXX_MODULE_SET`
+* :prop_tgt:`CXX_MODULE_SET_<NAME>`
+* :prop_tgt:`CXX_MODULE_DIRS`
+* :prop_tgt:`CXX_MODULE_DIRS_<NAME>`
+
+For file sets of type ``CXX_MODULE_HEADER_UNITS``:
+
+* :prop_tgt:`CXX_MODULE_HEADER_UNIT_SETS`
+* :prop_tgt:`INTERFACE_CXX_MODULE_HEADER_UNIT_SETS`
+* :prop_tgt:`CXX_MODULE_HEADER_UNIT_SET`
+* :prop_tgt:`CXX_MODULE_HEADER_UNIT_SET_<NAME>`
+* :prop_tgt:`CXX_MODULE_HEADER_UNIT_DIRS`
+* :prop_tgt:`CXX_MODULE_HEADER_UNIT_DIRS_<NAME>`
+
 Target properties related to include directories are also modified by
 ``target_sources(FILE_SET)`` as follows:
 
 :prop_tgt:`INCLUDE_DIRECTORIES`
 
-  If the ``TYPE`` is ``HEADERS``, and the scope of the file set is ``PRIVATE``
-  or ``PUBLIC``, all of the ``BASE_DIRS`` of the file set are wrapped in
-  :genex:`$<BUILD_INTERFACE>` and appended to this property.
+  If the ``TYPE`` is ``HEADERS`` or ``CXX_MODULE_HEADER_UNITS``, and the scope
+  of the file set is ``PRIVATE`` or ``PUBLIC``, all of the ``BASE_DIRS`` of
+  the file set are wrapped in :genex:`$<BUILD_INTERFACE>` and appended to this
+  property.
 
 :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES`
 
-  If the ``TYPE`` is ``HEADERS``, and the scope of the file set is
-  ``INTERFACE`` or ``PUBLIC``, all of the ``BASE_DIRS`` of the file set are
-  wrapped in :genex:`$<BUILD_INTERFACE>` and appended to this property.
+  If the ``TYPE`` is ``HEADERS`` or ``CXX_MODULE_HEADER_UNITS``, and the scope
+  of the file set is ``INTERFACE`` or ``PUBLIC``, all of the ``BASE_DIRS`` of
+  the file set are wrapped in :genex:`$<BUILD_INTERFACE>` and appended to this
+  property.

+ 0 - 5
Help/dev/experimental.rst

@@ -57,11 +57,6 @@ dependencies to the file specified by the ``<DYNDEP_FILE>`` placeholder. The
 ``CMAKE_EXPERIMENTAL_CXX_SCANDEP_DEPFILE_FORMAT`` file may be set to ``msvc``
 for scandep rules which use ``msvc``-style dependency reporting.
 
-For tools which need to know the file set the source belongs to, the
-``CMAKE_EXPERIMENTAL_CXX_MODULE_SOURCE_TYPE_FLAG_<FILE_SET_TYPE>`` flag may
-be provided so that different source types can be distinguished prior to
-scanning.
-
 The module dependencies should be written in the format described
 by the `P1689r4`_ paper.
 

+ 12 - 0
Help/manual/cmake-properties.7.rst

@@ -184,6 +184,16 @@ Properties on Targets
    /prop_tgt/CUDA_STANDARD
    /prop_tgt/CUDA_STANDARD_REQUIRED
    /prop_tgt/CXX_EXTENSIONS
+   /prop_tgt/CXX_MODULE_DIRS
+   /prop_tgt/CXX_MODULE_DIRS_NAME
+   /prop_tgt/CXX_MODULE_SET
+   /prop_tgt/CXX_MODULE_SET_NAME
+   /prop_tgt/CXX_MODULE_SETS
+   /prop_tgt/CXX_MODULE_HEADER_UNIT_DIRS
+   /prop_tgt/CXX_MODULE_HEADER_UNIT_DIRS_NAME
+   /prop_tgt/CXX_MODULE_HEADER_UNIT_SET
+   /prop_tgt/CXX_MODULE_HEADER_UNIT_SET_NAME
+   /prop_tgt/CXX_MODULE_HEADER_UNIT_SETS
    /prop_tgt/CXX_STANDARD
    /prop_tgt/CXX_STANDARD_REQUIRED
    /prop_tgt/DEBUG_POSTFIX
@@ -262,6 +272,8 @@ Properties on Targets
    /prop_tgt/INTERFACE_COMPILE_DEFINITIONS
    /prop_tgt/INTERFACE_COMPILE_FEATURES
    /prop_tgt/INTERFACE_COMPILE_OPTIONS
+   /prop_tgt/INTERFACE_CXX_MODULE_SETS
+   /prop_tgt/INTERFACE_CXX_MODULE_HEADER_UNIT_SETS
    /prop_tgt/INTERFACE_HEADER_SETS
    /prop_tgt/INTERFACE_HEADER_SETS_TO_VERIFY
    /prop_tgt/INTERFACE_INCLUDE_DIRECTORIES

+ 17 - 0
Help/prop_tgt/CXX_MODULE_DIRS.rst

@@ -0,0 +1,17 @@
+CXX_MODULE_DIRS
+---------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of base directories of the target's default
+C++ module set (i.e. the file set with name and type ``CXX_MODULES``). The
+property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_DIRS_<NAME>` for the list of base directories in
+other C++ module sets.

+ 17 - 0
Help/prop_tgt/CXX_MODULE_DIRS_NAME.rst

@@ -0,0 +1,17 @@
+CXX_MODULE_DIRS_<NAME>
+----------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of base directories of the target's ``<NAME>`` C++
+module set, which has the set type ``CXX_MODULES``. The property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_DIRS` for the list of base directories in the
+default C++ module set. See :prop_tgt:`CXX_MODULE_SETS` for the file set names
+of all C++ module sets.

+ 17 - 0
Help/prop_tgt/CXX_MODULE_HEADER_UNIT_DIRS.rst

@@ -0,0 +1,17 @@
+CXX_MODULE_HEADER_UNIT_DIRS
+---------------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of base directories of the target's default C++
+module header set (i.e. the file set with name and type
+``CXX_MODULE_HEADER_UNITS``). The property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_HEADER_UNIT_DIRS_<NAME>` for the list of base directories
+in other C++ module header sets.

+ 19 - 0
Help/prop_tgt/CXX_MODULE_HEADER_UNIT_DIRS_NAME.rst

@@ -0,0 +1,19 @@
+CXX_MODULE_HEADER_UNIT_DIRS_<NAME>
+----------------------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of base directories of the target's ``<NAME>`` C++
+module header set, which has the set type ``CXX_MODULE_HEADER_UNITS``. The
+property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_HEADER_UNIT_DIRS` for the list of base directories
+in the default C++ module header set. See
+:prop_tgt:`CXX_MODULE_HEADER_UNIT_SETS` for the file set names of all C++
+module header sets.

+ 18 - 0
Help/prop_tgt/CXX_MODULE_HEADER_UNIT_SET.rst

@@ -0,0 +1,18 @@
+CXX_MODULE_HEADER_UNIT_SET
+--------------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of files in the target's default C++ module header
+set, (i.e. the file set with name and type ``CXX_MODULE_HEADER_UNITS``). If
+any of the paths are relative, they are computed relative to the target's
+source directory. The property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_HEADER_UNIT_SET_<NAME>` for the list of files in
+other C++ module header sets.

+ 18 - 0
Help/prop_tgt/CXX_MODULE_HEADER_UNIT_SETS.rst

@@ -0,0 +1,18 @@
+CXX_MODULE_HEADER_UNIT_SETS
+---------------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Read-only list of the target's ``PRIVATE`` and ``PUBLIC`` C++ module header
+sets (i.e. all file sets with the type ``CXX_MODULE_HEADER_UNITS``). Files
+listed in these file sets are treated as source files for the purpose of IDE
+integration.
+
+C++ module header sets may be defined using the :command:`target_sources`
+command ``FILE_SET`` option with type ``CXX_MODULE_HEADER_UNITS``.
+
+See also :prop_tgt:`CXX_MODULE_HEADER_UNIT_SET_<NAME>`,
+:prop_tgt:`CXX_MODULE_HEADER_UNIT_SET` and
+:prop_tgt:`INTERFACE_CXX_MODULE_HEADER_UNIT_SETS`.

+ 19 - 0
Help/prop_tgt/CXX_MODULE_HEADER_UNIT_SET_NAME.rst

@@ -0,0 +1,19 @@
+CXX_MODULE_HEADER_UNIT_SET_<NAME>
+---------------------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of files in the target's ``<NAME>`` C++ module header
+set, which has the set type ``CXX_MODULE_HEADER_UNITS``. If any of the paths
+are relative, they are computed relative to the target's source directory. The
+property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_HEADER_UNIT_SET` for the list of files in the
+default C++ module header set. See :prop_tgt:`CXX_MODULE_HEADER_UNIT_SETS` for
+the file set names of all C++ module header sets.

+ 18 - 0
Help/prop_tgt/CXX_MODULE_SET.rst

@@ -0,0 +1,18 @@
+CXX_MODULE_SET
+--------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of files in the target's default C++ module set,
+(i.e. the file set with name and type ``CXX_MODULES``). If any of the paths
+are relative, they are computed relative to the target's source directory. The
+property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_SET_<NAME>` for the list of files in other C++
+module sets.

+ 16 - 0
Help/prop_tgt/CXX_MODULE_SETS.rst

@@ -0,0 +1,16 @@
+CXX_MODULE_SETS
+---------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Read-only list of the target's ``PRIVATE`` and ``PUBLIC`` C++ module sets (i.e.
+all file sets with the type ``CXX_MODULES``). Files listed in these file sets are
+treated as source files for the purpose of IDE integration.
+
+C++ module sets may be defined using the :command:`target_sources` command
+``FILE_SET`` option with type ``CXX_MODULES``.
+
+See also :prop_tgt:`CXX_MODULE_SET_<NAME>`, :prop_tgt:`CXX_MODULE_SET` and
+:prop_tgt:`INTERFACE_CXX_MODULE_SETS`.

+ 18 - 0
Help/prop_tgt/CXX_MODULE_SET_NAME.rst

@@ -0,0 +1,18 @@
+CXX_MODULE_SET_<NAME>
+---------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Semicolon-separated list of files in the target's ``<NAME>`` C++ module set,
+which has the set type ``CXX_MODULES``. If any of the paths are relative, they
+are computed relative to the target's source directory. The property supports
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+This property is normally only set by :command:`target_sources(FILE_SET)`
+rather than being manipulated directly.
+
+See :prop_tgt:`CXX_MODULE_SET` for the list of files in the default C++ module
+set. See :prop_tgt:`CXX_MODULE_SETS` for the file set names of all C++ module
+sets.

+ 16 - 0
Help/prop_tgt/INTERFACE_CXX_MODULE_HEADER_UNIT_SETS.rst

@@ -0,0 +1,16 @@
+INTERFACE_CXX_MODULE_HEADER_UNIT_SETS
+-------------------------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Read-only list of the target's ``PUBLIC`` C++ module header sets (i.e. all
+file sets with the type ``CXX_MODULE_HEADER_UNITS``). Files listed in these
+C++ module header sets can be installed with :command:`install(TARGETS)` and
+exported with :command:`install(EXPORT)` and :command:`export`.
+
+C++ module header sets may be defined using the :command:`target_sources`
+command ``FILE_SET`` option with type ``CXX_MODULE_HEADER_UNITS``.
+
+See also :prop_tgt:`CXX_MODULE_HEADER_UNIT_SETS`.

+ 16 - 0
Help/prop_tgt/INTERFACE_CXX_MODULE_SETS.rst

@@ -0,0 +1,16 @@
+INTERFACE_CXX_MODULE_SETS
+-------------------------
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Read-only list of the target's ``PUBLIC`` C++ module sets (i.e. all file sets
+with the type ``CXX_MODULES``). Files listed in these C++ module sets can be
+installed with :command:`install(TARGETS)` and exported with
+:command:`install(EXPORT)` and :command:`export`.
+
+C++ module sets may be defined using the :command:`target_sources` command
+``FILE_SET`` option with type ``CXX_MODULES``.
+
+See also :prop_tgt:`CXX_MODULE_SETS`.

+ 32 - 0
Source/cmExportBuildFileGenerator.cxx

@@ -9,7 +9,9 @@
 #include <sstream>
 #include <utility>
 
+#include <cm/string_view>
 #include <cmext/algorithm>
+#include <cmext/string_view>
 
 #include "cmExportSet.h"
 #include "cmFileSet.h"
@@ -382,6 +384,21 @@ std::string cmExportBuildFileGenerator::GetFileSetDirectories(
       std::any_of(directoryEntries.begin(), directoryEntries.end(),
                   EntryIsContextSensitive);
 
+    auto const& type = fileSet->GetType();
+    // C++ modules do not support interface file sets which are dependent upon
+    // the configuration.
+    if (contextSensitive &&
+        (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s)) {
+      auto* mf = this->LG->GetMakefile();
+      std::ostringstream e;
+      e << "The \"" << gte->GetName() << "\" target's interface file set \""
+        << fileSet->GetName() << "\" of type \"" << type
+        << "\" contains context-sensitive base directory entries which is not "
+           "supported.";
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return std::string{};
+    }
+
     for (auto const& directory : directories) {
       auto dest = cmOutputConverter::EscapeForCMake(
         directory, cmOutputConverter::WrapQuotes::NoWrap);
@@ -427,6 +444,21 @@ std::string cmExportBuildFileGenerator::GetFileSetFiles(cmGeneratorTarget* gte,
       std::any_of(fileEntries.begin(), fileEntries.end(),
                   EntryIsContextSensitive);
 
+    auto const& type = fileSet->GetType();
+    // C++ modules do not support interface file sets which are dependent upon
+    // the configuration.
+    if (contextSensitive &&
+        (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s)) {
+      auto* mf = this->LG->GetMakefile();
+      std::ostringstream e;
+      e << "The \"" << gte->GetName() << "\" target's interface file set \""
+        << fileSet->GetName() << "\" of type \"" << type
+        << "\" contains context-sensitive file entries which is not "
+           "supported.";
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return std::string{};
+    }
+
     for (auto const& it : files) {
       for (auto const& filename : it.second) {
         auto escapedFile = cmOutputConverter::EscapeForCMake(

+ 34 - 0
Source/cmExportInstallFileGenerator.cxx

@@ -7,6 +7,9 @@
 #include <sstream>
 #include <utility>
 
+#include <cm/string_view>
+#include <cmext/string_view>
+
 #include "cmExportSet.h"
 #include "cmFileSet.h"
 #include "cmGeneratedFileStream.h"
@@ -18,6 +21,7 @@
 #include "cmInstallTargetGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
+#include "cmMessageType.h"
 #include "cmOutputConverter.h"
 #include "cmPolicies.h"
 #include "cmStateTypes.h"
@@ -562,6 +566,21 @@ std::string cmExportInstallFileGenerator::GetFileSetDirectories(
                            cge->Evaluate(gte->LocalGenerator, config, gte),
                            cmOutputConverter::WrapQuotes::NoWrap));
 
+    auto const& type = fileSet->GetType();
+    // C++ modules do not support interface file sets which are dependent upon
+    // the configuration.
+    if (cge->GetHadContextSensitiveCondition() &&
+        (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s)) {
+      auto* mf = this->IEGen->GetLocalGenerator()->GetMakefile();
+      std::ostringstream e;
+      e << "The \"" << gte->GetName() << "\" target's interface file set \""
+        << fileSet->GetName() << "\" of type \"" << type
+        << "\" contains context-sensitive base file entries which is not "
+           "supported.";
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return std::string{};
+    }
+
     if (cge->GetHadContextSensitiveCondition() && configs.size() != 1) {
       resultVector.push_back(
         cmStrCat("\"$<$<CONFIG:", config, ">:", dest, ">\""));
@@ -610,6 +629,21 @@ std::string cmExportInstallFileGenerator::GetFileSetFiles(
       std::any_of(fileEntries.begin(), fileEntries.end(),
                   EntryIsContextSensitive);
 
+    auto const& type = fileSet->GetType();
+    // C++ modules do not support interface file sets which are dependent upon
+    // the configuration.
+    if (contextSensitive &&
+        (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s)) {
+      auto* mf = this->IEGen->GetLocalGenerator()->GetMakefile();
+      std::ostringstream e;
+      e << "The \"" << gte->GetName() << "\" target's interface file set \""
+        << fileSet->GetName() << "\" of type \"" << type
+        << "\" contains context-sensitive base file entries which is not "
+           "supported.";
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return std::string{};
+    }
+
     for (auto const& it : files) {
       auto prefix = it.first.empty() ? "" : cmStrCat(it.first, '/');
       for (auto const& filename : it.second) {

+ 89 - 1
Source/cmGeneratorTarget.cxx

@@ -1707,7 +1707,8 @@ void addFileSetEntry(cmGeneratorTarget const* headTarget,
         }
       }
       if (!found) {
-        if (fileSet->GetType() == "HEADERS"_s) {
+        if (fileSet->GetType() == "HEADERS"_s ||
+            fileSet->GetType() == "CXX_MODULE_HEADER_UNITS"_s) {
           headTarget->Makefile->GetOrCreateSourceGroup("Header Files")
             ->AddGroupFile(path);
         }
@@ -1728,6 +1729,20 @@ void AddFileSetEntries(cmGeneratorTarget const* headTarget,
       addFileSetEntry(headTarget, config, dagChecker, headerSet, entries);
     }
   }
+  for (auto const& entry : headTarget->Target->GetCxxModuleSetsEntries()) {
+    for (auto const& name : cmExpandedList(entry.Value)) {
+      auto const* cxxModuleSet = headTarget->Target->GetFileSet(name);
+      addFileSetEntry(headTarget, config, dagChecker, cxxModuleSet, entries);
+    }
+  }
+  for (auto const& entry :
+       headTarget->Target->GetCxxModuleHeaderSetsEntries()) {
+    for (auto const& name : cmExpandedList(entry.Value)) {
+      auto const* cxxModuleHeaderSet = headTarget->Target->GetFileSet(name);
+      addFileSetEntry(headTarget, config, dagChecker, cxxModuleHeaderSet,
+                      entries);
+    }
+  }
 }
 
 bool processSources(cmGeneratorTarget const* tgt,
@@ -8700,3 +8715,76 @@ std::string cmGeneratorTarget::GenerateHeaderSetVerificationFile(
 
   return filename;
 }
+
+bool cmGeneratorTarget::HaveCxx20ModuleSources() const
+{
+  auto const& fs_names = this->Target->GetAllFileSetNames();
+  return std::any_of(fs_names.begin(), fs_names.end(),
+                     [this](std::string const& name) -> bool {
+                       auto const* file_set = this->Target->GetFileSet(name);
+                       if (!file_set) {
+                         this->Makefile->IssueMessage(
+                           MessageType::INTERNAL_ERROR,
+                           cmStrCat("Target \"", this->Target->GetName(),
+                                    "\" is tracked to have file set \"", name,
+                                    "\", but it was not found."));
+                         return false;
+                       }
+
+                       auto const& fs_type = file_set->GetType();
+                       return fs_type == "CXX_MODULES"_s ||
+                         fs_type == "CXX_MODULE_HEADER_UNITS"_s;
+                     });
+}
+
+cmGeneratorTarget::Cxx20SupportLevel cmGeneratorTarget::HaveCxxModuleSupport(
+  std::string const& config) const
+{
+  auto const* state = this->Makefile->GetState();
+  if (!state->GetLanguageEnabled("CXX")) {
+    return Cxx20SupportLevel::MissingCxx;
+  }
+  cmStandardLevelResolver standardResolver(this->Makefile);
+  if (!standardResolver.HaveStandardAvailable(this, "CXX", config,
+                                              "cxx_std_20")) {
+    return Cxx20SupportLevel::NoCxx20;
+  }
+  if (!this->Makefile->IsOn("CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP")) {
+    return Cxx20SupportLevel::MissingExperimentalFlag;
+  }
+  return Cxx20SupportLevel::Supported;
+}
+
+void cmGeneratorTarget::CheckCxxModuleStatus(std::string const& config) const
+{
+  // Check for `CXX_MODULE*` file sets and a lack of support.
+  if (this->HaveCxx20ModuleSources()) {
+    switch (this->HaveCxxModuleSupport(config)) {
+      case cmGeneratorTarget::Cxx20SupportLevel::MissingCxx:
+        this->Makefile->IssueMessage(
+          MessageType::FATAL_ERROR,
+          cmStrCat("The \"", this->GetName(),
+                   "\" target has C++ module sources but the \"CXX\" language "
+                   "has not been enabled"));
+        break;
+      case cmGeneratorTarget::Cxx20SupportLevel::MissingExperimentalFlag:
+        this->Makefile->IssueMessage(
+          MessageType::FATAL_ERROR,
+          cmStrCat("The \"", this->GetName(),
+                   "\" target has C++ module sources but its experimental "
+                   "support has not been requested"));
+        break;
+      case cmGeneratorTarget::Cxx20SupportLevel::NoCxx20:
+        this->Makefile->IssueMessage(
+          MessageType::FATAL_ERROR,
+          cmStrCat(
+            "The \"", this->GetName(),
+            "\" target has C++ module sources but is not using at least "
+            "\"cxx_std_20\""));
+        break;
+      case cmGeneratorTarget::Cxx20SupportLevel::Supported:
+        // All is well.
+        break;
+    }
+  }
+}

+ 30 - 0
Source/cmGeneratorTarget.h

@@ -1196,4 +1196,34 @@ public:
     bool operator()(cmGeneratorTarget const* t1,
                     cmGeneratorTarget const* t2) const;
   };
+
+  // C++20 module support queries.
+
+  /**
+   * Query whether the target expects C++20 module support.
+   *
+   * This will inspect the target itself to see if C++20 module
+   * support is expected to work based on its sources.
+   */
+  bool HaveCxx20ModuleSources() const;
+
+  enum class Cxx20SupportLevel
+  {
+    // C++ is not available.
+    MissingCxx,
+    // The experimental feature is not available.
+    MissingExperimentalFlag,
+    // The target does not require at least C++20.
+    NoCxx20,
+    // C++20 modules are available and working.
+    Supported,
+  };
+  /**
+   * Query whether the target has C++20 module support available (regardless of
+   * whether it is required or not).
+   */
+  Cxx20SupportLevel HaveCxxModuleSupport(std::string const& config) const;
+
+  // Check C++ module status for the target.
+  void CheckCxxModuleStatus(std::string const& config) const;
 };

+ 15 - 0
Source/cmGlobalVisualStudio7Generator.cxx

@@ -395,12 +395,27 @@ void cmGlobalVisualStudio7Generator::WriteTargetsToSolution(
 {
   VisualStudioFolders.clear();
 
+  std::vector<std::string> configs =
+    root->GetMakefile()->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig);
+
   for (cmGeneratorTarget const* target : projectTargets) {
     if (!this->IsInSolution(target)) {
       continue;
     }
     bool written = false;
 
+    for (auto const& c : configs) {
+      target->CheckCxxModuleStatus(c);
+    }
+
+    if (target->HaveCxx20ModuleSources() && !this->SupportsCxxModuleDyndep()) {
+      root->GetMakefile()->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("The \"", target->GetName(),
+                 "\" target contains C++ module sources which are not "
+                 "supported by the generator"));
+    }
+
     // handle external vc project files
     cmValue expath = target->GetProperty("EXTERNAL_MSPROJECT");
     if (expath) {

+ 2 - 0
Source/cmGlobalVisualStudio7Generator.h

@@ -157,6 +157,8 @@ protected:
     cmValue typeGuid,
     const std::set<BT<std::pair<std::string, bool>>>& dependencies) = 0;
 
+  virtual bool SupportsCxxModuleDyndep() const { return false; }
+
   std::string ConvertToSolutionPath(const std::string& path);
 
   std::set<std::string> IsPartOfDefaultBuild(

+ 12 - 0
Source/cmGlobalXCodeGenerator.cxx

@@ -1376,6 +1376,18 @@ bool cmGlobalXCodeGenerator::CreateXCodeTarget(
     return true;
   }
 
+  for (std::string const& configName : this->CurrentConfigurationTypes) {
+    gtgt->CheckCxxModuleStatus(configName);
+  }
+
+  if (gtgt->HaveCxx20ModuleSources()) {
+    gtgt->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("The \"", gtgt->GetName(),
+               "\" target contains C++ module sources which are not "
+               "supported by the generator"));
+  }
+
   auto& gtgt_visited = this->CommandsVisited[gtgt];
   auto& deps = this->GetTargetDirectDepends(gtgt);
   for (auto& d : deps) {

+ 65 - 0
Source/cmMakefileTargetGenerator.cxx

@@ -21,6 +21,7 @@
 #include "cmComputeLinkInformation.h"
 #include "cmCustomCommand.h"
 #include "cmCustomCommandGenerator.h"
+#include "cmFileSet.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
@@ -46,6 +47,7 @@
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmTarget.h"
 #include "cmValue.h"
 #include "cmake.h"
 
@@ -190,6 +192,16 @@ void cmMakefileTargetGenerator::CreateRuleFile()
 
 void cmMakefileTargetGenerator::WriteTargetBuildRules()
 {
+  this->GeneratorTarget->CheckCxxModuleStatus(this->GetConfigName());
+
+  if (this->GeneratorTarget->HaveCxx20ModuleSources()) {
+    this->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("The \"", this->GeneratorTarget->GetName(),
+               "\" target contains C++ module sources which are not supported "
+               "by the generator"));
+  }
+
   // -- Write the custom commands for this target
 
   // Evaluates generator expressions and expands prop_value
@@ -302,6 +314,40 @@ void cmMakefileTargetGenerator::WriteTargetBuildRules()
     }
   }
 
+  std::map<std::string, std::string> file_set_map;
+
+  auto const* tgt = this->GeneratorTarget->Target;
+  for (auto const& name : tgt->GetAllFileSetNames()) {
+    auto const* file_set = tgt->GetFileSet(name);
+    if (!file_set) {
+      this->Makefile->IssueMessage(
+        MessageType::INTERNAL_ERROR,
+        cmStrCat("Target \"", tgt->GetName(),
+                 "\" is tracked to have file set \"", name,
+                 "\", but it was not found."));
+      continue;
+    }
+
+    auto fileEntries = file_set->CompileFileEntries();
+    auto directoryEntries = file_set->CompileDirectoryEntries();
+    auto directories = file_set->EvaluateDirectoryEntries(
+      directoryEntries, this->LocalGenerator, this->GetConfigName(),
+      this->GeneratorTarget);
+
+    std::map<std::string, std::vector<std::string>> files;
+    for (auto const& entry : fileEntries) {
+      file_set->EvaluateFileEntry(directories, files, entry,
+                                  this->LocalGenerator, this->GetConfigName(),
+                                  this->GeneratorTarget);
+    }
+
+    for (auto const& it : files) {
+      for (auto const& filename : it.second) {
+        file_set_map[filename] = file_set->GetType();
+      }
+    }
+  }
+
   std::vector<cmSourceFile const*> objectSources;
   this->GeneratorTarget->GetObjectSources(objectSources,
                                           this->GetConfigName());
@@ -314,6 +360,25 @@ void cmMakefileTargetGenerator::WriteTargetBuildRules()
       this->WriteObjectRuleFiles(*sf);
     }
   }
+
+  for (cmSourceFile const* sf : objectSources) {
+    auto const& path = sf->GetFullPath();
+    auto const it = file_set_map.find(path);
+    if (it != file_set_map.end()) {
+      auto const& file_set_type = it->second;
+      if (file_set_type == "CXX_MODULES"_s ||
+          file_set_type == "CXX_MODULE_HEADER_UNITS"_s) {
+        if (sf->GetLanguage() != "CXX"_s) {
+          this->Makefile->IssueMessage(
+            MessageType::FATAL_ERROR,
+            cmStrCat(
+              "Target \"", tgt->GetName(), "\" contains the source\n  ", path,
+              "\nin a file set of type \"", file_set_type,
+              R"(" but the source is not classified as a "CXX" source.)"));
+        }
+      }
+    }
+  }
 }
 
 void cmMakefileTargetGenerator::WriteCommonCodeRules()

+ 45 - 47
Source/cmNinjaTargetGenerator.cxx

@@ -36,7 +36,6 @@
 #include "cmRange.h"
 #include "cmRulePlaceholderExpander.h"
 #include "cmSourceFile.h"
-#include "cmStandardLevelResolver.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
@@ -153,17 +152,12 @@ std::string cmNinjaTargetGenerator::LanguageDyndepRule(
 bool cmNinjaTargetGenerator::NeedCxxModuleSupport(
   std::string const& lang, std::string const& config) const
 {
-  if (lang != "CXX") {
+  if (lang != "CXX"_s) {
     return false;
   }
-  if (!this->Makefile->IsOn("CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP")) {
-    return false;
-  }
-  cmGeneratorTarget const* tgt = this->GetGeneratorTarget();
-  cmStandardLevelResolver standardResolver(this->Makefile);
-  bool const uses_cxx20 =
-    standardResolver.HaveStandardAvailable(tgt, "CXX", config, "cxx_std_20");
-  return uses_cxx20 && this->GetGlobalGenerator()->CheckCxxModuleSupport();
+  return this->GetGeneratorTarget()->HaveCxxModuleSupport(config) ==
+    cmGeneratorTarget::Cxx20SupportLevel::Supported &&
+    this->GetGlobalGenerator()->CheckCxxModuleSupport();
 }
 
 bool cmNinjaTargetGenerator::NeedDyndep(std::string const& lang,
@@ -255,51 +249,53 @@ std::string cmNinjaTargetGenerator::ComputeFlagsForObject(
       flags, genexInterpreter.Evaluate(pchOptions, COMPILE_OPTIONS));
   }
 
-  if (this->NeedCxxModuleSupport(language, config)) {
-    auto const& path = source->GetFullPath();
-    auto const* tgt = this->GeneratorTarget->Target;
+  auto const& path = source->GetFullPath();
+  auto const* tgt = this->GeneratorTarget->Target;
 
-    std::string file_set_type;
+  std::string file_set_type;
 
-    for (auto const& name : tgt->GetAllFileSetNames()) {
-      auto const* file_set = tgt->GetFileSet(name);
-      if (!file_set) {
-        this->GetMakefile()->IssueMessage(
-          MessageType::INTERNAL_ERROR,
-          cmStrCat("Target `", tgt->GetName(),
-                   "` is tracked to have file set `", name,
-                   "`, but it was not found."));
-        continue;
-      }
+  for (auto const& name : tgt->GetAllFileSetNames()) {
+    auto const* file_set = tgt->GetFileSet(name);
+    if (!file_set) {
+      this->GetMakefile()->IssueMessage(
+        MessageType::INTERNAL_ERROR,
+        cmStrCat("Target \"", tgt->GetName(),
+                 "\" is tracked to have file set \"", name,
+                 "\", but it was not found."));
+      continue;
+    }
 
-      auto fileEntries = file_set->CompileFileEntries();
-      auto directoryEntries = file_set->CompileDirectoryEntries();
-      auto directories = file_set->EvaluateDirectoryEntries(
-        directoryEntries, this->LocalGenerator, config, this->GeneratorTarget);
+    auto fileEntries = file_set->CompileFileEntries();
+    auto directoryEntries = file_set->CompileDirectoryEntries();
+    auto directories = file_set->EvaluateDirectoryEntries(
+      directoryEntries, this->LocalGenerator, config, this->GeneratorTarget);
 
-      std::map<std::string, std::vector<std::string>> files;
-      for (auto const& entry : fileEntries) {
-        file_set->EvaluateFileEntry(directories, files, entry,
-                                    this->LocalGenerator, config,
-                                    this->GeneratorTarget);
-      }
+    std::map<std::string, std::vector<std::string>> files;
+    for (auto const& entry : fileEntries) {
+      file_set->EvaluateFileEntry(directories, files, entry,
+                                  this->LocalGenerator, config,
+                                  this->GeneratorTarget);
+    }
 
-      for (auto const& it : files) {
-        for (auto const& filename : it.second) {
-          if (filename == path) {
-            file_set_type = file_set->GetType();
-            break;
-          }
+    for (auto const& it : files) {
+      for (auto const& filename : it.second) {
+        if (filename == path) {
+          file_set_type = file_set->GetType();
+          break;
         }
       }
+    }
 
-      if (!file_set_type.empty()) {
-        std::string source_type_var = cmStrCat(
-          "CMAKE_EXPERIMENTAL_CXX_MODULE_SOURCE_TYPE_FLAG_", file_set_type);
-        cmMakefile* mf = this->GetMakefile();
-        if (cmValue source_type_flag = mf->GetDefinition(source_type_var)) {
-          this->LocalGenerator->AppendFlags(flags, *source_type_flag);
-        }
+    if (file_set_type == "CXX_MODULES"_s ||
+        file_set_type == "CXX_MODULE_HEADER_UNITS"_s) {
+      if (source->GetLanguage() != "CXX"_s) {
+        this->GetMakefile()->IssueMessage(
+          MessageType::FATAL_ERROR,
+          cmStrCat(
+            "Target \"", tgt->GetName(), "\" contains the source\n  ", path,
+            "\nin a file set of type \"", file_set_type,
+            R"(" but the source is not classified as a "CXX" source.)"));
+        continue;
       }
     }
   }
@@ -1038,6 +1034,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
   const std::string& config, const std::string& fileConfig,
   bool firstForConfig)
 {
+  this->GeneratorTarget->CheckCxxModuleStatus(config);
+
   // Write comments.
   cmGlobalNinjaGenerator::WriteDivider(this->GetImplFileStream(fileConfig));
   this->GetImplFileStream(fileConfig)

+ 79 - 0
Source/cmTarget.cxx

@@ -272,6 +272,8 @@ public:
   cmListFileBacktrace Backtrace;
 
   FileSetType HeadersFileSets;
+  FileSetType CxxModulesFileSets;
+  FileSetType CxxModuleHeadersFileSets;
 
   cmTargetInternals();
 
@@ -301,6 +303,19 @@ cmTargetInternals::cmTargetInternals()
                     "The default header set"_s, "Header set"_s,
                     FileSetEntries("HEADER_SETS"_s),
                     FileSetEntries("INTERFACE_HEADER_SETS"_s))
+  , CxxModulesFileSets("CXX_MODULES"_s, "CXX_MODULE_DIRS"_s,
+                       "CXX_MODULE_SET"_s, "CXX_MODULE_DIRS_"_s,
+                       "CXX_MODULE_SET_"_s, "C++ module"_s,
+                       "The default C++ module set"_s, "C++ module set"_s,
+                       FileSetEntries("CXX_MODULE_SETS"_s),
+                       FileSetEntries("INTERFACE_CXX_MODULE_SETS"_s))
+  , CxxModuleHeadersFileSets(
+      "CXX_MODULE_HEADER_UNITS"_s, "CXX_MODULE_HEADER_UNIT_DIRS"_s,
+      "CXX_MODULE_HEADER_UNIT_SET"_s, "CXX_MODULE_HEADER_UNIT_DIRS_"_s,
+      "CXX_MODULE_HEADER_UNIT_SET_"_s, "C++ module header"_s,
+      "The default C++ module header set"_s, "C++ module header set"_s,
+      FileSetEntries("CXX_MODULE_HEADER_UNIT_SETS"_s),
+      FileSetEntries("INTERFACE_CXX_MODULE_HEADER_UNIT_SETS"_s))
 {
 }
 
@@ -1366,11 +1381,32 @@ cmBTStringRange cmTarget::GetHeaderSetsEntries() const
   return cmMakeRange(this->impl->HeadersFileSets.SelfEntries.Entries);
 }
 
+cmBTStringRange cmTarget::GetCxxModuleSetsEntries() const
+{
+  return cmMakeRange(this->impl->CxxModulesFileSets.SelfEntries.Entries);
+}
+
+cmBTStringRange cmTarget::GetCxxModuleHeaderSetsEntries() const
+{
+  return cmMakeRange(this->impl->CxxModuleHeadersFileSets.SelfEntries.Entries);
+}
+
 cmBTStringRange cmTarget::GetInterfaceHeaderSetsEntries() const
 {
   return cmMakeRange(this->impl->HeadersFileSets.InterfaceEntries.Entries);
 }
 
+cmBTStringRange cmTarget::GetInterfaceCxxModuleSetsEntries() const
+{
+  return cmMakeRange(this->impl->CxxModulesFileSets.InterfaceEntries.Entries);
+}
+
+cmBTStringRange cmTarget::GetInterfaceCxxModuleHeaderSetsEntries() const
+{
+  return cmMakeRange(
+    this->impl->CxxModuleHeadersFileSets.InterfaceEntries.Entries);
+}
+
 namespace {
 #define MAKE_PROP(PROP) const std::string prop##PROP = #PROP
 MAKE_PROP(C_STANDARD);
@@ -1630,6 +1666,12 @@ void cmTarget::StoreProperty(const std::string& prop, ValueType value)
   } else if (this->impl->HeadersFileSets.WriteProperties(
                this, this->impl.get(), prop, value, true)) {
     /* Handled in the `if` condition. */
+  } else if (this->impl->CxxModulesFileSets.WriteProperties(
+               this, this->impl.get(), prop, value, true)) {
+    /* Handled in the `if` condition. */
+  } else if (this->impl->CxxModuleHeadersFileSets.WriteProperties(
+               this, this->impl.get(), prop, value, true)) {
+    /* Handled in the `if` condition. */
   } else {
     this->impl->Properties.SetProperty(prop, value);
   }
@@ -1741,6 +1783,13 @@ void cmTarget::AppendProperty(const std::string& prop,
     this->impl->Makefile->IssueMessage(
       MessageType::FATAL_ERROR, prop + " property may not be appended.");
   } else if (this->impl->HeadersFileSets.WriteProperties(
+               this, this->impl.get(), prop, value,
+               false)) { // NOLINT(bugprone-branch-clone)
+    /* Handled in the `if` condition. */
+  } else if (this->impl->CxxModulesFileSets.WriteProperties(
+               this, this->impl.get(), prop, value, false)) {
+    /* Handled in the `if` condition. */
+  } else if (this->impl->CxxModuleHeadersFileSets.WriteProperties(
                this, this->impl.get(), prop, value, false)) {
     /* Handled in the `if` condition. */
   } else {
@@ -2249,6 +2298,17 @@ cmValue cmTarget::GetProperty(const std::string& prop) const
     if (headers.first) {
       return headers.second;
     }
+    auto cxx_modules = this->impl->CxxModulesFileSets.ReadProperties(
+      this, this->impl.get(), prop);
+    if (cxx_modules.first) {
+      return cxx_modules.second;
+    }
+    auto cxx_module_headers =
+      this->impl->CxxModuleHeadersFileSets.ReadProperties(
+        this, this->impl.get(), prop);
+    if (cxx_module_headers.first) {
+      return cxx_module_headers.second;
+    }
   }
 
   cmValue retVal = this->impl->Properties.GetPropertyValue(prop);
@@ -2526,6 +2586,11 @@ std::pair<cmFileSet*, bool> cmTarget::GetOrCreateFileSet(
     auto bt = this->impl->Makefile->GetBacktrace();
     if (type == this->impl->HeadersFileSets.TypeName) {
       this->impl->HeadersFileSets.AddFileSet(name, vis, std::move(bt));
+    } else if (type == this->impl->CxxModulesFileSets.TypeName) {
+      this->impl->CxxModulesFileSets.AddFileSet(name, vis, std::move(bt));
+    } else if (type == this->impl->CxxModuleHeadersFileSets.TypeName) {
+      this->impl->CxxModuleHeadersFileSets.AddFileSet(name, vis,
+                                                      std::move(bt));
     }
   }
   return std::make_pair(&result.first->second, result.second);
@@ -2536,6 +2601,12 @@ std::string cmTarget::GetFileSetsPropertyName(const std::string& type)
   if (type == "HEADERS") {
     return "HEADER_SETS";
   }
+  if (type == "CXX_MODULES") {
+    return "CXX_MODULE_SETS";
+  }
+  if (type == "CXX_MODULE_HEADER_UNITS") {
+    return "CXX_MODULE_HEADER_UNIT_SETS";
+  }
   return "";
 }
 
@@ -2544,6 +2615,12 @@ std::string cmTarget::GetInterfaceFileSetsPropertyName(const std::string& type)
   if (type == "HEADERS") {
     return "INTERFACE_HEADER_SETS";
   }
+  if (type == "CXX_MODULES") {
+    return "INTERFACE_CXX_MODULE_SETS";
+  }
+  if (type == "CXX_MODULE_HEADER_UNITS") {
+    return "INTERFACE_CXX_MODULE_HEADER_UNIT_SETS";
+  }
   return "";
 }
 
@@ -2571,6 +2648,8 @@ std::vector<std::string> cmTarget::GetAllInterfaceFileSets() const
   };
 
   appendEntries(this->impl->HeadersFileSets.InterfaceEntries.Entries);
+  appendEntries(this->impl->CxxModulesFileSets.InterfaceEntries.Entries);
+  appendEntries(this->impl->CxxModuleHeadersFileSets.InterfaceEntries.Entries);
 
   return result;
 }

+ 4 - 0
Source/cmTarget.h

@@ -275,8 +275,12 @@ public:
   cmBTStringRange GetLinkInterfaceDirectExcludeEntries() const;
 
   cmBTStringRange GetHeaderSetsEntries() const;
+  cmBTStringRange GetCxxModuleSetsEntries() const;
+  cmBTStringRange GetCxxModuleHeaderSetsEntries() const;
 
   cmBTStringRange GetInterfaceHeaderSetsEntries() const;
+  cmBTStringRange GetInterfaceCxxModuleSetsEntries() const;
+  cmBTStringRange GetInterfaceCxxModuleHeaderSetsEntries() const;
 
   std::string ImportedGetFullPath(const std::string& config,
                                   cmStateEnums::ArtifactType artifact) const;

+ 26 - 4
Source/cmTargetSourcesCommand.cxx

@@ -9,6 +9,7 @@
 #include <cmext/string_view>
 
 #include "cmArgumentParser.h"
+#include "cmExperimental.h"
 #include "cmFileSet.h"
 #include "cmGeneratorExpression.h"
 #include "cmListFileCache.h"
@@ -256,9 +257,30 @@ bool TargetSourcesImpl::HandleOneFileSet(
       this->SetError("Must specify a TYPE when creating file set");
       return false;
     }
-    if (type != "HEADERS"_s) {
-      this->SetError("File set TYPE may only be \"HEADERS\"");
-      return false;
+    bool const supportCxx20FileSetTypes = cmExperimental::HasSupportEnabled(
+      *this->Makefile, cmExperimental::Feature::CxxModuleCMakeApi);
+
+    if (supportCxx20FileSetTypes) {
+      if (type != "HEADERS"_s && type != "CXX_MODULES"_s &&
+          type != "CXX_MODULE_HEADER_UNITS"_s) {
+        this->SetError(
+          R"(File set TYPE may only be "HEADERS", "CXX_MODULES", or "CXX_MODULE_HEADER_UNITS")");
+        return false;
+      }
+
+      if (cmFileSetVisibilityIsForInterface(visibility) &&
+          !cmFileSetVisibilityIsForSelf(visibility)) {
+        if (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s) {
+          this->SetError(
+            R"(File set TYPEs "CXX_MODULES" and "CXX_MODULE_HEADER_UNITS" may not have "INTERFACE" visibility)");
+          return false;
+        }
+      }
+    } else {
+      if (type != "HEADERS"_s) {
+        this->SetError("File set TYPE may only be \"HEADERS\"");
+        return false;
+      }
     }
 
     if (args.BaseDirs.empty()) {
@@ -294,7 +316,7 @@ bool TargetSourcesImpl::HandleOneFileSet(
   if (!baseDirectories.empty()) {
     fileSet.first->AddDirectoryEntry(
       BT<std::string>(baseDirectories, this->Makefile->GetBacktrace()));
-    if (type == "HEADERS"_s) {
+    if (type == "HEADERS"_s || type == "CXX_MODULE_HEADER_UNITS"_s) {
       for (auto const& dir : cmExpandedList(baseDirectories)) {
         auto interfaceDirectoriesGenex =
           cmStrCat("$<BUILD_INTERFACE:", dir, ">");

+ 12 - 0
Source/cmVisualStudio10TargetGenerator.cxx

@@ -347,6 +347,18 @@ std::ostream& cmVisualStudio10TargetGenerator::Elem::WriteString(
 
 void cmVisualStudio10TargetGenerator::Generate()
 {
+  for (std::string const& config : this->Configurations) {
+    this->GeneratorTarget->CheckCxxModuleStatus(config);
+  }
+
+  if (this->GeneratorTarget->HaveCxx20ModuleSources()) {
+    this->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("The \"", this->GeneratorTarget->GetName(),
+               "\" target contains C++ module sources which are not supported "
+               "by the generator"));
+  }
+
   this->ProjectType = this->ComputeProjectType(this->GeneratorTarget);
   this->Managed = this->ProjectType == VsProjectType::csproj;
   const std::string ProjectFileExtension =

+ 3 - 0
Tests/RunCMake/CMakeLists.txt

@@ -564,6 +564,9 @@ if(CMake_TEST_CUDA)
 endif()
 add_RunCMake_test(DependencyGraph -DCMAKE_Fortran_COMPILER=${CMAKE_Fortran_COMPILER})
 
+# Add C++ Module tests.
+add_RunCMake_test(CXXModules)
+
 # ctresalloc links against CMakeLib and CTestLib, which means it can't be built
 # if CMake_TEST_EXTERNAL_CMAKE is activated (the compiler might be different.)
 # So, it has to be provided in the original build tree.

+ 6 - 0
Tests/RunCMake/CXXModules/CMakeLists.txt

@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.23)
+project(${RunCMake_TEST} NONE)
+
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "17be90bd-a850-44e0-be50-448de847d652")
+
+include(${RunCMake_TEST}.cmake)

+ 1 - 0
Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsInterface-result.txt

@@ -0,0 +1 @@
+1

+ 12 - 0
Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsInterface-stderr.txt

@@ -0,0 +1,12 @@
+CMake Warning \(dev\) at FileSetModuleHeaderUnitsInterface.cmake:2 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error at FileSetModuleHeaderUnitsInterface.cmake:2 \(target_sources\):
+  target_sources File set TYPEs "CXX_MODULES" and "CXX_MODULE_HEADER_UNITS"
+  may not have "INTERFACE" visibility
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)

+ 8 - 0
Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsInterface.cmake

@@ -0,0 +1,8 @@
+add_library(module-header)
+target_sources(module-header
+  INTERFACE
+    FILE_SET fs TYPE CXX_MODULE_HEADER_UNITS FILES
+      sources/module-header.h)
+target_compile_features(module-header
+  PRIVATE
+    cxx_std_20)

+ 11 - 0
Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPrivate-stderr.txt

@@ -0,0 +1,11 @@
+CMake Warning \(dev\) at FileSetModuleHeaderUnitsPrivate.cmake:7 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 13 - 0
Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPrivate.cmake

@@ -0,0 +1,13 @@
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(module-header
+  sources/cxx-anchor.cxx)
+target_sources(module-header
+  PRIVATE
+    FILE_SET fs TYPE CXX_MODULE_HEADER_UNITS FILES
+      sources/module-header.h)
+target_compile_features(module-header
+  PRIVATE
+    cxx_std_20)

+ 11 - 0
Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPublic-stderr.txt

@@ -0,0 +1,11 @@
+CMake Warning \(dev\) at FileSetModuleHeaderUnitsPublic.cmake:7 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 13 - 0
Tests/RunCMake/CXXModules/FileSetModuleHeaderUnitsPublic.cmake

@@ -0,0 +1,13 @@
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(module-header
+  sources/cxx-anchor.cxx)
+target_sources(module-header
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULE_HEADER_UNITS FILES
+      sources/module-header.h)
+target_compile_features(module-header
+  PRIVATE
+    cxx_std_20)

+ 1 - 0
Tests/RunCMake/CXXModules/FileSetModulesInterface-result.txt

@@ -0,0 +1 @@
+1

+ 12 - 0
Tests/RunCMake/CXXModules/FileSetModulesInterface-stderr.txt

@@ -0,0 +1,12 @@
+CMake Warning \(dev\) at FileSetModulesInterface.cmake:2 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error at FileSetModulesInterface.cmake:2 \(target_sources\):
+  target_sources File set TYPEs "CXX_MODULES" and "CXX_MODULE_HEADER_UNITS"
+  may not have "INTERFACE" visibility
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)

+ 8 - 0
Tests/RunCMake/CXXModules/FileSetModulesInterface.cmake

@@ -0,0 +1,8 @@
+add_library(module)
+target_sources(module
+  INTERFACE
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(module
+  PRIVATE
+    cxx_std_20)

+ 11 - 0
Tests/RunCMake/CXXModules/FileSetModulesPrivate-stderr.txt

@@ -0,0 +1,11 @@
+CMake Warning \(dev\) at FileSetModulesPrivate.cmake:6 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 12 - 0
Tests/RunCMake/CXXModules/FileSetModulesPrivate.cmake

@@ -0,0 +1,12 @@
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(module)
+target_sources(module
+  PRIVATE
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(module
+  PRIVATE
+    cxx_std_20)

+ 11 - 0
Tests/RunCMake/CXXModules/FileSetModulesPublic-stderr.txt

@@ -0,0 +1,11 @@
+CMake Warning \(dev\) at FileSetModulesPublic.cmake:6 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 12 - 0
Tests/RunCMake/CXXModules/FileSetModulesPublic.cmake

@@ -0,0 +1,12 @@
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(module)
+target_sources(module
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(module
+  PRIVATE
+    cxx_std_20)

+ 1 - 0
Tests/RunCMake/CXXModules/NoCXX-result.txt

@@ -0,0 +1 @@
+1

+ 20 - 0
Tests/RunCMake/CXXModules/NoCXX-stderr.txt

@@ -0,0 +1,20 @@
+CMake Warning \(dev\) at NoCXX.cmake:4 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error in CMakeLists.txt:
+  The "nocxx" target has C\+\+ module sources but the "CXX" language has not
+  been enabled
+
+(
+CMake Error in CMakeLists.txt:
+(  The "nocxx" target has C\+\+ module sources but the "CXX" language has not
+  been enabled
+|  The "nocxx" target contains C\+\+ module sources which are not supported by
+  the generator
+)
+)*
+CMake Generate step failed.  Build files cannot be regenerated correctly.

+ 9 - 0
Tests/RunCMake/CXXModules/NoCXX.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+
+add_library(nocxx)
+target_sources(nocxx
+  PRIVATE
+    sources/c-anchor.c
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)

+ 1 - 0
Tests/RunCMake/CXXModules/NoCXX20-result.txt

@@ -0,0 +1 @@
+1

+ 20 - 0
Tests/RunCMake/CXXModules/NoCXX20-stderr.txt

@@ -0,0 +1,20 @@
+CMake Warning \(dev\) at NoCXX20.cmake:4 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error in CMakeLists.txt:
+  The "nocxx20" target has C\+\+ module sources but is not using at least
+  "cxx_std_20"
+
+(
+CMake Error in CMakeLists.txt:
+(  The "nocxx20" target has C\+\+ module sources but is not using at least
+  "cxx_std_20"
+|  The "nocxx20" target contains C\+\+ module sources which are not supported by
+  the generator
+)
+)*
+CMake Generate step failed.  Build files cannot be regenerated correctly.

+ 10 - 0
Tests/RunCMake/CXXModules/NoCXX20.cmake

@@ -0,0 +1,10 @@
+enable_language(CXX)
+
+add_library(nocxx20)
+target_sources(nocxx20
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(nocxx20
+  PRIVATE
+    cxx_std_17)

+ 1 - 0
Tests/RunCMake/CXXModules/NoCXX20ModuleFlag-result.txt

@@ -0,0 +1 @@
+1

+ 20 - 0
Tests/RunCMake/CXXModules/NoCXX20ModuleFlag-stderr.txt

@@ -0,0 +1,20 @@
+CMake Warning \(dev\) at NoCXX20ModuleFlag.cmake:4 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error in CMakeLists.txt:
+  The "noexperimentalflag" target has C\+\+ module sources but its experimental
+  support has not been requested
+
+(
+CMake Error in CMakeLists.txt:
+(  The "noexperimentalflag" target has C\+\+ module sources but its experimental
+  support has not been requested
+|  The "noexperimentalflag" target contains C\+\+ module sources which are not
+  supported by the generator
+)
+)*
+CMake Generate step failed.  Build files cannot be regenerated correctly.

+ 10 - 0
Tests/RunCMake/CXXModules/NoCXX20ModuleFlag.cmake

@@ -0,0 +1,10 @@
+enable_language(CXX)
+
+add_library(noexperimentalflag)
+target_sources(noexperimentalflag
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(noexperimentalflag
+  PRIVATE
+    cxx_std_20)

+ 1 - 0
Tests/RunCMake/CXXModules/NoDyndepSupport-result.txt

@@ -0,0 +1 @@
+1

+ 30 - 0
Tests/RunCMake/CXXModules/NoDyndepSupport-stderr.txt

@@ -0,0 +1,30 @@
+CMake Warning \(dev\) at NoDyndepSupport.cmake:10 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+(CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error:
+  The Ninja generator does not support C\+\+20 modules using Ninja version
+
+    .*
+
+  due to lack of required features.  Ninja 1.10 or higher is required.
+
+|CMake Error in CMakeLists.txt:
+  The "nodyndep" target contains C\+\+ module sources which are not supported
+  by the generator
+
+(
+CMake Error in CMakeLists.txt:
+  The "nodyndep" target contains C\+\+ module sources which are not supported
+  by the generator
+
+)*)
+CMake Generate step failed.  Build files cannot be regenerated correctly.

+ 16 - 0
Tests/RunCMake/CXXModules/NoDyndepSupport.cmake

@@ -0,0 +1,16 @@
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+if (NOT CMAKE_CXX_STANDARD_DEFAULT)
+  set(CMAKE_CXX_STANDARD_DEFAULT "11")
+endif ()
+
+add_library(nodyndep)
+target_sources(nodyndep
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(nodyndep
+  PRIVATE
+    cxx_std_20)

+ 1 - 0
Tests/RunCMake/CXXModules/NotCXXSourceModuleHeaderUnits-result.txt

@@ -0,0 +1 @@
+1

+ 22 - 0
Tests/RunCMake/CXXModules/NotCXXSourceModuleHeaderUnits-stderr.txt

@@ -0,0 +1,22 @@
+CMake Warning \(dev\) at NotCXXSourceModuleHeaderUnits.cmake:7 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error in CMakeLists.txt:
+  Target "not-cxx-source" contains the source
+
+    .*/Tests/RunCMake/CXXModules/sources/c-anchor.c
+
+  in a file set of type "CXX_MODULE_HEADER_UNITS" but the source is not
+  classified as a "CXX" source.
+
+
+CMake Generate step failed.  Build files cannot be regenerated correctly.

+ 15 - 0
Tests/RunCMake/CXXModules/NotCXXSourceModuleHeaderUnits.cmake

@@ -0,0 +1,15 @@
+enable_language(C)
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(not-cxx-source)
+target_sources(not-cxx-source
+  PRIVATE
+    sources/cxx-anchor.cxx
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULE_HEADER_UNITS FILES
+      sources/c-anchor.c)
+target_compile_features(not-cxx-source
+  PRIVATE
+    cxx_std_20)

+ 1 - 0
Tests/RunCMake/CXXModules/NotCXXSourceModules-result.txt

@@ -0,0 +1 @@
+1

+ 17 - 0
Tests/RunCMake/CXXModules/NotCXXSourceModules-stderr.txt

@@ -0,0 +1,17 @@
+CMake Warning \(dev\) at NotCXXSourceModules.cmake:7 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error in CMakeLists.txt:
+  Target "not-cxx-source" contains the source
+
+    .*/Tests/RunCMake/CXXModules/sources/c-anchor.c
+
+  in a file set of type "CXX_MODULES" but the source is not classified as a
+  "CXX" source.
+
+
+CMake Generate step failed.  Build files cannot be regenerated correctly.

+ 13 - 0
Tests/RunCMake/CXXModules/NotCXXSourceModules.cmake

@@ -0,0 +1,13 @@
+enable_language(C)
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(not-cxx-source)
+target_sources(not-cxx-source
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/c-anchor.c)
+target_compile_features(not-cxx-source
+  PRIVATE
+    cxx_std_20)

+ 67 - 0
Tests/RunCMake/CXXModules/RunCMakeTest.cmake

@@ -0,0 +1,67 @@
+include(RunCMake)
+
+# For `if (IN_LIST)`
+cmake_policy(SET CMP0057 NEW)
+
+run_cmake(compiler_introspection)
+include("${RunCMake_BINARY_DIR}/compiler_introspection-build/info.cmake")
+
+# Test negative cases where C++20 modules do not work.
+run_cmake(NoCXX)
+if ("cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
+  # This test requires that the compiler be told to compile in an older-than-20
+  # standard. If the compiler forces a standard to be used, skip it.
+  if (NOT forced_cxx_standard)
+    run_cmake(NoCXX20)
+  endif ()
+
+  # This test uses C++20, but another prerequisite is missing, so forced
+  # standards don't matter.
+  run_cmake(NoCXX20ModuleFlag)
+endif ()
+
+if (RunCMake_GENERATOR MATCHES "Ninja")
+  execute_process(
+    COMMAND "${CMAKE_MAKE_PROGRAM}" --version
+    RESULT_VARIABLE res
+    OUTPUT_VARIABLE ninja_version
+    ERROR_VARIABLE err
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    ERROR_STRIP_TRAILING_WHITESPACE)
+
+  if (res)
+    message(WARNING
+      "Failed to determine `ninja` version: ${err}")
+    set(ninja_version "0")
+  endif ()
+endif ()
+
+# Test behavior when the generator does not support C++20 modules.
+if (NOT RunCMake_GENERATOR MATCHES "Ninja" OR
+    ninja_version VERSION_LESS "1.10" OR
+    NOT "cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
+  if ("cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
+    run_cmake(NoDyndepSupport)
+  endif ()
+
+  # Bail; the remaining tests require the generator to successfully generate
+  # with C++20 modules in the source list.
+  return ()
+endif ()
+
+set(fileset_types
+  Modules
+  ModuleHeaderUnits)
+set(scopes
+  Interface
+  Private
+  Public)
+foreach (fileset_type IN LISTS fileset_types)
+  foreach (scope IN LISTS scopes)
+    run_cmake("FileSet${fileset_type}${scope}")
+  endforeach ()
+
+  # Test the error message when a non-C++ source file is found in the source
+  # list.
+  run_cmake("NotCXXSource${fileset_type}")
+endforeach ()

+ 25 - 0
Tests/RunCMake/CXXModules/compiler_introspection.cmake

@@ -0,0 +1,25 @@
+enable_language(CXX)
+
+set(info "")
+
+# See `Modules/Compiler/MSVC-CXX.cmake` for this. If there is explicitly no
+# default, the feature list is populated to be everything.
+if (DEFINED CMAKE_CXX_STANDARD_DEFAULT AND
+    CMAKE_CXX_STANDARD_DEFAULT STREQUAL "")
+  set(CMAKE_CXX_COMPILE_FEATURES "")
+endif ()
+
+# Detect if the environment forces a C++ standard, let the test selection know.
+set(forced_cxx_standard 0)
+if (CMAKE_CXX_FLAGS MATCHES "-std=")
+  set(forced_cxx_standard 1)
+endif ()
+
+# Forward information about the C++ compile features.
+string(APPEND info "\
+set(CMAKE_CXX_COMPILE_FEATURES \"${CMAKE_CXX_COMPILE_FEATURES}\")
+set(CMAKE_MAKE_PROGRAM \"${CMAKE_MAKE_PROGRAM}\")
+set(forced_cxx_standard \"${forced_cxx_standard}\")
+")
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/info.cmake" "${info}")

+ 4 - 0
Tests/RunCMake/CXXModules/sources/c-anchor.c

@@ -0,0 +1,4 @@
+int c_anchor()
+{
+  return 0;
+}

+ 4 - 0
Tests/RunCMake/CXXModules/sources/cxx-anchor.cxx

@@ -0,0 +1,4 @@
+int cxx_anchor()
+{
+  return 0;
+}

+ 9 - 0
Tests/RunCMake/CXXModules/sources/module-header.h

@@ -0,0 +1,9 @@
+#ifndef module_header_h
+#define module_header_h
+
+inline int h()
+{
+  return 0;
+}
+
+#endif

+ 6 - 0
Tests/RunCMake/CXXModules/sources/module-impl.cxx

@@ -0,0 +1,6 @@
+module M;
+
+int f()
+{
+  return 0;
+}

+ 11 - 0
Tests/RunCMake/CXXModules/sources/module-internal-part-impl.cxx

@@ -0,0 +1,11 @@
+#ifdef _MSC_VER
+// Only MSVC supports this pattern.
+module M : internal_part;
+#else
+module M;
+#endif
+
+int i()
+{
+  return 0;
+}

+ 3 - 0
Tests/RunCMake/CXXModules/sources/module-internal-part.cxx

@@ -0,0 +1,3 @@
+module M : internal_part;
+
+int i();

+ 13 - 0
Tests/RunCMake/CXXModules/sources/module-part-impl.cxx

@@ -0,0 +1,13 @@
+#ifdef _MSC_VER
+// Only MSVC supports this pattern.
+module M : part;
+#else
+module M;
+#endif
+
+import M : internal_part;
+
+int p()
+{
+  return i();
+}

+ 3 - 0
Tests/RunCMake/CXXModules/sources/module-part.cxx

@@ -0,0 +1,3 @@
+export module M : part;
+
+int p();

+ 6 - 0
Tests/RunCMake/CXXModules/sources/module-use.cxx

@@ -0,0 +1,6 @@
+import M;
+
+int main(int argc, char* argv[])
+{
+  return f() + p();
+}

+ 5 - 0
Tests/RunCMake/CXXModules/sources/module.cxx

@@ -0,0 +1,5 @@
+export module M;
+export import M : part;
+import M : internal_part;
+
+int f();

+ 1 - 0
Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental-result.txt

@@ -0,0 +1 @@
+1

+ 12 - 0
Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental-stderr.txt

@@ -0,0 +1,12 @@
+^CMake Warning \(dev\) at FileSetDefaultWrongTypeExperimental.cmake:6 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error at FileSetDefaultWrongTypeExperimental\.cmake:[0-9]+ \(target_sources\):
+  target_sources File set TYPE may only be "HEADERS", "CXX_MODULES", or
+  "CXX_MODULE_HEADER_UNITS"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 6 - 0
Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental.cmake

@@ -0,0 +1,6 @@
+enable_language(C)
+
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "17be90bd-a850-44e0-be50-448de847d652")
+
+add_library(lib1 STATIC empty.c)
+target_sources(lib1 PRIVATE FILE_SET UNKNOWN)

+ 1 - 0
Tests/RunCMake/target_sources/FileSetWrongTypeExperimental-result.txt

@@ -0,0 +1 @@
+1

+ 12 - 0
Tests/RunCMake/target_sources/FileSetWrongTypeExperimental-stderr.txt

@@ -0,0 +1,12 @@
+^CMake Warning \(dev\) at FileSetWrongTypeExperimental.cmake:6 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error at FileSetWrongTypeExperimental\.cmake:[0-9]+ \(target_sources\):
+  target_sources File set TYPE may only be "HEADERS", "CXX_MODULES", or
+  "CXX_MODULE_HEADER_UNITS"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 6 - 0
Tests/RunCMake/target_sources/FileSetWrongTypeExperimental.cmake

@@ -0,0 +1,6 @@
+enable_language(C)
+
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "17be90bd-a850-44e0-be50-448de847d652")
+
+add_library(lib1 STATIC empty.c)
+target_sources(lib1 PRIVATE FILE_SET a TYPE UNKNOWN)

+ 2 - 0
Tests/RunCMake/target_sources/RunCMakeTest.cmake

@@ -26,6 +26,8 @@ run_cmake(FileSetProperties)
 run_cmake(FileSetNoType)
 run_cmake(FileSetWrongType)
 run_cmake(FileSetDefaultWrongType)
+run_cmake(FileSetWrongTypeExperimental)
+run_cmake(FileSetDefaultWrongTypeExperimental)
 run_cmake(FileSetChangeScope)
 run_cmake(FileSetChangeType)
 run_cmake(FileSetWrongBaseDirs)

+ 13 - 2
Tests/RunCMake/try_compile/CxxStandard-stderr.txt

@@ -1,6 +1,17 @@
-^CMake Error at .*/Tests/RunCMake/try_compile/CxxStandard-build/CMakeFiles/CMakeTmp/CMakeLists.txt:[0-9]+ \(add_executable\):
+^(CMake Error in .*/Tests/RunCMake/try_compile/CxxStandard-build/CMakeFiles/CMakeTmp/CMakeLists.txt:
+  The CXX_STANDARD property on target "cmTC_[0-9a-f]*" contained an invalid
+  value: "3".
+
+
+)?CMake Error at .*/Tests/RunCMake/try_compile/CxxStandard-build/CMakeFiles/CMakeTmp/CMakeLists.txt:[0-9]+ \(add_executable\):
   CXX_STANDARD is set to invalid value '3'
-+
+
+(
+CMake Error in .*/Tests/RunCMake/try_compile/CxxStandard-build/CMakeFiles/CMakeTmp/CMakeLists.txt:
+  The CXX_STANDARD property on target "cmTC_[0-9a-f]*" contained an invalid
+  value: "3".
+
+)?
 CMake Error at CxxStandard.cmake:[0-9]+ \(try_compile\):
   Failed to generate test project build system.
 Call Stack \(most recent call first\):