VERIFY_*_HEADER_SETS: Create verification targets even when empty

Always create `<target>_verify_*_header_sets` targets and the aggregate
`all_verify_*_header_sets` utility targets when the corresponding
property is enabled, even when no matching headers exist.

When headers exist, an OBJECT library is created as before. When no
headers are found, a UTILITY target is created instead to avoid
warnings from tools like Xcode's libtool about empty static libraries.

Update documentation with `.. versionchanged:: 4.4` directives, update
tests, and add a release note.

Fixes: #27685
This commit is contained in:
Mickaël Germain
2026-03-17 20:42:00 -07:00
parent 2f96373389
commit d6ecae0600
17 changed files with 72 additions and 26 deletions

View File

@@ -10,13 +10,20 @@ VERIFY_INTERFACE_HEADER_SETS
Used to verify that all headers in a target's ``PUBLIC`` and ``INTERFACE``
header sets can be included on their own.
.. versionchanged:: 4.4
Previously, the verification target was only created when the target had
at least one matching header. Now it is always created when the property
is enabled.
When this property is set to true, and the target is an object library, static
library, shared library, interface library, or executable (subject to policy
:policy:`CMP0209`) and the target has one or more ``PUBLIC`` or ``INTERFACE``
header sets, an object library target named
:policy:`CMP0209`), an object library target named
``<target_name>_verify_interface_header_sets`` is created. This verification
target has one source file per header in the ``PUBLIC`` and ``INTERFACE``
header sets. Each source file only includes its associated header file.
target has one source file per header in the target's ``PUBLIC`` and
``INTERFACE`` header sets. Each source file only includes its associated
header file. If the target has no matching header sets, a utility target is
created instead so that the target name always exists for build system
dependencies.
The verification target links against the original target to get all of its
usage requirements.

View File

@@ -10,13 +10,19 @@ VERIFY_PRIVATE_HEADER_SETS
Used to verify that all headers in a target's ``PUBLIC`` and ``PRIVATE``
header sets can be included on their own.
.. versionchanged:: 4.4
Previously, the verification target was only created when the target had
at least one matching header. Now it is always created when the property
is enabled.
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.
library, shared library, module library, interface library, or executable, an
object library target named ``<target_name>_verify_private_header_sets`` is
created. This verification target has one source file per header in the
target's ``PUBLIC`` and ``PRIVATE`` header sets. Each source file only
includes its associated header file. If the target has no matching header
sets, a utility target is created instead so that the target name always
exists for build system dependencies.
Properties affecting compilation are copied from the original target to the
verification target so that the headers will be interpreted the same way by

View File

@@ -26,11 +26,10 @@ 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|).
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.

View File

@@ -0,0 +1,9 @@
always-create-all-verify-targets
---------------------------------
* The :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` and
:prop_tgt:`VERIFY_PRIVATE_HEADER_SETS` target properties now always
create the per-target verification target and the aggregate
``all_verify_interface_header_sets``, ``all_verify_private_header_sets``,
and ``all_verify_header_sets`` targets when the property is enabled,
even if the target has no matching header sets.

View File

@@ -5925,7 +5925,15 @@ bool cmGeneratorTarget::AddHeaderSetVerification()
}
}
if (!stubSources.empty()) {
if (stubSources.empty()) {
// No headers to verify. Create a utility target so the target
// name always exists (e.g. for build system dependencies) without
// needing a placeholder source. This avoids warnings from tools
// like Xcode's libtool about empty static libraries.
verifyTarget =
this->Makefile->AddNewUtilityTarget(verifyTargetName, true);
} else {
// Create an OBJECT library to compile the verification stubs.
{
cmMakefile::PolicyPushPop polScope(this->Makefile);
this->Makefile->SetPolicy(cmPolicies::CMP0119, cmPolicies::NEW);

View File

@@ -125,3 +125,21 @@ run_cmake_build(AllVerifyPrivateHeaderSets private all)
set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_PRIVATE_HEADER_SETS=ON)
run_cmake(VerifyPrivateHeaderSetsNonexistent)
unset(RunCMake_TEST_OPTIONS)
# Test that verify targets are always created even with no headers
if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
endif()
run_cmake(VerifyEmptyHeaderSets)
unset(RunCMake_TEST_OPTIONS)
run_cmake_build(VerifyEmptyHeaderSets interface empty_iface)
run_cmake_build(VerifyEmptyHeaderSets private empty_priv)
run_cmake_build(VerifyEmptyHeaderSets interface all)
run_cmake_build(VerifyEmptyHeaderSets private all)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/VerifyEmptyHeaderSets-build)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(VerifyEmptyHeaderSets-all_verify_header_sets-Debug-build
${CMAKE_COMMAND} --build . --config Debug --target all_verify_header_sets
)

View File

@@ -0,0 +1,9 @@
enable_language(C)
# Target with VERIFY_INTERFACE_HEADER_SETS ON but no interface file sets
add_library(empty_iface STATIC lib.c)
set_property(TARGET empty_iface PROPERTY VERIFY_INTERFACE_HEADER_SETS ON)
# Target with VERIFY_PRIVATE_HEADER_SETS ON but no private file sets
add_library(empty_priv STATIC lib.c)
set_property(TARGET empty_priv PROPERTY VERIFY_PRIVATE_HEADER_SETS ON)