File sets: Always verify interface files sets of executable targets

When VERIFY_INTERFACE_FILE_SETS is set to true on an executable
target, verification of that target's interface file sets was only being
performed if the target's ENABLE_EXPORTS property was set to true.
We now always verify the interface file sets of executable targets,
subject to the new CMP0209 policy. This allows interface file sets to
be verified even for executable targets that do not export symbols.
Interface file sets containing headers that are fully inlined is an
example use case for this scenario. Unit test executables may link
to the executable under test, possibly with $<COMPILE_ONLY:...>.

Issue: #23448 #27046
This commit is contained in:
Craig Scott
2026-01-16 18:10:14 +11:00
parent c7a1d5ee58
commit 20219414c3
14 changed files with 104 additions and 9 deletions
+1
View File
@@ -100,6 +100,7 @@ Policies Introduced by CMake 4.3
.. toctree::
:maxdepth: 1
CMP0209: Verify interface header sets checks executables without exports. </policy/CMP0209>
CMP0208: export(EXPORT) does not allow empty arguments. </policy/CMP0208>
CMP0207: file(GET_RUNTIME_DEPENDENCIES) normalizes paths before matching. </policy/CMP0207>
CMP0206: The CPack Archive Generator defaults to UID 0 and GID 0. </policy/CMP0206>
+27
View File
@@ -0,0 +1,27 @@
CMP0209
-------
.. versionadded:: 4.3
Verify interface header sets checks executables without exports.
When :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` is set to true on an executable
target, CMake 4.2 and below would only perform those checks if the target's
:prop_tgt:`ENABLE_EXPORTS` property was true. The reasoning behind this
exclusion was that no other target could consume the headers of an executable
target if that executable target didn't export symbols. Since then, other
use cases have emerged for verifying header file sets where exporting symbols
is no longer a requirement. Therefore, CMake 4.3 and above prefer to verify
interface file sets of executable targets regardless of whether they export
symbols or not.
The ``OLD`` behavior of this policy only verifies interface file sets of an
executable target if its :prop_tgt:`ENABLE_EXPORTS` property is set to true.
The ``NEW`` behavior always verifies interface file sets of an executable
target when :prop_tgt:`VERIFY_INTERFACE_HEADER_SETS` is set to true.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.3
.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
.. include:: include/STANDARD_ADVICE.rst
.. include:: include/DEPRECATED.rst
@@ -7,13 +7,14 @@ Used to verify that all headers in a target's ``PUBLIC`` and ``INTERFACE``
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, interface library, or executable with exports enabled,
and the target has one or more ``PUBLIC`` or ``INTERFACE`` header sets, 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. The verification target links against the original
target to get all of its usage requirements. The verification target has its
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
``<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.
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`, and :prop_tgt:`UNITY_BUILD` properties set to false.
@@ -0,0 +1,6 @@
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`.
+19 -1
View File
@@ -5712,7 +5712,7 @@ bool cmGeneratorTarget::AddHeaderSetVerification()
this->GetType() != cmStateEnums::UNKNOWN_LIBRARY &&
this->GetType() != cmStateEnums::OBJECT_LIBRARY &&
this->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
!this->IsExecutableWithExports()) {
this->GetType() != cmStateEnums::EXECUTABLE) {
return true;
}
@@ -5741,6 +5741,24 @@ bool cmGeneratorTarget::AddHeaderSetVerification()
}
}
}
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"));
}
return true;
}
if (!verifySet.empty()) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
+5 -1
View File
@@ -623,6 +623,9 @@ class cmMakefile;
"file(GET_RUNTIME_DEPENDENCIES) normalizes paths before matching.", \
4, 3, 0, WARN) \
SELECT(POLICY, CMP0208, "export(EXPORT) does not allow empty arguments.", \
4, 3, 0, WARN) \
SELECT(POLICY, CMP0209, \
"Verify interface header sets checks executables without exports.", \
4, 3, 0, WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
@@ -676,7 +679,8 @@ class cmMakefile;
F(CMP0200) \
F(CMP0202) \
F(CMP0203) \
F(CMP0204)
F(CMP0204) \
F(CMP0209)
#define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F) \
F(CMP0116) \
@@ -52,6 +52,7 @@
\* CMP0202
\* CMP0203
\* CMP0204
\* CMP0209
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
@@ -55,3 +55,7 @@ run_cmake_build(AllVerifyInterfaceHeaderSets interface all)
set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_INTERFACE_HEADER_SETS=ON)
run_cmake(VerifyInterfaceHeaderSetsNonexistent)
unset(RunCMake_TEST_OPTIONS)
run_cmake(VerifyInterfaceHeaderSets-CMP0209-NEW)
run_cmake(VerifyInterfaceHeaderSets-CMP0209-OLD)
run_cmake(VerifyInterfaceHeaderSets-CMP0209-WARN)
@@ -0,0 +1,2 @@
cmake_policy(SET CMP0209 NEW)
include(VerifyInterfaceHeaderSets-CMP0209.cmake)
@@ -0,0 +1,2 @@
cmake_policy(SET CMP0209 OLD)
include(VerifyInterfaceHeaderSets-CMP0209.cmake)
@@ -0,0 +1,10 @@
CMake Warning \(dev\) in CMakeLists\.txt:
Policy CMP0209 is not set: Verify interface header sets checks executables
without exports\. Run "cmake --help-policy CMP0209" for policy details\.
Use the cmake_policy command to set the policy and suppress this warning\.
Executable target "exe" has interface header file sets, but it does not
enable exports\. Those headers would be verified under CMP0209 NEW
behavior\.
This warning is for project developers\. Use -Wno-dev to suppress it\.
@@ -0,0 +1 @@
include(VerifyInterfaceHeaderSets-CMP0209.cmake)
@@ -0,0 +1,16 @@
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)
set(CMAKE_VERIFY_INTERFACE_HEADER_SETS ON)
add_executable(exe main.c)
target_sources(exe INTERFACE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
add_executable(export_exe main.c)
set_property(TARGET export_exe PROPERTY ENABLE_EXPORTS TRUE)
target_sources(export_exe INTERFACE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
@@ -1,3 +1,5 @@
cmake_policy(SET CMP0209 NEW)
enable_language(C CXX)
add_compile_definitions(TEST_ADD_COMPILE_DEFINITIONS)