Merge topic 'always-create-all-verify-targets'

d6ecae0600 VERIFY_*_HEADER_SETS: Create verification targets even when empty
2f96373389 cmGeneratorTarget: refactor header set verification to collect then create

Acked-by: Kitware Robot <kwrobot@kitware.com>
Tested-by: buildbot <buildbot@kitware.com>
Merge-request: !11807
This commit is contained in:
Brad King
2026-03-19 15:37:13 +00:00
committed by Kitware Robot
17 changed files with 160 additions and 115 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

@@ -5863,6 +5863,10 @@ bool cmGeneratorTarget::AddHeaderSetVerification()
}
cm::optional<cm::optional<std::string>> defaultLanguage;
// First, collect all verification stubs before creating the target,
// so we know whether to create an OBJECT library or not.
std::vector<std::string> stubSources;
for (auto const* fileSet : fileSets) {
auto const& dirCges = fileSet->CompileDirectoryEntries();
auto const& fileCges = fileSet->CompileFileEntries();
@@ -5907,93 +5911,10 @@ bool cmGeneratorTarget::AddHeaderSetVerification()
}
std::string filename = *filenameOpt;
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.
// Copy language-standard properties for all supported
// languages. We don't care if we set properties for languages
// this target won't eventually use.
static std::array<std::string, 19> const 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", "OBJC_STANDARD",
"OBJC_STANDARD_REQUIRED", "OBJC_EXTENSIONS",
"OBJCXX_STANDARD", "OBJCXX_STANDARD_REQUIRED",
"OBJCXX_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);
}
if (fileCgesContextSensitive) {
filename = cmStrCat("$<$<CONFIG:", config, ">:", filename, '>');
}
verifyTarget->AddSource(filename);
stubSources.emplace_back(std::move(filename));
}
}
@@ -6004,13 +5925,99 @@ bool cmGeneratorTarget::AddHeaderSetVerification()
}
}
if (verifyTarget) {
this->LocalGenerator->AddGeneratorTarget(
cm::make_unique<cmGeneratorTarget>(verifyTarget,
this->LocalGenerator));
}
}
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);
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.
// Copy language-standard properties for all supported
// languages. We don't care if we set properties for languages
// this target won't eventually use.
static std::array<std::string, 19> const 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", "OBJC_STANDARD",
"OBJC_STANDARD_REQUIRED", "OBJC_EXTENSIONS",
"OBJCXX_STANDARD", "OBJCXX_STANDARD_REQUIRED",
"OBJCXX_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);
}
}
for (auto const& source : stubSources) {
verifyTarget->AddSource(source);
}
}
if (!allVerifyTarget) {
allVerifyTarget =
this->GlobalGenerator->GetMakefiles().front()->AddNewUtilityTarget(
allVerifyTargetName, true);
}
allVerifyTarget->AddUtility(verifyTargetName, false);
this->LocalGenerator->AddGeneratorTarget(
cm::make_unique<cmGeneratorTarget>(verifyTarget, this->LocalGenerator));
}
return true;
}

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)