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
This commit is contained in:
Craig Scott
2026-01-16 18:10:17 +11:00
parent 61df2e15a2
commit b61637f90f
53 changed files with 642 additions and 221 deletions
+2
View File
@@ -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
View File
@@ -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
@@ -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`.
@@ -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`.
+9 -26
View File
@@ -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.
usage requirements.
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`.
.. |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
@@ -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
@@ -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|.
@@ -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.
@@ -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
@@ -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
@@ -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
View File
@@ -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()
+202 -131
View File
@@ -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;
}
for (bool const isInterface : { false, true }) {
if (!this->GetPropertyAsBool(isInterface ? "VERIFY_INTERFACE_HEADER_SETS"
: "VERIFY_PRIVATE_HEADER_SETS")) {
continue;
}
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;
}
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 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());
}
char const* headerSetsProperty = isInterface
? "INTERFACE_HEADER_SETS_TO_VERIFY"
: "PRIVATE_HEADER_SETS_TO_VERIFY";
cmTarget* verifyTarget = nullptr;
std::string const verifyTargetName =
cmStrCat(this->GetName(), "_verify_interface_header_sets");
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());
}
char const* allVerifyTargetName = "all_verify_interface_header_sets";
cmTarget* allVerifyTarget =
this->GlobalGenerator->GetMakefiles().front()->FindTargetToUse(
allVerifyTargetName, { cmStateEnums::TargetDomain::NATIVE });
cmTarget* verifyTarget = nullptr;
std::string const verifyTargetName =
cmStrCat(this->GetName(),
isInterface ? "_verify_interface_header_sets"
: "_verify_private_header_sets");
auto interfaceFileSetEntries = this->Target->GetInterfaceHeaderSetsEntries();
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 });
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);
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);
}
}
}
}
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 (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;
}
}
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();
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;
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;
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;
}
}
}
}
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 (!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 (!verifyTarget) {
{
cmMakefile::PolicyPushPop polScope(this->Makefile);
this->Makefile->SetPolicy(cmPolicies::CMP0119,
cmPolicies::NEW);
verifyTarget = this->Makefile->AddLibrary(
verifyTargetName, cmStateEnums::OBJECT_LIBRARY, {}, true);
}
if (!allVerifyTarget) {
allVerifyTarget =
this->GlobalGenerator->GetMakefiles()
.front()
->AddNewUtilityTarget(allVerifyTargetName, 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, '>');
}
verifyTarget->AddSource(filename);
}
if (fileCgesContextSensitive) {
filename = cmStrCat("$<$<CONFIG:", config, ">:", 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
View File
@@ -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
View File
@@ -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 },
@@ -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'
@@ -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}'")
@@ -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
)
@@ -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()
@@ -0,0 +1,4 @@
enable_language(C)
add_subdirectory(dir3)
add_subdirectory(dir4)
@@ -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)
@@ -0,0 +1 @@
(TEST_A_H defined)?
@@ -0,0 +1 @@
(TEST_A_H defined)?
@@ -0,0 +1 @@
(Compiled in debug mode)?
@@ -0,0 +1 @@
(Compiled in debug mode)?
@@ -0,0 +1 @@
(Compiled in release mode)?
@@ -0,0 +1 @@
(Compiled in release mode)?
@@ -0,0 +1 @@
(TEST_DIR_C_H defined)?
@@ -0,0 +1 @@
(TEST_DIR_C_H defined)?
@@ -0,0 +1 @@
(TEST_DIR_CXX_H defined)?
@@ -0,0 +1 @@
(TEST_DIR_CXX_H defined)?
@@ -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)
@@ -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\.$
@@ -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")
@@ -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)
@@ -0,0 +1,5 @@
#ifdef _WIN32
__declspec(dllimport)
#endif
extern void
lib3(void);
@@ -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)
@@ -0,0 +1,5 @@
#ifdef _WIN32
__declspec(dllimport)
#endif
extern void
lib4(void);
@@ -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>"