summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Help/manual/cmake-properties.7.rst1
-rw-r--r--Help/manual/cmake-variables.7.rst1
-rw-r--r--Help/prop_tgt/VERIFY_HEADER_SETS.rst24
-rw-r--r--Help/release/dev/verify-header-sets.rst7
-rw-r--r--Help/variable/CMAKE_VERIFY_HEADER_SETS.rst17
-rw-r--r--Source/cmGeneratorTarget.cxx173
-rw-r--r--Source/cmGeneratorTarget.h5
-rw-r--r--Source/cmGlobalGenerator.cxx26
-rw-r--r--Source/cmGlobalGenerator.h2
-rw-r--r--Source/cmTarget.cxx1
-rw-r--r--Tests/RunCMake/CMakeLists.txt2
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake42
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stdout.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-check.cmake33
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stdout.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stdout.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stdout.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stdout.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-result.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-stderr.txt1
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets.cmake60
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/a.h5
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/debug.h3
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/dir/c.h8
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/dir/cxx.h8
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/lang_test.h8
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/lib.c6
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/lib.cxx6
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/main.c11
-rw-r--r--Tests/RunCMake/VerifyHeaderSets/release.h3
47 files changed, 478 insertions, 0 deletions
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 65f9248..968a3b1 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -380,6 +380,7 @@ Properties on Targets
/prop_tgt/UNITY_BUILD_CODE_BEFORE_INCLUDE
/prop_tgt/UNITY_BUILD_MODE
/prop_tgt/UNITY_BUILD_UNIQUE_ID
+ /prop_tgt/VERIFY_HEADER_SETS
/prop_tgt/VERSION
/prop_tgt/VISIBILITY_INLINES_HIDDEN
/prop_tgt/VS_CONFIGURATION_TYPE
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 8d20ae2..0b81677 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -503,6 +503,7 @@ Variables that Control the Build
/variable/CMAKE_UNITY_BUILD_BATCH_SIZE
/variable/CMAKE_UNITY_BUILD_UNIQUE_ID
/variable/CMAKE_USE_RELATIVE_PATHS
+ /variable/CMAKE_VERIFY_HEADER_SETS
/variable/CMAKE_VISIBILITY_INLINES_HIDDEN
/variable/CMAKE_VS_GLOBALS
/variable/CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD
diff --git a/Help/prop_tgt/VERIFY_HEADER_SETS.rst b/Help/prop_tgt/VERIFY_HEADER_SETS.rst
new file mode 100644
index 0000000..cbfd51b
--- /dev/null
+++ b/Help/prop_tgt/VERIFY_HEADER_SETS.rst
@@ -0,0 +1,24 @@
+VERIFY_HEADER_SETS
+------------------
+
+.. versionadded:: 3.24
+
+Used to verify that all headers in a target's 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, or executable with exports enabled, and the target
+has one or more header sets, an object library target named
+``<target_name>_verify_header_sets`` is created. This verification target has
+one source file per header in the 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.
+
+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, the header file is not compiled.
diff --git a/Help/release/dev/verify-header-sets.rst b/Help/release/dev/verify-header-sets.rst
new file mode 100644
index 0000000..7676382
--- /dev/null
+++ b/Help/release/dev/verify-header-sets.rst
@@ -0,0 +1,7 @@
+verify-header-sets
+------------------
+
+* A new :prop_tgt:`VERIFY_HEADER_SETS` target property was added, which can be
+ used to verify that all headers in header sets can be used on their own.
+* A new :variable:`CMAKE_VERIFY_HEADER_SETS` variable was added, which is used
+ to initialize the :prop_tgt:`VERIFY_HEADER_SETS` target property.
diff --git a/Help/variable/CMAKE_VERIFY_HEADER_SETS.rst b/Help/variable/CMAKE_VERIFY_HEADER_SETS.rst
new file mode 100644
index 0000000..8bd618a
--- /dev/null
+++ b/Help/variable/CMAKE_VERIFY_HEADER_SETS.rst
@@ -0,0 +1,17 @@
+CMAKE_VERIFY_HEADER_SETS
+------------------------
+
+.. versionadded:: 3.24
+
+This variable is used to initialize the :prop_tgt:`VERIFY_HEADER_SETS`
+property of targets when they are created. Setting it to true
+enables header set verification.
+
+Projects should not 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.
+
+By default, this variable is not set, which will result in header set
+verification being disabled.
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 1a13bdb..f1ab85c 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -8522,3 +8522,176 @@ cmGeneratorTarget::ManagedType cmGeneratorTarget::GetManagedType(
// has to be set manually for C# targets.
return this->IsCSharpOnly() ? ManagedType::Managed : ManagedType::Native;
}
+
+bool cmGeneratorTarget::AddHeaderSetVerification()
+{
+ if (!this->GetPropertyAsBool("VERIFY_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->IsExecutableWithExports()) {
+ return true;
+ }
+
+ cmTarget* verifyTarget = nullptr;
+
+ auto interfaceFileSetEntries = this->Target->GetInterfaceHeaderSetsEntries();
+
+ std::set<cmFileSet*> fileSets;
+ auto const addFileSets = [&fileSets, this](const cmBTStringRange& entries) {
+ for (auto const& entry : entries) {
+ for (auto const& name : cmExpandedList(entry.Value)) {
+ fileSets.insert(this->Target->GetFileSet(name));
+ }
+ }
+ };
+ addFileSets(interfaceFileSetEntries);
+
+ cm::optional<std::set<std::string>> languages;
+ for (auto* fileSet : fileSets) {
+ auto dirCges = fileSet->CompileDirectoryEntries();
+ auto fileCges = fileSet->CompileFileEntries();
+
+ static auto const contextSensitive =
+ [](const std::unique_ptr<cmCompiledGeneratorExpression>& 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)) {
+ if (first || dirCgesContextSensitive) {
+ dirs = fileSet->EvaluateDirectoryEntries(dirCges, this->LocalGenerator,
+ config, 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,
+ this->LocalGenerator, config, 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, languages);
+ if (filename.empty()) {
+ continue;
+ }
+
+ if (!verifyTarget) {
+ {
+ cmMakefile::PolicyPushPop polScope(this->Makefile);
+ this->Makefile->SetPolicy(cmPolicies::CMP0119, cmPolicies::NEW);
+ verifyTarget = this->Makefile->AddLibrary(
+ cmStrCat(this->GetName(), "_verify_header_sets"),
+ cmStateEnums::OBJECT_LIBRARY, {}, true);
+ }
+
+ 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");
+ }
+
+ if (fileCgesContextSensitive) {
+ filename = cmStrCat("$<$<CONFIG:", config, ">:", filename, ">");
+ }
+ verifyTarget->AddSource(filename);
+ }
+ }
+
+ if (!dirCgesContextSensitive && !fileCgesContextSensitive) {
+ break;
+ }
+ first = false;
+ }
+ }
+
+ if (verifyTarget) {
+ this->LocalGenerator->AddGeneratorTarget(
+ cm::make_unique<cmGeneratorTarget>(verifyTarget, this->LocalGenerator));
+ }
+
+ return true;
+}
+
+std::string cmGeneratorTarget::GenerateHeaderSetVerificationFile(
+ cmSourceFile& source, const std::string& dir,
+ cm::optional<std::set<std::string>>& languages) const
+{
+ std::string extension;
+ std::string language = source.GetOrDetermineLanguage();
+
+ if (language.empty()) {
+ if (!languages) {
+ languages.emplace();
+ for (auto const& tgtSource : this->GetAllConfigSources()) {
+ auto const& tgtSourceLanguage =
+ tgtSource.Source->GetOrDetermineLanguage();
+ if (tgtSourceLanguage == "CXX") {
+ languages->insert("CXX");
+ break; // C++ overrides everything else, so we don't need to keep
+ // checking.
+ }
+ if (tgtSourceLanguage == "C") {
+ languages->insert("C");
+ }
+ }
+ }
+
+ if (languages->count("CXX")) {
+ language = "CXX";
+ } else if (languages->count("C")) {
+ language = "C";
+ }
+ }
+
+ if (language == "C") {
+ extension = ".c";
+ } else if (language == "CXX") {
+ extension = ".cxx";
+ } else {
+ return "";
+ }
+
+ std::string headerFilename = dir;
+ if (!headerFilename.empty()) {
+ headerFilename += '/';
+ }
+ headerFilename += source.GetLocation().GetName();
+
+ auto filename = cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(),
+ '/', this->GetName(), "_verify_header_sets/",
+ headerFilename, extension);
+ auto* verificationSource = this->Makefile->GetOrCreateSource(filename);
+ verificationSource->SetProperty("LANGUAGE", language);
+
+ cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(filename));
+
+ cmGeneratedFileStream fout(filename);
+ fout.SetCopyIfDifferent(true);
+ fout << "#include <" << headerFilename << ">\n";
+ fout.close();
+
+ return filename;
+}
diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h
index 96e48a4..53844ae 100644
--- a/Source/cmGeneratorTarget.h
+++ b/Source/cmGeneratorTarget.h
@@ -868,6 +868,11 @@ public:
std::vector<std::string> GetGeneratedISPCObjects(
std::string const& config) const;
+ bool AddHeaderSetVerification();
+ std::string GenerateHeaderSetVerificationFile(
+ cmSourceFile& source, const std::string& dir,
+ cm::optional<std::set<std::string>>& languages) const;
+
private:
void AddSourceCommon(const std::string& src, bool before = false);
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index fb84cd1..3c31db1 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1501,6 +1501,11 @@ bool cmGlobalGenerator::Compute()
return false;
}
+ // Iterate through all targets and add verification targets for header sets
+ if (!this->AddHeaderSetVerification()) {
+ return false;
+ }
+
// Iterate through all targets and set up AUTOMOC, AUTOUIC and AUTORCC
if (!this->QtAutoGen()) {
return false;
@@ -1722,6 +1727,27 @@ bool cmGlobalGenerator::QtAutoGen()
#endif
}
+bool cmGlobalGenerator::AddHeaderSetVerification()
+{
+ for (auto const& gen : this->LocalGenerators) {
+ // Because AddHeaderSetVerification() adds generator targets, we need to
+ // cache the existing list of generator targets before starting.
+ std::vector<cmGeneratorTarget*> genTargets;
+ genTargets.reserve(gen->GetGeneratorTargets().size());
+ for (auto const& tgt : gen->GetGeneratorTargets()) {
+ genTargets.push_back(tgt.get());
+ }
+
+ for (auto* tgt : genTargets) {
+ if (!tgt->AddHeaderSetVerification()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
bool cmGlobalGenerator::AddAutomaticSources()
{
for (const auto& lg : this->LocalGenerators) {
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index a4b2ae3..5965a16 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -573,6 +573,8 @@ protected:
/// @return true on success
bool QtAutoGen();
+ bool AddHeaderSetVerification();
+
bool AddAutomaticSources();
std::string SelectMakeProgram(const std::string& makeProgram,
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index 4ca1b9b..e2314e2 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -425,6 +425,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
}
initProp("FOLDER");
+ initProp("VERIFY_HEADER_SETS");
if (this->GetGlobalGenerator()->IsXcode()) {
initProp("XCODE_GENERATE_SCHEME");
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index e695844..dbff293 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -978,6 +978,8 @@ add_RunCMake_test(CMakePresetsTest
-DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA}
)
+add_RunCMake_test(VerifyHeaderSets)
+
if(${CMAKE_GENERATOR} MATCHES "Make|Ninja")
add_RunCMake_test(TransformDepfile)
endif()
diff --git a/Tests/RunCMake/VerifyHeaderSets/CMakeLists.txt b/Tests/RunCMake/VerifyHeaderSets/CMakeLists.txt
new file mode 100644
index 0000000..5ff8d3e
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.23)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake b/Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake
new file mode 100644
index 0000000..06d48bf
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/RunCMakeTest.cmake
@@ -0,0 +1,42 @@
+include(RunCMake)
+
+function(run_cmake_build name target)
+ if(NOT BUILD_CONFIG)
+ set(BUILD_CONFIG Debug)
+ endif()
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
+ set(RunCMake_TEST_NO_CLEAN 1)
+ run_cmake_command(${name}-${target}-${BUILD_CONFIG}-build ${CMAKE_COMMAND} --build . --config ${BUILD_CONFIG} --target ${target})
+endfunction()
+
+set(RunCMake_TEST_OPTIONS -DCMAKE_VERIFY_HEADER_SETS=ON)
+if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+ list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
+endif()
+run_cmake(VerifyHeaderSets)
+unset(RunCMake_TEST_OPTIONS)
+
+run_cmake_build(VerifyHeaderSets static_verify_header_sets)
+run_cmake_build(VerifyHeaderSets shared_verify_header_sets)
+run_cmake_build(VerifyHeaderSets object_verify_header_sets)
+run_cmake_build(VerifyHeaderSets interface_verify_header_sets)
+run_cmake_build(VerifyHeaderSets exe_verify_header_sets)
+run_cmake_build(VerifyHeaderSets export_exe_verify_header_sets)
+run_cmake_build(VerifyHeaderSets none_verify_header_sets)
+run_cmake_build(VerifyHeaderSets property_off_verify_header_sets)
+run_cmake_build(VerifyHeaderSets private_verify_header_sets)
+run_cmake_build(VerifyHeaderSets a_h_verify_header_sets)
+run_cmake_build(VerifyHeaderSets dir_c_h_verify_header_sets)
+run_cmake_build(VerifyHeaderSets dir_cxx_h_verify_header_sets)
+
+if(NOT RunCMake_GENERATOR STREQUAL "Xcode")
+ run_cmake_build(VerifyHeaderSets config_verify_header_sets)
+ if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+ set(BUILD_CONFIG Release)
+ run_cmake_build(VerifyHeaderSets config_verify_header_sets)
+ unset(BUILD_CONFIG)
+ endif()
+endif()
+
+run_cmake_build(VerifyHeaderSets lang_test_c_verify_header_sets)
+run_cmake_build(VerifyHeaderSets lang_test_cxx_verify_header_sets)
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..b78bc52
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+(TEST_A_H defined)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stdout.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stdout.txt
new file mode 100644
index 0000000..b78bc52
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-a_h_verify_header_sets-Debug-build-stdout.txt
@@ -0,0 +1 @@
+(TEST_A_H defined)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-check.cmake b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-check.cmake
new file mode 100644
index 0000000..44e028f
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-check.cmake
@@ -0,0 +1,33 @@
+function(check_file target filename)
+ set(full_filename "${RunCMake_TEST_BINARY_DIR}/${target}_verify_header_sets/${filename}")
+ if(NOT EXISTS "${full_filename}")
+ string(APPEND RunCMake_TEST_FAILED "File ${full_filename} should exist but does not\n")
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+ return()
+ endif()
+
+ if(filename MATCHES "^(.*)(\\.[a-z]+)$")
+ set(header_filename "${CMAKE_MATCH_1}")
+ endif()
+ set(expected_contents "#include <${header_filename}>\n")
+ file(READ "${full_filename}" actual_contents)
+
+ if(NOT actual_contents STREQUAL expected_contents)
+ string(REPLACE "\n" "\n " expected_contents_formatted "${expected_contents}")
+ string(REPLACE "\n" "\n " actual_contents_formatted "${actual_contents}")
+ string(APPEND RunCMake_TEST_FAILED "Expected contents of ${full_filename}:\n ${expected_contents_formatted}\nActual contents:\n ${actual_contents_formatted}\n")
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+ return()
+ endif()
+endfunction()
+
+check_file(static a.h.c)
+check_file(static dir/c.h.c)
+check_file(static dir/cxx.h.cxx)
+
+if(NOT RunCMake_GENERATOR STREQUAL "Xcode")
+ check_file(config debug.h.c)
+ if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+ check_file(config release.h.c)
+ endif()
+endif()
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..eaa9a03
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+(Compiled in debug mode)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stdout.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stdout.txt
new file mode 100644
index 0000000..eaa9a03
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Debug-build-stdout.txt
@@ -0,0 +1 @@
+(Compiled in debug mode)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stderr.txt
new file mode 100644
index 0000000..25699f9
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stderr.txt
@@ -0,0 +1 @@
+(Compiled in release mode)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stdout.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stdout.txt
new file mode 100644
index 0000000..25699f9
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-config_verify_header_sets-Release-build-stdout.txt
@@ -0,0 +1 @@
+(Compiled in release mode)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..27ef042
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+(TEST_DIR_C_H defined)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stdout.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stdout.txt
new file mode 100644
index 0000000..27ef042
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_c_h_verify_header_sets-Debug-build-stdout.txt
@@ -0,0 +1 @@
+(TEST_DIR_C_H defined)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..cd17d11
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+(TEST_DIR_CXX_H defined)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stdout.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stdout.txt
new file mode 100644
index 0000000..cd17d11
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-dir_cxx_h_verify_header_sets-Debug-build-stdout.txt
@@ -0,0 +1 @@
+(TEST_DIR_CXX_H defined)?
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-exe_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-none_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-private_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-result.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-stderr.txt b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets-property_off_verify_header_sets-Debug-build-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets.cmake b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets.cmake
new file mode 100644
index 0000000..f515031
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/VerifyHeaderSets.cmake
@@ -0,0 +1,60 @@
+enable_language(C CXX)
+
+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 INTERFACE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(shared SHARED lib.c)
+target_sources(shared INTERFACE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(object OBJECT lib.c)
+target_sources(object INTERFACE 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)
+
+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)
+
+add_library(none STATIC lib.c)
+
+add_library(property_off STATIC lib.c)
+target_sources(property_off INTERFACE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+set_property(TARGET property_off PROPERTY VERIFY_HEADER_SETS OFF)
+
+add_library(private STATIC lib.c)
+target_sources(private PRIVATE FILE_SET HEADERS FILES a.h dir/c.h dir/cxx.h)
+
+add_library(a_h STATIC lib.c)
+target_compile_definitions(a_h INTERFACE TEST_A_H)
+target_sources(a_h INTERFACE 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 INTERFACE TEST_DIR_C_H)
+target_sources(dir_c_h INTERFACE 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 INTERFACE TEST_DIR_CXX_H)
+target_sources(dir_cxx_h INTERFACE 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 INTERFACE 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 INTERFACE FILE_SET HEADERS FILES lang_test.h)
+
+add_library(lang_test_cxx STATIC lib.c lib.cxx)
+target_compile_definitions(lang_test_cxx INTERFACE EXPECT_CXX)
+target_sources(lang_test_cxx INTERFACE FILE_SET HEADERS FILES lang_test.h)
diff --git a/Tests/RunCMake/VerifyHeaderSets/a.h b/Tests/RunCMake/VerifyHeaderSets/a.h
new file mode 100644
index 0000000..8b17182
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/a.h
@@ -0,0 +1,5 @@
+#ifdef TEST_A_H
+# error "TEST_A_H defined"
+#endif
+
+extern void a_h(void);
diff --git a/Tests/RunCMake/VerifyHeaderSets/debug.h b/Tests/RunCMake/VerifyHeaderSets/debug.h
new file mode 100644
index 0000000..4d4baa1
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/debug.h
@@ -0,0 +1,3 @@
+#error "Compiled in debug mode"
+
+extern void debug_h(void);
diff --git a/Tests/RunCMake/VerifyHeaderSets/dir/c.h b/Tests/RunCMake/VerifyHeaderSets/dir/c.h
new file mode 100644
index 0000000..151cd81
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/dir/c.h
@@ -0,0 +1,8 @@
+#ifdef TEST_DIR_C_H
+# error "TEST_DIR_C_H defined"
+#endif
+#ifdef __cplusplus
+# error "__cplusplus defined"
+#endif
+
+extern void dir_c_h(void);
diff --git a/Tests/RunCMake/VerifyHeaderSets/dir/cxx.h b/Tests/RunCMake/VerifyHeaderSets/dir/cxx.h
new file mode 100644
index 0000000..255f61b
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/dir/cxx.h
@@ -0,0 +1,8 @@
+#ifdef TEST_DIR_CXX_H
+# error "TEST_DIR_CXX_H defined"
+#endif
+#ifndef __cplusplus
+# error "__cplusplus not defined"
+#endif
+
+extern void dir_cxx_h(void);
diff --git a/Tests/RunCMake/VerifyHeaderSets/lang_test.h b/Tests/RunCMake/VerifyHeaderSets/lang_test.h
new file mode 100644
index 0000000..633a2a4
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/lang_test.h
@@ -0,0 +1,8 @@
+#if defined(__cplusplus) && !defined(EXPECT_CXX)
+# error "__cplusplus defined but EXPECT_CXX not defined"
+#endif
+#if !defined(__cplusplus) && defined(EXPECT_CXX)
+# error "__cplusplus not defined but EXPECT_CXX defined"
+#endif
+
+extern void lang_test_h(void);
diff --git a/Tests/RunCMake/VerifyHeaderSets/lib.c b/Tests/RunCMake/VerifyHeaderSets/lib.c
new file mode 100644
index 0000000..6401eca
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/lib.c
@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+ void lib_c(void)
+{
+}
diff --git a/Tests/RunCMake/VerifyHeaderSets/lib.cxx b/Tests/RunCMake/VerifyHeaderSets/lib.cxx
new file mode 100644
index 0000000..a0b3096
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/lib.cxx
@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+ void lib_cxx(void)
+{
+}
diff --git a/Tests/RunCMake/VerifyHeaderSets/main.c b/Tests/RunCMake/VerifyHeaderSets/main.c
new file mode 100644
index 0000000..8a83e8c
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/main.c
@@ -0,0 +1,11 @@
+int main(void)
+{
+ return 0;
+}
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+ void main_c(void)
+{
+}
diff --git a/Tests/RunCMake/VerifyHeaderSets/release.h b/Tests/RunCMake/VerifyHeaderSets/release.h
new file mode 100644
index 0000000..7641988
--- /dev/null
+++ b/Tests/RunCMake/VerifyHeaderSets/release.h
@@ -0,0 +1,3 @@
+#error "Compiled in release mode"
+
+extern void release_h(void);