Просмотр исходного кода

File sets: Add verification of private file sets

Add support for PRIVATE header sets, analogous to the existing
INTERFACE header sets support. A key difference between the two is
the way they handle build and usage requirements. INTERFACE
header sets rely on the original target's INTERFACE, whereas PRIVATE
header sets copy across various properties from the original target.

Generated files need special handling for PRIVATE header sets.
Add a dependency on the original target to the verification target to
ensure such generated files will exist.

Fixes: #23448
Craig Scott 2 недель назад
Родитель
Сommit
b61637f90f
53 измененных файлов с 649 добавлено и 228 удалено
  1. 2 0
      Help/manual/cmake-properties.7.rst
  2. 1 0
      Help/manual/cmake-variables.7.rst
  3. 7 5
      Help/prop_tgt/INTERFACE_HEADER_SETS_TO_VERIFY.rst
  4. 15 0
      Help/prop_tgt/PRIVATE_HEADER_SETS_TO_VERIFY.rst
  5. 10 27
      Help/prop_tgt/VERIFY_INTERFACE_HEADER_SETS.rst
  6. 34 0
      Help/prop_tgt/VERIFY_PRIVATE_HEADER_SETS.rst
  7. 34 0
      Help/prop_tgt/include/VERIFY_XXX_HEADER_SETS.rst
  8. 7 0
      Help/release/dev/verify-private-header-sets.rst
  9. 5 34
      Help/variable/CMAKE_VERIFY_INTERFACE_HEADER_SETS.rst
  10. 10 0
      Help/variable/CMAKE_VERIFY_PRIVATE_HEADER_SETS.rst
  11. 39 0
      Help/variable/include/CMAKE_VERIFY_XXX_HEADER_SETS.rst
  12. 29 12
      Modules/FetchContent.cmake
  13. 208 137
      Source/cmGeneratorTarget.cxx
  14. 27 4
      Source/cmGlobalGenerator.cxx
  15. 1 0
      Source/cmTarget.cxx
  16. 7 3
      Tests/RunCMake/FetchContent/VerifyHeaderSet-stdout.txt
  17. 9 4
      Tests/RunCMake/FetchContent/VerifyHeaderSet.cmake
  18. 3 2
      Tests/RunCMake/FetchContent/VerifyHeaderSet/CMakeLists.txt
  19. 10 0
      Tests/RunCMake/VerifyHeaderSets/AllVerifyPrivateHeaderSets-all_verify_private_header_sets-Debug-build-check.cmake
  20. 4 0
      Tests/RunCMake/VerifyHeaderSets/AllVerifyPrivateHeaderSets.cmake
  21. 44 0
      Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake
  22. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-a_h-Debug-build-result.txt
  23. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-a_h-Debug-build-stderr.txt
  24. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-a_h-Debug-build-stdout.txt
  25. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Debug-build-result.txt
  26. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Debug-build-stderr.txt
  27. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Debug-build-stdout.txt
  28. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Release-build-result.txt
  29. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Release-build-stderr.txt
  30. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Release-build-stdout.txt
  31. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_c_h-Debug-build-result.txt
  32. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_c_h-Debug-build-stderr.txt
  33. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_c_h-Debug-build-stdout.txt
  34. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_cxx_h-Debug-build-result.txt
  35. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_cxx_h-Debug-build-stderr.txt
  36. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_cxx_h-Debug-build-stdout.txt
  37. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-iface_lang_cxx-Debug-build-result.txt
  38. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-iface_lang_cxx-Debug-build-stderr.txt
  39. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-interface-Debug-build-result.txt
  40. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-interface-Debug-build-stderr.txt
  41. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-none-Debug-build-result.txt
  42. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-none-Debug-build-stderr.txt
  43. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-property_off-Debug-build-result.txt
  44. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-property_off-Debug-build-stderr.txt
  45. 88 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets.cmake
  46. 1 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsNonexistent-result.txt
  47. 9 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsNonexistent-stderr.txt
  48. 5 0
      Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsNonexistent.cmake
  49. 3 0
      Tests/RunCMake/VerifyHeaderSets/dir3/CMakeLists.txt
  50. 5 0
      Tests/RunCMake/VerifyHeaderSets/dir3/lib3.h
  51. 3 0
      Tests/RunCMake/VerifyHeaderSets/dir4/CMakeLists.txt
  52. 5 0
      Tests/RunCMake/VerifyHeaderSets/dir4/lib4.h
  53. 1 0
      Tests/RunCMake/property_init/Always.cmake

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

@@ -392,6 +392,7 @@ Properties on Targets
    /prop_tgt/PRECOMPILE_HEADERS_REUSE_FROM
    /prop_tgt/PREFIX
    /prop_tgt/PRIVATE_HEADER
+   /prop_tgt/PRIVATE_HEADER_SETS_TO_VERIFY
    /prop_tgt/PROJECT_LABEL
    /prop_tgt/PUBLIC_HEADER
    /prop_tgt/RESOURCE
@@ -432,6 +433,7 @@ Properties on Targets
    /prop_tgt/UNITY_BUILD_RELOCATABLE
    /prop_tgt/UNITY_BUILD_UNIQUE_ID
    /prop_tgt/VERIFY_INTERFACE_HEADER_SETS
+   /prop_tgt/VERIFY_PRIVATE_HEADER_SETS
    /prop_tgt/VERSION
    /prop_tgt/VISIBILITY_INLINES_HIDDEN
    /prop_tgt/VS_CONFIGURATION_TYPE

+ 1 - 0
Help/manual/cmake-variables.7.rst

@@ -598,6 +598,7 @@ Variables that Control the Build
    /variable/CMAKE_UNITY_BUILD_RELOCATABLE
    /variable/CMAKE_UNITY_BUILD_UNIQUE_ID
    /variable/CMAKE_VERIFY_INTERFACE_HEADER_SETS
+   /variable/CMAKE_VERIFY_PRIVATE_HEADER_SETS
    /variable/CMAKE_VISIBILITY_INLINES_HIDDEN
    /variable/CMAKE_VS_DEBUGGER_COMMAND
    /variable/CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS

+ 7 - 5
Help/prop_tgt/INTERFACE_HEADER_SETS_TO_VERIFY.rst

@@ -4,10 +4,12 @@ INTERFACE_HEADER_SETS_TO_VERIFY
 .. versionadded:: 3.24
 
 Used to specify which ``PUBLIC`` and ``INTERFACE`` header sets of a target
-should be verified.
+should be verified as interface headers.
 
 This property contains a semicolon-separated list of header sets which
-should be verified if :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` is set to
-``TRUE``. If the list is empty, all ``PUBLIC`` and ``INTERFACE`` header sets
-are verified. (If the project does not want to verify any header sets on the
-target, simply set :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` to ``FALSE``.)
+should be verified if :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` is set to true.
+If the list is empty, all ``PUBLIC`` and ``INTERFACE`` header sets are
+verified. If the project does not want to verify any interface header sets on
+the target, set :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` to false.
+
+See also :prop_tgt:`PRIVATE_HEADER_SETS_TO_VERIFY`.

+ 15 - 0
Help/prop_tgt/PRIVATE_HEADER_SETS_TO_VERIFY.rst

@@ -0,0 +1,15 @@
+PRIVATE_HEADER_SETS_TO_VERIFY
+-----------------------------
+
+.. versionadded:: 4.3
+
+Used to specify which ``PUBLIC`` and ``PRIVATE`` header sets of a target
+should be verified as private headers.
+
+This property contains a semicolon-separated list of header sets which
+should be verified if :prop_tgt:`VERIFY_PRIVATE_HEADER_SETS` is set to true.
+If the list is empty, all ``PUBLIC`` and ``PRIVATE`` header sets are verified.
+If the project does not want to verify any private header sets on the target,
+set :prop_tgt:`VERIFY_PRIVATE_HEADER_SETS` to false.
+
+See also :prop_tgt:`INTERFACE_HEADER_SETS_TO_VERIFY`.

+ 10 - 27
Help/prop_tgt/VERIFY_INTERFACE_HEADER_SETS.rst

@@ -14,30 +14,13 @@ header sets, an object library target named
 target has one source file per header in the ``PUBLIC`` and ``INTERFACE``
 header sets. Each source file only includes its associated header file.
 The verification target links against the original target to get all of its
-usage requirements. The verification target has its
-:prop_tgt:`EXCLUDE_FROM_ALL` and :prop_tgt:`DISABLE_PRECOMPILE_HEADERS`
-properties set to true, and its :prop_tgt:`AUTOMOC`, :prop_tgt:`AUTORCC`,
-:prop_tgt:`AUTOUIC`, :prop_tgt:`UNITY_BUILD`, and
-:prop_tgt:`CXX_SCAN_FOR_MODULES` properties set to false.
-
-If the header's :prop_sf:`LANGUAGE` property is set, the value of that property
-is used to determine the language with which to compile the header file.
-Otherwise, if the target has any C++ sources, the header is compiled as C++.
-Otherwise, if the target has any C sources, the header is compiled as C.
-Otherwise, if C++ is enabled globally, the header is compiled as C++.
-Otherwise, if C is enabled globally, the header is compiled as C. Otherwise,
-the header file is not compiled.
-
-If the header's :prop_sf:`SKIP_LINTING` property is set to true, the file is
-not compiled.
-
-If any verification targets are created, a top-level target called
-``all_verify_interface_header_sets`` is created which depends on all
-verification targets.
-
-This property is initialized by the value of the
-:variable:`CMAKE_VERIFY_INTERFACE_HEADER_SETS` variable if it is set when
-a target is created.
-
-If the project wishes to control which header sets are verified by this
-property, it can set :prop_tgt:`INTERFACE_HEADER_SETS_TO_VERIFY`.
+usage requirements.
+
+.. |xxx| replace:: interface
+.. |THIS_PROPERTY| replace:: ``VERIFY_INTERFACE_HEADER_SETS``
+.. |COMPLEMENTARY_PROPERTY| replace:: :prop_tgt:`VERIFY_PRIVATE_HEADER_SETS`
+.. |THIS_ALL_TARGET| replace:: ``all_verify_interface_header_sets``
+.. |COMPLEMENTARY_ALL_TARGET| replace:: ``all_verify_private_header_sets``
+.. |INIT_VARIABLE| replace:: :variable:`CMAKE_VERIFY_INTERFACE_HEADER_SETS`
+.. |SETS_TO_VERIFY_PROPERTY| replace:: :prop_tgt:`INTERFACE_HEADER_SETS_TO_VERIFY`
+.. include:: include/VERIFY_XXX_HEADER_SETS.rst

+ 34 - 0
Help/prop_tgt/VERIFY_PRIVATE_HEADER_SETS.rst

@@ -0,0 +1,34 @@
+VERIFY_PRIVATE_HEADER_SETS
+--------------------------
+
+.. versionadded:: 4.3
+
+Used to verify that all headers in a target's ``PUBLIC`` and ``PRIVATE``
+header sets can be included on their own.
+
+When this property is set to true, and the target is an object library, static
+library, shared library, module library, interface library, or executable, and
+the target has one or more ``PUBLIC`` or ``PRIVATE`` header sets, an object
+library target named ``<target_name>_verify_private_header_sets`` is created.
+This verification target has one source file per header in the ``PUBLIC`` and
+``PRIVATE`` header sets. Each source file only includes its associated header
+file.
+
+Properties affecting compilation are copied from the original target to the
+verification target so that the headers will be interpreted the same way by
+the compiler as when compiling the original target's sources.  There are some
+caveats with this approach.  It cannot replicate the same conditions if any
+of those properties or properties inherited through build requirements from
+transitive dependencies contain
+:ref:`target-dependent generator expressions <Target-Dependent Expressions>`
+that do not specify the target for the expansion.  Such expressions can expand
+to different contents depending on the target they are being used on.
+
+.. |xxx| replace:: private
+.. |THIS_PROPERTY| replace:: ``VERIFY_PRIVATE_HEADER_SETS``
+.. |COMPLEMENTARY_PROPERTY| replace:: :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS`
+.. |THIS_ALL_TARGET| replace:: ``all_verify_private_header_sets``
+.. |COMPLEMENTARY_ALL_TARGET| replace:: ``all_verify_interface_header_sets``
+.. |INIT_VARIABLE| replace:: :variable:`CMAKE_VERIFY_PRIVATE_HEADER_SETS`
+.. |SETS_TO_VERIFY_PROPERTY| replace:: :prop_tgt:`PRIVATE_HEADER_SETS_TO_VERIFY`
+.. include:: include/VERIFY_XXX_HEADER_SETS.rst

+ 34 - 0
Help/prop_tgt/include/VERIFY_XXX_HEADER_SETS.rst

@@ -0,0 +1,34 @@
+The verification target has its
+:prop_tgt:`EXCLUDE_FROM_ALL` and :prop_tgt:`DISABLE_PRECOMPILE_HEADERS`
+properties set to true, and its :prop_tgt:`AUTOMOC`, :prop_tgt:`AUTORCC`,
+:prop_tgt:`AUTOUIC`, :prop_tgt:`UNITY_BUILD`, and
+:prop_tgt:`CXX_SCAN_FOR_MODULES` properties set to false.
+
+If the header's :prop_sf:`LANGUAGE` property is set, the value of that property
+is used to determine the language with which to compile the header file.
+Otherwise, if the target has any C++ sources, the header is compiled as C++.
+Otherwise, if the target has any C sources, the header is compiled as C.
+Otherwise, if C++ is enabled globally, the header is compiled as C++.
+Otherwise, if C is enabled globally, the header is compiled as C. Otherwise,
+the header file is not compiled.
+
+If the header's :prop_sf:`SKIP_LINTING` property is set to true, the file is
+not compiled.
+
+If |THIS_PROPERTY| and |COMPLEMENTARY_PROPERTY| are both set to true, headers
+belonging to ``PUBLIC`` file sets will be verified twice, but with different
+conditions.  The compiler flags used for private and interface contexts can be
+different, leading to the compiler interpreting the contents of the header
+differently.
+
+If any |xxx| file set verification targets are created, a top-level target
+called |THIS_ALL_TARGET| is created which depends on all |xxx| verification
+targets.  Another target called ``all_verify_header_sets`` is also created
+which depends on |THIS_ALL_TARGET|, and on |COMPLEMENTARY_ALL_TARGET| if it
+exists (see |COMPLEMENTARY_PROPERTY|).
+
+This property is initialized by the value of the |INIT_VARIABLE| variable if
+it is set when a target is created.
+
+If the project wishes to control which header sets are verified by this
+property, it can set |SETS_TO_VERIFY_PROPERTY|.

+ 7 - 0
Help/release/dev/verify-private-header-sets.rst

@@ -4,3 +4,10 @@ verify-private-header-sets
 * When :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` is set to true on an executable
   target, that target's interface file sets are verified regardless of its
   :prop_tgt:`ENABLE_EXPORTS` property. See policy :policy:`CMP0209`.
+* The :variable:`CMAKE_VERIFY_PRIVATE_HEADER_SETS` variable and corresponding
+  :prop_tgt:`VERIFY_PRIVATE_HEADER_SETS` target property were added to
+  enable build rules that verify all headers in private file sets can be used
+  on their own.
+* The :prop_tgt:`PRIVATE_HEADER_SETS_TO_VERIFY` target property was added to
+  customize which private file sets to verify when the target's
+  :prop_tgt:`VERIFY_PRIVATE_HEADER_SETS` property is true.

+ 5 - 34
Help/variable/CMAKE_VERIFY_INTERFACE_HEADER_SETS.rst

@@ -3,37 +3,8 @@ CMAKE_VERIFY_INTERFACE_HEADER_SETS
 
 .. versionadded:: 3.24
 
-This variable is used to initialize the
-:prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` property of targets when they are
-created.  Setting it to true enables header set verification.
-
-Projects should not normally set this variable, it is intended as a developer
-control to be set on the :manual:`cmake(1)` command line or other
-equivalent methods.  The developer must have the ability to enable or
-disable header set verification according to the capabilities of their own
-machine and compiler.
-
-Verification of a dependency's header sets is not typically of interest
-to developers.  Therefore, :command:`FetchContent_MakeAvailable` explicitly
-sets ``CMAKE_VERIFY_INTERFACE_HEADER_SETS`` to false for the duration of its
-call, but restores its original value before returning.  If a project brings
-a dependency directly into the main build (e.g. calling
-:command:`add_subdirectory` on a vendored project from a git submodule), it
-should also do likewise.  For example:
-
-.. code:: cmake
-
-  # Save original setting so we can restore it later
-  set(want_header_set_verification ${CMAKE_VERIFY_INTERFACE_HEADER_SETS})
-
-  # Include the vendored dependency with header set verification disabled
-  set(CMAKE_VERIFY_INTERFACE_HEADER_SETS OFF)
-  add_subdirectory(...)   # Vendored sources, e.g. from git submodules
-
-  # Add the project's own sources. Restore the developer's original choice
-  # for whether to enable header set verification.
-  set(CMAKE_VERIFY_INTERFACE_HEADER_SETS ${want_header_set_verification})
-  add_subdirectory(src)
-
-By default, this variable is not set, which will result in header set
-verification being disabled.
+.. |VERIFY_XXX_HEADER_SETS| replace:: :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS`
+.. |CMAKE_VERIFY_XXX_HEADER_SETS| replace:: ``CMAKE_VERIFY_INTERFACE_HEADER_SETS``
+.. |COMPLEMENTARY_CMAKE_VERIFY_XXX_HEADER_SETS| replace:: :variable:`CMAKE_VERIFY_PRIVATE_HEADER_SETS`
+.. |xxx| replace:: interface
+.. include:: include/CMAKE_VERIFY_XXX_HEADER_SETS.rst

+ 10 - 0
Help/variable/CMAKE_VERIFY_PRIVATE_HEADER_SETS.rst

@@ -0,0 +1,10 @@
+CMAKE_VERIFY_PRIVATE_HEADER_SETS
+--------------------------------
+
+.. versionadded:: 4.3
+
+.. |VERIFY_XXX_HEADER_SETS| replace:: :prop_tgt:`VERIFY_PRIVATE_HEADER_SETS`
+.. |CMAKE_VERIFY_XXX_HEADER_SETS| replace:: ``CMAKE_VERIFY_PRIVATE_HEADER_SETS``
+.. |COMPLEMENTARY_CMAKE_VERIFY_XXX_HEADER_SETS| replace:: :variable:`CMAKE_VERIFY_INTERFACE_HEADER_SETS`
+.. |xxx| replace:: private
+.. include:: include/CMAKE_VERIFY_XXX_HEADER_SETS.rst

+ 39 - 0
Help/variable/include/CMAKE_VERIFY_XXX_HEADER_SETS.rst

@@ -0,0 +1,39 @@
+This variable is used to initialize the |VERIFY_XXX_HEADER_SETS| property of
+targets when they are created.  Setting it to true enables |xxx| header set
+verification.
+
+Projects should not normally set this variable, it is intended as a developer
+control to be set on the :manual:`cmake(1)` command line or other
+equivalent methods.  The developer must have the ability to enable or
+disable header set verification according to the capabilities of their own
+machine and compiler.
+
+Verification of a dependency's header sets is not typically of interest to
+developers.  Therefore, :command:`FetchContent_MakeAvailable` explicitly sets
+|CMAKE_VERIFY_XXX_HEADER_SETS| and |COMPLEMENTARY_CMAKE_VERIFY_XXX_HEADER_SETS|
+to false for the duration of its call, but restores their original values
+before returning.  If a project brings a dependency directly into the main
+build (e.g. calling :command:`add_subdirectory` on a vendored project from a
+git submodule), it should also do likewise.  For example:
+
+.. code:: cmake
+
+  # Save original setting so we can restore it later
+  set(want_interface_header_set_verification ${CMAKE_VERIFY_INTERFACE_HEADER_SETS})
+  set(want_private_header_set_verification ${CMAKE_VERIFY_PRIVATE_HEADER_SETS})
+
+  # Include the vendored dependency with header set verification disabled
+  set(CMAKE_VERIFY_INTERFACE_HEADER_SETS OFF)
+  set(CMAKE_VERIFY_PRIVATE_HEADER_SETS OFF)
+  add_subdirectory(...)   # Vendored sources, e.g. from git submodules
+
+  # Add the project's own sources. Restore the developer's original choice
+  # for whether to enable header set verification.
+  set(CMAKE_VERIFY_INTERFACE_HEADER_SETS ${want_interface_header_set_verification})
+  set(CMAKE_VERIFY_PRIVATE_HEADER_SETS ${want_private_header_set_verification})
+  add_subdirectory(src)
+
+By default, this variable is not set, which will result in |xxx| header set
+verification being disabled.
+
+See also |COMPLEMENTARY_CMAKE_VERIFY_XXX_HEADER_SETS|.

+ 29 - 12
Modules/FetchContent.cmake

@@ -397,17 +397,20 @@ Commands
     FetchContent_Declare(other ...)
     FetchContent_MakeAvailable(uses_other other)
 
-  Note that :variable:`CMAKE_VERIFY_INTERFACE_HEADER_SETS` is explicitly set
-  to false upon entry to ``FetchContent_MakeAvailable()``, and is restored to
-  its original value before the command returns.  Developers typically only
+  Note that :variable:`CMAKE_VERIFY_INTERFACE_HEADER_SETS` and
+  :variable:`CMAKE_VERIFY_PRIVATE_HEADER_SETS` are explicitly set to false
+  upon entry to ``FetchContent_MakeAvailable()``, and are restored to their
+  original values before the command returns.  Developers typically only
   want to verify header sets from the main project, not those from any
   dependencies.  This local manipulation of the
-  :variable:`CMAKE_VERIFY_INTERFACE_HEADER_SETS` variable provides that
+  :variable:`CMAKE_VERIFY_INTERFACE_HEADER_SETS` and
+  :variable:`CMAKE_VERIFY_PRIVATE_HEADER_SETS` variables provides that
   intuitive behavior.  You can use variables like
   :variable:`CMAKE_PROJECT_INCLUDE` or
   :variable:`CMAKE_PROJECT_<PROJECT-NAME>_INCLUDE` to turn verification back
   on for all or some dependencies.  You can also set the
-  :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` property of individual targets.
+  :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` and
+  :prop_tgt:`VERIFY_PRIVATE_HEADER_SETS` properties of individual targets.
 
 .. command:: FetchContent_Populate
 
@@ -2240,12 +2243,14 @@ endfunction()
 # calls will be available to the caller.
 macro(FetchContent_MakeAvailable)
 
-  # We must append an item, even if the variable is unset, so prefix its value.
-  # We will strip that prefix when we pop the value at the end of the macro.
+  # We must append these, even if the variables are unset, so prefix the values.
+  # We will strip that prefix when we pop the values at the end of the macro.
   list(APPEND __cmake_fcCurrentVarsStack
     "__fcprefix__${CMAKE_VERIFY_INTERFACE_HEADER_SETS}"
+    "__fcprefix__${CMAKE_VERIFY_PRIVATE_HEADER_SETS}"
   )
   set(CMAKE_VERIFY_INTERFACE_HEADER_SETS FALSE)
+  set(CMAKE_VERIFY_PRIVATE_HEADER_SETS FALSE)
 
   get_property(__cmake_providerCommand GLOBAL PROPERTY
     __FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER
@@ -2453,18 +2458,30 @@ macro(FetchContent_MakeAvailable)
   endforeach()
 
   # Prefix will be "__fcprefix__"
-  list(POP_BACK __cmake_fcCurrentVarsStack __cmake_original_verify_setting)
-  string(SUBSTRING "${__cmake_original_verify_setting}"
-    12 -1 __cmake_original_verify_setting
+  list(POP_BACK __cmake_fcCurrentVarsStack
+    __cmake_original_verify_private_setting
+    __cmake_original_verify_interface_setting
+  )
+  string(SUBSTRING "${__cmake_original_verify_private_setting}"
+    12 -1 __cmake_original_verify_private_setting
+  )
+  string(SUBSTRING "${__cmake_original_verify_interface_setting}"
+    12 -1 __cmake_original_verify_interface_setting
+  )
+  set(CMAKE_VERIFY_PRIVATE_HEADER_SETS
+    ${__cmake_original_verify_private_setting}
+  )
+  set(CMAKE_VERIFY_INTERFACE_HEADER_SETS
+    ${__cmake_original_verify_interface_setting}
   )
-  set(CMAKE_VERIFY_INTERFACE_HEADER_SETS ${__cmake_original_verify_setting})
 
   # clear local variables to prevent leaking into the caller's scope
   unset(__cmake_contentName)
   unset(__cmake_contentNameLower)
   unset(__cmake_contentNameUpper)
   unset(__cmake_providerCommand)
-  unset(__cmake_original_verify_setting)
+  unset(__cmake_original_verify_interface_setting)
+  unset(__cmake_original_verify_private_setting)
 
 endmacro()
 

+ 208 - 137
Source/cmGeneratorTarget.cxx

@@ -9,6 +9,7 @@
 #include <cstddef>
 #include <cstdio>
 #include <cstring>
+#include <initializer_list>
 #include <sstream>
 #include <unordered_set>
 #include <utility>
@@ -5703,166 +5704,236 @@ cmGeneratorTarget::ManagedType cmGeneratorTarget::GetManagedType(
 
 bool cmGeneratorTarget::AddHeaderSetVerification()
 {
-  if (!this->GetPropertyAsBool("VERIFY_INTERFACE_HEADER_SETS")) {
-    return true;
-  }
-
-  if (this->GetType() != cmStateEnums::STATIC_LIBRARY &&
-      this->GetType() != cmStateEnums::SHARED_LIBRARY &&
-      this->GetType() != cmStateEnums::UNKNOWN_LIBRARY &&
-      this->GetType() != cmStateEnums::OBJECT_LIBRARY &&
-      this->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
-      this->GetType() != cmStateEnums::EXECUTABLE) {
-    return true;
-  }
-
-  auto verifyValue = this->GetProperty("INTERFACE_HEADER_SETS_TO_VERIFY");
-  bool const all = verifyValue.IsEmpty();
-  std::set<std::string> verifySet;
-  if (!all) {
-    cmList verifyList{ verifyValue };
-    verifySet.insert(verifyList.begin(), verifyList.end());
-  }
-
-  cmTarget* verifyTarget = nullptr;
-  std::string const verifyTargetName =
-    cmStrCat(this->GetName(), "_verify_interface_header_sets");
+  for (bool const isInterface : { false, true }) {
+    if (!this->GetPropertyAsBool(isInterface ? "VERIFY_INTERFACE_HEADER_SETS"
+                                             : "VERIFY_PRIVATE_HEADER_SETS")) {
+      continue;
+    }
 
-  char const* allVerifyTargetName = "all_verify_interface_header_sets";
-  cmTarget* allVerifyTarget =
-    this->GlobalGenerator->GetMakefiles().front()->FindTargetToUse(
-      allVerifyTargetName, { cmStateEnums::TargetDomain::NATIVE });
+    if (this->GetType() != cmStateEnums::STATIC_LIBRARY &&
+        this->GetType() != cmStateEnums::SHARED_LIBRARY &&
+        (this->GetType() != cmStateEnums::MODULE_LIBRARY || isInterface) &&
+        this->GetType() != cmStateEnums::UNKNOWN_LIBRARY &&
+        this->GetType() != cmStateEnums::OBJECT_LIBRARY &&
+        this->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
+        this->GetType() != cmStateEnums::EXECUTABLE) {
+      continue;
+    }
 
-  auto interfaceFileSetEntries = this->Target->GetInterfaceHeaderSetsEntries();
+    char const* headerSetsProperty = isInterface
+      ? "INTERFACE_HEADER_SETS_TO_VERIFY"
+      : "PRIVATE_HEADER_SETS_TO_VERIFY";
+
+    auto verifyValue = this->GetProperty(headerSetsProperty);
+    bool const all = verifyValue.IsEmpty();
+    std::set<std::string> verifySet;
+    if (!all) {
+      cmList verifyList{ verifyValue };
+      verifySet.insert(verifyList.begin(), verifyList.end());
+    }
+
+    cmTarget* verifyTarget = nullptr;
+    std::string const verifyTargetName =
+      cmStrCat(this->GetName(),
+               isInterface ? "_verify_interface_header_sets"
+                           : "_verify_private_header_sets");
+
+    char const* allVerifyTargetName = isInterface
+      ? "all_verify_interface_header_sets"
+      : "all_verify_private_header_sets";
+    cmTarget* allVerifyTarget =
+      this->GlobalGenerator->GetMakefiles().front()->FindTargetToUse(
+        allVerifyTargetName, { cmStateEnums::TargetDomain::NATIVE });
+
+    auto fileSetEntries = isInterface
+      ? this->Target->GetInterfaceHeaderSetsEntries()
+      : this->Target->GetHeaderSetsEntries();
+
+    std::set<cmFileSet*> fileSets;
+    for (auto const& entry : fileSetEntries) {
+      for (auto const& name : cmList{ entry.Value }) {
+        if (all || verifySet.count(name)) {
+          fileSets.insert(this->Target->GetFileSet(name));
+          verifySet.erase(name);
+        }
+      }
+    }
 
-  std::set<cmFileSet*> fileSets;
-  for (auto const& entry : interfaceFileSetEntries) {
-    for (auto const& name : cmList{ entry.Value }) {
-      if (all || verifySet.count(name)) {
-        fileSets.insert(this->Target->GetFileSet(name));
-        verifySet.erase(name);
+    if (isInterface) {
+      cmPolicies::PolicyStatus const cmp0209 = this->GetPolicyStatusCMP0209();
+      if (cmp0209 != cmPolicies::NEW &&
+          this->GetType() == cmStateEnums::EXECUTABLE &&
+          !this->GetPropertyAsBool("ENABLE_EXPORTS")) {
+        if (cmp0209 == cmPolicies::WARN && !fileSets.empty()) {
+          this->Makefile->IssueMessage(
+            MessageType::AUTHOR_WARNING,
+            cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0209),
+                     "\n"
+                     "Executable target \"",
+                     this->GetName(),
+                     "\" has interface header file sets, but it does not "
+                     "enable exports. Those headers would be verified under "
+                     "CMP0209 NEW behavior.\n"));
+        }
+        continue;
       }
     }
-  }
 
-  cmPolicies::PolicyStatus const cmp0209 = this->GetPolicyStatusCMP0209();
-  if (cmp0209 != cmPolicies::NEW &&
-      this->GetType() == cmStateEnums::EXECUTABLE &&
-      !this->GetPropertyAsBool("ENABLE_EXPORTS")) {
-    if (cmp0209 == cmPolicies::WARN && !fileSets.empty()) {
+    if (!verifySet.empty()) {
       this->Makefile->IssueMessage(
-        MessageType::AUTHOR_WARNING,
-        cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0209),
-                 "\n"
-                 "Executable target \"",
+        MessageType::FATAL_ERROR,
+        cmStrCat("Property ", headerSetsProperty, " of target \"",
                  this->GetName(),
-                 "\" has interface header file sets, but it does not "
-                 "enable exports. Those headers would be verified under "
-                 "CMP0209 NEW behavior.\n"));
+                 "\" contained the following header sets that are nonexistent "
+                 "or not ",
+                 isInterface ? "INTERFACE" : "PRIVATE", ":\n  ",
+                 cmJoin(verifySet, "\n  ")));
+      return false;
     }
-    return true;
-  }
-  if (!verifySet.empty()) {
-    this->Makefile->IssueMessage(
-      MessageType::FATAL_ERROR,
-      cmStrCat("Property INTERFACE_HEADER_SETS_TO_VERIFY of target \"",
-               this->GetName(),
-               "\" contained the following header sets that are nonexistent "
-               "or not INTERFACE:\n  ",
-               cmJoin(verifySet, "\n  ")));
-    return false;
-  }
 
-  cm::optional<std::set<std::string>> languages;
-  for (auto* fileSet : fileSets) {
-    auto dirCges = fileSet->CompileDirectoryEntries();
-    auto fileCges = fileSet->CompileFileEntries();
-
-    static auto const contextSensitive =
-      [](std::unique_ptr<cmCompiledGeneratorExpression> const& cge) {
-        return cge->GetHadContextSensitiveCondition();
-      };
-    bool dirCgesContextSensitive = false;
-    bool fileCgesContextSensitive = false;
-
-    std::vector<std::string> dirs;
-    std::map<std::string, std::vector<std::string>> filesPerDir;
-    bool first = true;
-    for (auto const& config : this->Makefile->GetGeneratorConfigs(
-           cmMakefile::GeneratorConfigQuery::IncludeEmptyConfig)) {
-      cm::GenEx::Context context(this->LocalGenerator, config);
-      if (first || dirCgesContextSensitive) {
-        dirs = fileSet->EvaluateDirectoryEntries(dirCges, context, this);
-        dirCgesContextSensitive =
-          std::any_of(dirCges.begin(), dirCges.end(), contextSensitive);
-      }
-      if (first || fileCgesContextSensitive) {
-        filesPerDir.clear();
-        for (auto const& fileCge : fileCges) {
-          fileSet->EvaluateFileEntry(dirs, filesPerDir, fileCge, context,
-                                     this);
-          if (fileCge->GetHadContextSensitiveCondition()) {
-            fileCgesContextSensitive = true;
-          }
+    cm::optional<std::set<std::string>> languages;
+    for (auto* fileSet : fileSets) {
+      auto dirCges = fileSet->CompileDirectoryEntries();
+      auto fileCges = fileSet->CompileFileEntries();
+
+      static auto const contextSensitive =
+        [](std::unique_ptr<cmCompiledGeneratorExpression> const& cge) {
+          return cge->GetHadContextSensitiveCondition();
+        };
+      bool dirCgesContextSensitive = false;
+      bool fileCgesContextSensitive = false;
+
+      std::vector<std::string> dirs;
+      std::map<std::string, std::vector<std::string>> filesPerDir;
+      bool first = true;
+      for (auto const& config : this->Makefile->GetGeneratorConfigs(
+             cmMakefile::GeneratorConfigQuery::IncludeEmptyConfig)) {
+        cm::GenEx::Context context(this->LocalGenerator, config);
+        if (first || dirCgesContextSensitive) {
+          dirs = fileSet->EvaluateDirectoryEntries(dirCges, context, this);
+          dirCgesContextSensitive =
+            std::any_of(dirCges.begin(), dirCges.end(), contextSensitive);
         }
-      }
-
-      for (auto const& files : filesPerDir) {
-        for (auto const& file : files.second) {
-          std::string filename = this->GenerateHeaderSetVerificationFile(
-            *this->Makefile->GetOrCreateSource(file), files.first,
-            verifyTargetName, languages);
-          if (filename.empty()) {
-            continue;
+        if (first || fileCgesContextSensitive) {
+          filesPerDir.clear();
+          for (auto const& fileCge : fileCges) {
+            fileSet->EvaluateFileEntry(dirs, filesPerDir, fileCge, context,
+                                       this);
+            if (fileCge->GetHadContextSensitiveCondition()) {
+              fileCgesContextSensitive = true;
+            }
           }
+        }
 
-          if (!verifyTarget) {
-            {
-              cmMakefile::PolicyPushPop polScope(this->Makefile);
-              this->Makefile->SetPolicy(cmPolicies::CMP0119, cmPolicies::NEW);
-              verifyTarget = this->Makefile->AddLibrary(
-                verifyTargetName, cmStateEnums::OBJECT_LIBRARY, {}, true);
+        for (auto const& files : filesPerDir) {
+          for (auto const& file : files.second) {
+            std::string filename = this->GenerateHeaderSetVerificationFile(
+              *this->Makefile->GetOrCreateSource(file), files.first,
+              verifyTargetName, languages);
+            if (filename.empty()) {
+              continue;
             }
 
-            verifyTarget->AddLinkLibrary(
-              *this->Makefile, this->GetName(),
-              cmTargetLinkLibraryType::GENERAL_LibraryType);
-            verifyTarget->SetProperty("AUTOMOC", "OFF");
-            verifyTarget->SetProperty("AUTORCC", "OFF");
-            verifyTarget->SetProperty("AUTOUIC", "OFF");
-            verifyTarget->SetProperty("DISABLE_PRECOMPILE_HEADERS", "ON");
-            verifyTarget->SetProperty("UNITY_BUILD", "OFF");
-            verifyTarget->SetProperty("CXX_SCAN_FOR_MODULES", "OFF");
-            verifyTarget->FinalizeTargetConfiguration(
-              this->Makefile->GetCompileDefinitionsEntries());
-
-            if (!allVerifyTarget) {
-              allVerifyTarget =
-                this->GlobalGenerator->GetMakefiles()
-                  .front()
-                  ->AddNewUtilityTarget(allVerifyTargetName, true);
+            if (!verifyTarget) {
+              {
+                cmMakefile::PolicyPushPop polScope(this->Makefile);
+                this->Makefile->SetPolicy(cmPolicies::CMP0119,
+                                          cmPolicies::NEW);
+                verifyTarget = this->Makefile->AddLibrary(
+                  verifyTargetName, cmStateEnums::OBJECT_LIBRARY, {}, true);
+              }
+
+              if (isInterface) {
+                // Link to the original target so that we pick up its
+                // interface compile options just like a consumer would.
+                // This also ensures any generated headers in the original
+                // target will be created.
+                verifyTarget->AddLinkLibrary(
+                  *this->Makefile, this->GetName(),
+                  cmTargetLinkLibraryType::GENERAL_LibraryType);
+              } else {
+                // For private file sets, we need to simulate compiling the
+                // same way as the original target. That includes linking to
+                // the same things so we pick up the same transitive
+                // properties. For the <LANG>_... properties, we don't care if
+                // we set them for languages this target won't eventually use.
+                // The verify header sets feature currently only supports the
+                // C and C++ languages, so we just always set those here for
+                // simplicity rather than working out all languages the target
+                // has to compile for.
+                static std::vector<std::string> propertiesToCopy = {
+                  "COMPILE_DEFINITIONS", "COMPILE_FEATURES",
+                  "COMPILE_FLAGS",       "COMPILE_OPTIONS",
+                  "DEFINE_SYMBOL",       "INCLUDE_DIRECTORIES",
+                  "LINK_LIBRARIES",      "C_STANDARD",
+                  "C_STANDARD_REQUIRED", "C_EXTENSIONS",
+                  "CXX_STANDARD",        "CXX_STANDARD_REQUIRED",
+                  "CXX_EXTENSIONS"
+                };
+                for (std::string const& prop : propertiesToCopy) {
+                  cmValue propValue = this->Target->GetProperty(prop);
+                  if (propValue.IsSet()) {
+                    verifyTarget->SetProperty(prop, propValue);
+                  }
+                }
+                // The original target might have generated headers. Since
+                // we only link to the original target for compilation,
+                // there's nothing to force such generation to happen yet.
+                // Our verify target must depend on the original target to
+                // ensure such generated files will be created.
+                verifyTarget->AddUtility(this->GetName(), false,
+                                         this->Makefile);
+                verifyTarget->AddCodegenDependency(this->GetName());
+              }
+
+              verifyTarget->SetProperty("AUTOMOC", "OFF");
+              verifyTarget->SetProperty("AUTORCC", "OFF");
+              verifyTarget->SetProperty("AUTOUIC", "OFF");
+              verifyTarget->SetProperty("DISABLE_PRECOMPILE_HEADERS", "ON");
+              verifyTarget->SetProperty("UNITY_BUILD", "OFF");
+              verifyTarget->SetProperty("CXX_SCAN_FOR_MODULES", "OFF");
+
+              if (isInterface) {
+                verifyTarget->FinalizeTargetConfiguration(
+                  this->Makefile->GetCompileDefinitionsEntries());
+              } else {
+                // Private verification only needs to add the directory scope
+                // definitions here
+                for (auto const& def :
+                     this->Makefile->GetCompileDefinitionsEntries()) {
+                  verifyTarget->InsertCompileDefinition(def);
+                }
+              }
+
+              if (!allVerifyTarget) {
+                allVerifyTarget =
+                  this->GlobalGenerator->GetMakefiles()
+                    .front()
+                    ->AddNewUtilityTarget(allVerifyTargetName, true);
+              }
+
+              allVerifyTarget->AddUtility(verifyTargetName, false);
             }
 
-            allVerifyTarget->AddUtility(verifyTargetName, false);
-          }
-
-          if (fileCgesContextSensitive) {
-            filename = cmStrCat("$<$<CONFIG:", config, ">:", filename, '>');
+            if (fileCgesContextSensitive) {
+              filename = cmStrCat("$<$<CONFIG:", config, ">:", filename, '>');
+            }
+            verifyTarget->AddSource(filename);
           }
-          verifyTarget->AddSource(filename);
         }
-      }
 
-      if (!dirCgesContextSensitive && !fileCgesContextSensitive) {
-        break;
+        if (!dirCgesContextSensitive && !fileCgesContextSensitive) {
+          break;
+        }
+        first = false;
       }
-      first = false;
     }
-  }
 
-  if (verifyTarget) {
-    this->LocalGenerator->AddGeneratorTarget(
-      cm::make_unique<cmGeneratorTarget>(verifyTarget, this->LocalGenerator));
+    if (verifyTarget) {
+      this->LocalGenerator->AddGeneratorTarget(
+        cm::make_unique<cmGeneratorTarget>(verifyTarget,
+                                           this->LocalGenerator));
+    }
   }
 
   return true;

+ 27 - 4
Source/cmGlobalGenerator.cxx

@@ -1861,13 +1861,36 @@ bool cmGlobalGenerator::AddHeaderSetVerification()
     }
   }
 
-  cmTarget* allVerifyTarget = this->Makefiles.front()->FindTargetToUse(
-    "all_verify_interface_header_sets",
-    { cmStateEnums::TargetDomain::NATIVE });
-  if (allVerifyTarget) {
+  cmTarget* allVerifyInterfaceTarget =
+    this->Makefiles.front()->FindTargetToUse(
+      "all_verify_interface_header_sets",
+      { cmStateEnums::TargetDomain::NATIVE });
+  if (allVerifyInterfaceTarget) {
+    this->LocalGenerators.front()->AddGeneratorTarget(
+      cm::make_unique<cmGeneratorTarget>(allVerifyInterfaceTarget,
+                                         this->LocalGenerators.front().get()));
+  }
+  cmTarget* allVerifyPrivateTarget = this->Makefiles.front()->FindTargetToUse(
+    "all_verify_private_header_sets", { cmStateEnums::TargetDomain::NATIVE });
+  if (allVerifyPrivateTarget) {
+    this->LocalGenerators.front()->AddGeneratorTarget(
+      cm::make_unique<cmGeneratorTarget>(allVerifyPrivateTarget,
+                                         this->LocalGenerators.front().get()));
+  }
+
+  if (allVerifyInterfaceTarget || allVerifyPrivateTarget) {
+    cmTarget* allVerifyTarget =
+      this->GetMakefiles().front()->AddNewUtilityTarget(
+        "all_verify_header_sets", true);
     this->LocalGenerators.front()->AddGeneratorTarget(
       cm::make_unique<cmGeneratorTarget>(allVerifyTarget,
                                          this->LocalGenerators.front().get()));
+    if (allVerifyInterfaceTarget) {
+      allVerifyTarget->AddUtility(allVerifyInterfaceTarget->GetName(), false);
+    }
+    if (allVerifyPrivateTarget) {
+      allVerifyTarget->AddUtility(allVerifyPrivateTarget->GetName(), false);
+    }
   }
 
   return true;

+ 1 - 0
Source/cmTarget.cxx

@@ -498,6 +498,7 @@ TargetProperty const StaticTargetProperties[] = {
   { "UNITY_BUILD_RELOCATABLE"_s, IC::CanCompileSources },
   { "OPTIMIZE_DEPENDENCIES"_s, IC::CanCompileSources },
   { "VERIFY_INTERFACE_HEADER_SETS"_s },
+  { "VERIFY_PRIVATE_HEADER_SETS"_s },
   // -- Android
   { "ANDROID_ANT_ADDITIONAL_OPTIONS"_s, IC::CanCompileSources },
   { "ANDROID_PROCESS_MAX"_s, IC::CanCompileSources },

+ 7 - 3
Tests/RunCMake/FetchContent/VerifyHeaderSet-stdout.txt

@@ -1,4 +1,8 @@
--- Before subproject, var = 'TRUE'
--- Inside subproject, var = 'FALSE'
--- After subproject, var = 'TRUE'
+-- Before subproject, interface var = 'TRUE'
+-- Before subproject, private var = 'TRUE'
+-- Inside subproject, interface var = 'FALSE'
+-- Inside subproject, private var = 'FALSE'
+-- After subproject, interface var = 'TRUE'
+-- After subproject, private var = 'TRUE'
 -- Subproject target property VERIFY_INTERFACE_HEADER_SETS='FALSE'
+-- Subproject target property VERIFY_PRIVATE_HEADER_SETS='FALSE'

+ 9 - 4
Tests/RunCMake/FetchContent/VerifyHeaderSet.cmake

@@ -1,16 +1,21 @@
 enable_language(C)
 
 set(CMAKE_VERIFY_INTERFACE_HEADER_SETS TRUE)
+set(CMAKE_VERIFY_PRIVATE_HEADER_SETS TRUE)
 
 include(FetchContent)
 FetchContent_Declare(verify_subproj
   SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/VerifyHeaderSet
 )
-message(STATUS "Before subproject, var = '${CMAKE_VERIFY_INTERFACE_HEADER_SETS}'")
+message(STATUS "Before subproject, interface var = '${CMAKE_VERIFY_INTERFACE_HEADER_SETS}'")
+message(STATUS "Before subproject, private var = '${CMAKE_VERIFY_PRIVATE_HEADER_SETS}'")
 FetchContent_MakeAvailable(verify_subproj)
 
 # Provide a way to verify the variable was reset back to its original value
-message(STATUS "After subproject, var = '${CMAKE_VERIFY_INTERFACE_HEADER_SETS}'")
+message(STATUS "After subproject, interface var = '${CMAKE_VERIFY_INTERFACE_HEADER_SETS}'")
+message(STATUS "After subproject, private var = '${CMAKE_VERIFY_PRIVATE_HEADER_SETS}'")
 
-get_property(verify TARGET Blah PROPERTY VERIFY_INTERFACE_HEADER_SETS)
-message(STATUS "Subproject target property VERIFY_INTERFACE_HEADER_SETS='${verify}'")
+get_property(verify_interface TARGET Blah PROPERTY VERIFY_INTERFACE_HEADER_SETS)
+get_property(verify_private TARGET Blah PROPERTY VERIFY_PRIVATE_HEADER_SETS)
+message(STATUS "Subproject target property VERIFY_INTERFACE_HEADER_SETS='${verify_interface}'")
+message(STATUS "Subproject target property VERIFY_PRIVATE_HEADER_SETS='${verify_private}'")

+ 3 - 2
Tests/RunCMake/FetchContent/VerifyHeaderSet/CMakeLists.txt

@@ -1,9 +1,10 @@
 cmake_minimum_required(VERSION 3.24)
 project(VerifyHeaderSet LANGUAGES C)
 
-message(STATUS "Inside subproject, var = '${CMAKE_VERIFY_INTERFACE_HEADER_SETS}'")
+message(STATUS "Inside subproject, interface var = '${CMAKE_VERIFY_INTERFACE_HEADER_SETS}'")
+message(STATUS "Inside subproject, private var = '${CMAKE_VERIFY_PRIVATE_HEADER_SETS}'")
 
 add_library(Blah INTERFACE)
 target_sources(Blah
-  INTERFACE FILE_SET HEADERS FILES blah.h
+  PUBLIC FILE_SET HEADERS FILES blah.h
 )

+ 10 - 0
Tests/RunCMake/VerifyHeaderSets/AllVerifyPrivateHeaderSets-all_verify_private_header_sets-Debug-build-check.cmake

@@ -0,0 +1,10 @@
+# A custom command is used to copy the header file from the source directory to
+# the binary directory. If the verification target was built, the custom
+# command should have been executed, and the file should be present in the
+# binary directory.
+if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/dir3/lib3.h")
+  string(APPEND RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/dir3/lib3.h should exist but it does not\n")
+endif()
+if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/dir4/lib4.h")
+  string(APPEND RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/dir4/lib4.h should exist but it does not\n")
+endif()

+ 4 - 0
Tests/RunCMake/VerifyHeaderSets/AllVerifyPrivateHeaderSets.cmake

@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_subdirectory(dir3)
+add_subdirectory(dir4)

+ 44 - 0
Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake

@@ -59,3 +59,47 @@ unset(RunCMake_TEST_OPTIONS)
 run_cmake(VerifyInterfaceHeaderSets-CMP0209-NEW)
 run_cmake(VerifyInterfaceHeaderSets-CMP0209-OLD)
 run_cmake(VerifyInterfaceHeaderSets-CMP0209-WARN)
+
+set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_PRIVATE_HEADER_SETS=ON)
+if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+  list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
+endif()
+run_cmake(VerifyPrivateHeaderSets)
+unset(RunCMake_TEST_OPTIONS)
+
+run_cmake_build(VerifyPrivateHeaderSets private static)
+run_cmake_build(VerifyPrivateHeaderSets private shared)
+run_cmake_build(VerifyPrivateHeaderSets private object)
+run_cmake_build(VerifyPrivateHeaderSets private interface)
+run_cmake_build(VerifyPrivateHeaderSets private iface_private)
+run_cmake_build(VerifyPrivateHeaderSets private exe)
+run_cmake_build(VerifyPrivateHeaderSets private none)
+run_cmake_build(VerifyPrivateHeaderSets private property_off)
+run_cmake_build(VerifyPrivateHeaderSets private a_h)
+run_cmake_build(VerifyPrivateHeaderSets private dir_c_h)
+run_cmake_build(VerifyPrivateHeaderSets private dir_cxx_h)
+
+if(NOT RunCMake_GENERATOR STREQUAL "Xcode")
+  run_cmake_build(VerifyPrivateHeaderSets private config)
+  if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(BUILD_CONFIG Release)
+    run_cmake_build(VerifyPrivateHeaderSets private config)
+    unset(BUILD_CONFIG)
+  endif()
+endif()
+
+run_cmake_build(VerifyPrivateHeaderSets private lang_test_c)
+run_cmake_build(VerifyPrivateHeaderSets private lang_test_cxx)
+run_cmake_build(VerifyPrivateHeaderSets private iface_lang_cxx)
+run_cmake_build(VerifyPrivateHeaderSets private list)
+run_cmake_build(VerifyPrivateHeaderSets private skip_linting)
+
+set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_PRIVATE_HEADER_SETS=ON)
+run_cmake(AllVerifyPrivateHeaderSets)
+unset(RunCMake_TEST_OPTIONS)
+
+run_cmake_build(AllVerifyPrivateHeaderSets private all)
+
+set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_PRIVATE_HEADER_SETS=ON)
+run_cmake(VerifyPrivateHeaderSetsNonexistent)
+unset(RunCMake_TEST_OPTIONS)

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-a_h-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-a_h-Debug-build-stderr.txt

@@ -0,0 +1 @@
+(TEST_A_H defined)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-a_h-Debug-build-stdout.txt

@@ -0,0 +1 @@
+(TEST_A_H defined)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Debug-build-stderr.txt

@@ -0,0 +1 @@
+(Compiled in debug mode)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Debug-build-stdout.txt

@@ -0,0 +1 @@
+(Compiled in debug mode)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Release-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Release-build-stderr.txt

@@ -0,0 +1 @@
+(Compiled in release mode)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-config-Release-build-stdout.txt

@@ -0,0 +1 @@
+(Compiled in release mode)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_c_h-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_c_h-Debug-build-stderr.txt

@@ -0,0 +1 @@
+(TEST_DIR_C_H defined)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_c_h-Debug-build-stdout.txt

@@ -0,0 +1 @@
+(TEST_DIR_C_H defined)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_cxx_h-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_cxx_h-Debug-build-stderr.txt

@@ -0,0 +1 @@
+(TEST_DIR_CXX_H defined)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-dir_cxx_h-Debug-build-stdout.txt

@@ -0,0 +1 @@
+(TEST_DIR_CXX_H defined)?

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-iface_lang_cxx-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-iface_lang_cxx-Debug-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-interface-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-interface-Debug-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-none-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-none-Debug-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-property_off-Debug-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets-property_off-Debug-build-stderr.txt

@@ -0,0 +1 @@
+.*

+ 88 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSets.cmake

@@ -0,0 +1,88 @@
+cmake_policy(SET CMP0209 NEW)
+
+enable_language(C CXX)
+
+add_compile_definitions(TEST_ADD_COMPILE_DEFINITIONS)
+
+set_property(SOURCE a.h PROPERTY LANGUAGE C)
+set_property(SOURCE dir/c.h PROPERTY LANGUAGE C)
+set_property(SOURCE dir/cxx.h PROPERTY LANGUAGE CXX)
+
+add_library(static STATIC lib.c)
+target_sources(static PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(shared SHARED lib.c)
+target_sources(shared PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(module MODULE lib.c)
+target_sources(module PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(object OBJECT lib.c)
+target_sources(object PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(interface INTERFACE)
+target_sources(interface INTERFACE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+# Though a bit strange, INTERFACE libraries can have PRIVATE file sets.
+# A more likely scenario would be generated headers which might be PUBLIC.
+# Don't make the name of this target any longer or else it triggers a crash in the OpenWatcom compiler.
+# That crash only occurs when debug information is enabled, which is the case when this test runs.
+add_library(iface_private INTERFACE)
+target_sources(iface_private PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_executable(exe main.c)
+target_sources(exe PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(none STATIC lib.c)
+
+add_library(property_off STATIC lib.c)
+target_sources(property_off PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+set_property(TARGET property_off PROPERTY VERIFY_PRIVATE_HEADER_SETS OFF)
+
+add_library(a_h STATIC lib.c)
+target_compile_definitions(a_h PRIVATE TEST_A_H)
+target_sources(a_h PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(dir_c_h STATIC lib.c)
+target_compile_definitions(dir_c_h PRIVATE TEST_DIR_C_H)
+target_sources(dir_c_h PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(dir_cxx_h STATIC lib.c)
+target_compile_definitions(dir_cxx_h PRIVATE TEST_DIR_CXX_H)
+target_sources(dir_cxx_h PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+set_property(SOURCE debug.h PROPERTY LANGUAGE C)
+set_property(SOURCE release.h PROPERTY LANGUAGE C)
+
+if(NOT CMAKE_GENERATOR STREQUAL "Xcode")
+  add_library(config STATIC lib.c)
+  target_sources(config PRIVATE FILE_SET HEADERS FILES $<IF:$<CONFIG:Debug>,debug.h,release.h>)
+endif()
+
+add_library(lang_test_c STATIC lib.c)
+target_sources(lang_test_c PRIVATE FILE_SET HEADERS FILES lang_test.h)
+
+add_library(lang_test_cxx STATIC lib.c lib.cxx)
+target_compile_definitions(lang_test_cxx PRIVATE EXPECT_CXX)
+target_sources(lang_test_cxx PRIVATE FILE_SET HEADERS FILES lang_test.h)
+
+# Don't make the name of this target any longer or else it triggers a crash in the OpenWatcom compiler.
+# That crash only occurs when debug information is enabled, which is the case when this test runs.
+add_library(iface_lang_cxx INTERFACE)
+target_compile_definitions(iface_lang_cxx INTERFACE EXPECT_CXX)
+target_sources(iface_lang_cxx INTERFACE FILE_SET HEADERS FILES lang_test.h)
+
+set_property(SOURCE error.h PROPERTY LANGUAGE C)
+
+add_library(list STATIC lib.c)
+target_sources(list PRIVATE
+  FILE_SET a TYPE HEADERS FILES a.h
+  FILE_SET c TYPE HEADERS FILES dir/c.h
+  FILE_SET error TYPE HEADERS FILES error.h
+)
+set_property(TARGET list PROPERTY PRIVATE_HEADER_SETS_TO_VERIFY "a;c")
+
+add_library(skip_linting STATIC lib.c)
+target_sources(skip_linting PRIVATE FILE_SET HEADERS FILES lang_test.h skip_linting.h)
+set_property(SOURCE skip_linting.h PROPERTY LANGUAGE C)
+set_property(SOURCE skip_linting.h PROPERTY SKIP_LINTING TRUE)

+ 1 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsNonexistent-result.txt

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

+ 9 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsNonexistent-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error in CMakeLists\.txt:
+  Property PRIVATE_HEADER_SETS_TO_VERIFY of target "nonexistent" contained
+  the following header sets that are nonexistent or not PRIVATE:
+
+    b
+    c
+
+
+CMake Generate step failed\.  Build files cannot be regenerated correctly\.$

+ 5 - 0
Tests/RunCMake/VerifyHeaderSets/VerifyPrivateHeaderSetsNonexistent.cmake

@@ -0,0 +1,5 @@
+enable_language(C)
+
+add_library(nonexistent STATIC lib.c)
+target_sources(nonexistent PRIVATE FILE_SET a TYPE HEADERS FILES a.h)
+set_property(TARGET nonexistent PROPERTY PRIVATE_HEADER_SETS_TO_VERIFY "a;c;b")

+ 3 - 0
Tests/RunCMake/VerifyHeaderSets/dir3/CMakeLists.txt

@@ -0,0 +1,3 @@
+add_library(lib3 STATIC ../lib.c)
+add_custom_command(OUTPUT lib3.h COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/lib3.h lib3.h)
+target_sources(lib3 PRIVATE FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR} FILES ${CMAKE_CURRENT_BINARY_DIR}/lib3.h)

+ 5 - 0
Tests/RunCMake/VerifyHeaderSets/dir3/lib3.h

@@ -0,0 +1,5 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+extern void
+lib3(void);

+ 3 - 0
Tests/RunCMake/VerifyHeaderSets/dir4/CMakeLists.txt

@@ -0,0 +1,3 @@
+add_library(lib4 STATIC ../lib.c)
+add_custom_command(OUTPUT lib4.h COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/lib4.h lib4.h)
+target_sources(lib4 PRIVATE FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR} FILES ${CMAKE_CURRENT_BINARY_DIR}/lib4.h)

+ 5 - 0
Tests/RunCMake/VerifyHeaderSets/dir4/lib4.h

@@ -0,0 +1,5 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+extern void
+lib4(void);

+ 1 - 0
Tests/RunCMake/property_init/Always.cmake

@@ -5,6 +5,7 @@ set(properties
 
   # Build graph properties
   "VERIFY_INTERFACE_HEADER_SETS"  "TRUE"    "<SAME>"
+  "VERIFY_PRIVATE_HEADER_SETS"    "TRUE"    "<SAME>"
 
   # Metadata
   "FOLDER"                        "folder"  "<SAME>"