mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
cmGeneratorTarget: move header set verification to dedicated file
Move AddHeaderSetVerification, GenerateHeaderSetVerificationFile, ResolveHeaderLanguage, and GenerateStubForLanguage out of the monolithic cmGeneratorTarget.cxx into a new split file following the existing cmGeneratorTarget_*.cxx pattern.
This commit is contained in:
committed by
Brad King
parent
d6ecae0600
commit
448dd01b1f
@@ -311,6 +311,7 @@ add_library(
|
||||
cmGeneratorTarget.cxx
|
||||
cmGeneratorTarget.h
|
||||
cmGeneratorTarget_CompatibleInterface.cxx
|
||||
cmGeneratorTarget_HeaderSetVerification.cxx
|
||||
cmGeneratorTarget_IncludeDirectories.cxx
|
||||
cmGeneratorTarget_Link.cxx
|
||||
cmGeneratorTarget_LinkDirectories.cxx
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "cmPropertyMap.h"
|
||||
#include "cmRulePlaceholderExpander.h"
|
||||
#include "cmSourceFile.h"
|
||||
#include "cmSourceFileLocation.h"
|
||||
#include "cmSourceFileLocationKind.h"
|
||||
#include "cmStandardLevel.h"
|
||||
#include "cmStandardLevelResolver.h"
|
||||
@@ -5775,393 +5774,6 @@ cmGeneratorTarget::ManagedType cmGeneratorTarget::GetManagedType(
|
||||
return this->IsCSharpOnly() ? ManagedType::Managed : ManagedType::Native;
|
||||
}
|
||||
|
||||
bool cmGeneratorTarget::AddHeaderSetVerification()
|
||||
{
|
||||
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::MODULE_LIBRARY || isInterface) &&
|
||||
this->GetType() != cmStateEnums::UNKNOWN_LIBRARY &&
|
||||
this->GetType() != cmStateEnums::OBJECT_LIBRARY &&
|
||||
this->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
|
||||
this->GetType() != cmStateEnums::EXECUTABLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char const* headerSetsProperty = isInterface
|
||||
? "INTERFACE_HEADER_SETS_TO_VERIFY"
|
||||
: "PRIVATE_HEADER_SETS_TO_VERIFY";
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
cmTarget* verifyTarget = nullptr;
|
||||
std::string const verifyTargetName =
|
||||
cmStrCat(this->GetName(),
|
||||
isInterface ? "_verify_interface_header_sets"
|
||||
: "_verify_private_header_sets");
|
||||
|
||||
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 });
|
||||
|
||||
auto fileSetEntries = isInterface
|
||||
? this->GetInterfaceFileSets(cm::FileSetMetadata::HEADERS)
|
||||
: this->GetFileSets(cm::FileSetMetadata::HEADERS);
|
||||
|
||||
std::set<cmGeneratorFileSet const*> fileSets;
|
||||
for (auto const& fileSet : fileSetEntries) {
|
||||
if (all || verifySet.count(fileSet->GetName())) {
|
||||
fileSets.insert(fileSet);
|
||||
verifySet.erase(fileSet->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
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::FATAL_ERROR,
|
||||
cmStrCat("Property ", headerSetsProperty, " of target \"",
|
||||
this->GetName(),
|
||||
"\" contained the following header sets that are nonexistent "
|
||||
"or not ",
|
||||
isInterface ? "INTERFACE" : "PRIVATE", ":\n ",
|
||||
cmJoin(verifySet, "\n ")));
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& files : filesPerDir) {
|
||||
for (auto const& file : files.second) {
|
||||
cm::optional<std::string> filenameOpt =
|
||||
this->GenerateHeaderSetVerificationFile(
|
||||
*this->Makefile->GetOrCreateSource(file), files.first,
|
||||
verifyTargetName, defaultLanguage);
|
||||
if (!filenameOpt) {
|
||||
continue;
|
||||
}
|
||||
std::string filename = *filenameOpt;
|
||||
|
||||
if (fileCgesContextSensitive) {
|
||||
filename = cmStrCat("$<$<CONFIG:", config, ">:", filename, '>');
|
||||
}
|
||||
stubSources.emplace_back(std::move(filename));
|
||||
}
|
||||
}
|
||||
|
||||
if (!dirCgesContextSensitive && !fileCgesContextSensitive) {
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmGeneratorTarget::GenerateHeaderSetVerificationFile(
|
||||
cmSourceFile& source, std::string const& dir,
|
||||
std::string const& verifyTargetName,
|
||||
cm::optional<cm::optional<std::string>>& defaultLanguage) const
|
||||
{
|
||||
if (source.GetPropertyAsBool("SKIP_LINTING")) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
cm::optional<std::string> language =
|
||||
this->ResolveHeaderLanguage(source, defaultLanguage);
|
||||
if (!language) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
std::string headerFilename = dir;
|
||||
if (!headerFilename.empty()) {
|
||||
headerFilename += '/';
|
||||
}
|
||||
headerFilename += source.GetLocation().GetName();
|
||||
|
||||
return this->GenerateStubForLanguage(*language, headerFilename,
|
||||
verifyTargetName, source);
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmGeneratorTarget::ResolveHeaderLanguage(
|
||||
cmSourceFile& source,
|
||||
cm::optional<cm::optional<std::string>>& defaultLanguage) const
|
||||
{
|
||||
static std::array<cm::string_view, 4> const supportedLangs{ {
|
||||
"C",
|
||||
"CXX",
|
||||
"OBJC",
|
||||
"OBJCXX",
|
||||
} };
|
||||
auto isSupported = [](cm::string_view lang) -> bool {
|
||||
return std::find(supportedLangs.begin(), supportedLangs.end(), lang) !=
|
||||
supportedLangs.end();
|
||||
};
|
||||
|
||||
// If the source has an explicit language, validate and return it.
|
||||
std::string language = source.GetOrDetermineLanguage();
|
||||
if (!language.empty()) {
|
||||
if (!isSupported(language)) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
return cm::optional<std::string>(std::move(language));
|
||||
}
|
||||
|
||||
/*
|
||||
Compute and cache the default language for unlanguaged headers.
|
||||
The lattice join is run once per file set, not once per header.
|
||||
Lattice: OBJCXX
|
||||
/ \
|
||||
CXX OBJC
|
||||
\ /
|
||||
C
|
||||
*/
|
||||
if (!defaultLanguage) {
|
||||
std::set<std::string> langs;
|
||||
for (AllConfigSource const& tgtSource : this->GetAllConfigSources()) {
|
||||
std::string const& lang = tgtSource.Source->GetOrDetermineLanguage();
|
||||
if (isSupported(lang)) {
|
||||
langs.insert(lang);
|
||||
}
|
||||
}
|
||||
if (langs.empty()) {
|
||||
std::vector<std::string> languagesVector;
|
||||
this->GlobalGenerator->GetEnabledLanguages(languagesVector);
|
||||
for (std::string const& lang : languagesVector) {
|
||||
if (isSupported(lang)) {
|
||||
langs.insert(lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cm::optional<std::string> resolved;
|
||||
if (langs.count("OBJCXX") || (langs.count("CXX") && langs.count("OBJC"))) {
|
||||
resolved = "OBJCXX"; // promote
|
||||
} else if (langs.count("CXX")) {
|
||||
resolved = "CXX";
|
||||
} else if (langs.count("OBJC")) {
|
||||
resolved = "OBJC";
|
||||
} else if (langs.count("C")) {
|
||||
resolved = "C";
|
||||
}
|
||||
defaultLanguage = resolved;
|
||||
}
|
||||
|
||||
return *defaultLanguage;
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmGeneratorTarget::GenerateStubForLanguage(
|
||||
std::string const& language, std::string const& headerFilename,
|
||||
std::string const& verifyTargetName, cmSourceFile& source) const
|
||||
{
|
||||
static std::array<std::pair<cm::string_view, cm::string_view>, 4> const
|
||||
langToExt = { {
|
||||
{ "C", ".c" },
|
||||
{ "CXX", ".cxx" },
|
||||
{ "OBJC", ".m" },
|
||||
{ "OBJCXX", ".mm" },
|
||||
} };
|
||||
|
||||
// NOLINTNEXTLINE(readability-qualified-auto)
|
||||
auto const it =
|
||||
std::find_if(langToExt.begin(), langToExt.end(),
|
||||
[&](std::pair<cm::string_view, cm::string_view> const& p) {
|
||||
return p.first == language;
|
||||
});
|
||||
if (it == langToExt.end()) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
std::string filename =
|
||||
cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/',
|
||||
verifyTargetName, '/', headerFilename, it->second);
|
||||
|
||||
cmSourceFile* verificationSource =
|
||||
this->Makefile->GetOrCreateSource(filename);
|
||||
source.SetSpecialSourceType(
|
||||
cmSourceFile::SpecialSourceType::HeaderSetVerificationSource);
|
||||
verificationSource->SetProperty("LANGUAGE", language);
|
||||
|
||||
cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(filename));
|
||||
|
||||
cmGeneratedFileStream fout(filename);
|
||||
fout.SetCopyIfDifferent(true);
|
||||
// The IWYU "associated" pragma tells include-what-you-use to
|
||||
// consider the headerFile as part of the entire language
|
||||
// unit within include-what-you-use and as a result allows
|
||||
// one to get IWYU advice for headers.
|
||||
// Also suppress clang-tidy include checks in generated code.
|
||||
fout
|
||||
<< "/* NOLINTNEXTLINE(misc-header-include-cycle,misc-include-cleaner) */\n"
|
||||
<< "#include <" << headerFilename << "> /* IWYU pragma: associated */\n";
|
||||
fout.close();
|
||||
|
||||
return cm::optional<std::string>(std::move(filename));
|
||||
}
|
||||
|
||||
std::string cmGeneratorTarget::GetImportedXcFrameworkPath(
|
||||
std::string const& config) const
|
||||
{
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||||
/* clang-format off */
|
||||
#include "cmGeneratorTarget.h"
|
||||
/* clang-format on */
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/memory>
|
||||
#include <cm/optional>
|
||||
#include <cm/string_view>
|
||||
|
||||
#include "cmFileSetMetadata.h"
|
||||
#include "cmGenExContext.h"
|
||||
#include "cmGeneratedFileStream.h"
|
||||
#include "cmGeneratorExpression.h"
|
||||
#include "cmGeneratorFileSet.h"
|
||||
#include "cmGlobalGenerator.h"
|
||||
#include "cmList.h"
|
||||
#include "cmLocalGenerator.h"
|
||||
#include "cmMakefile.h"
|
||||
#include "cmMessageType.h"
|
||||
#include "cmPolicies.h"
|
||||
#include "cmSourceFile.h"
|
||||
#include "cmSourceFileLocation.h"
|
||||
#include "cmStateTypes.h"
|
||||
#include "cmStringAlgorithms.h"
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmTarget.h"
|
||||
#include "cmTargetLinkLibraryType.h"
|
||||
#include "cmValue.h"
|
||||
|
||||
bool cmGeneratorTarget::AddHeaderSetVerification()
|
||||
{
|
||||
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::MODULE_LIBRARY || isInterface) &&
|
||||
this->GetType() != cmStateEnums::UNKNOWN_LIBRARY &&
|
||||
this->GetType() != cmStateEnums::OBJECT_LIBRARY &&
|
||||
this->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
|
||||
this->GetType() != cmStateEnums::EXECUTABLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char const* headerSetsProperty = isInterface
|
||||
? "INTERFACE_HEADER_SETS_TO_VERIFY"
|
||||
: "PRIVATE_HEADER_SETS_TO_VERIFY";
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
cmTarget* verifyTarget = nullptr;
|
||||
std::string const verifyTargetName =
|
||||
cmStrCat(this->GetName(),
|
||||
isInterface ? "_verify_interface_header_sets"
|
||||
: "_verify_private_header_sets");
|
||||
|
||||
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 });
|
||||
|
||||
auto fileSetEntries = isInterface
|
||||
? this->GetInterfaceFileSets(cm::FileSetMetadata::HEADERS)
|
||||
: this->GetFileSets(cm::FileSetMetadata::HEADERS);
|
||||
|
||||
std::set<cmGeneratorFileSet const*> fileSets;
|
||||
for (auto const& fileSet : fileSetEntries) {
|
||||
if (all || verifySet.count(fileSet->GetName())) {
|
||||
fileSets.insert(fileSet);
|
||||
verifySet.erase(fileSet->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
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::FATAL_ERROR,
|
||||
cmStrCat("Property ", headerSetsProperty, " of target \"",
|
||||
this->GetName(),
|
||||
"\" contained the following header sets that are nonexistent "
|
||||
"or not ",
|
||||
isInterface ? "INTERFACE" : "PRIVATE", ":\n ",
|
||||
cmJoin(verifySet, "\n ")));
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& files : filesPerDir) {
|
||||
for (auto const& file : files.second) {
|
||||
cm::optional<std::string> filenameOpt =
|
||||
this->GenerateHeaderSetVerificationFile(
|
||||
*this->Makefile->GetOrCreateSource(file), files.first,
|
||||
verifyTargetName, defaultLanguage);
|
||||
if (!filenameOpt) {
|
||||
continue;
|
||||
}
|
||||
std::string filename = *filenameOpt;
|
||||
|
||||
if (fileCgesContextSensitive) {
|
||||
filename = cmStrCat("$<$<CONFIG:", config, ">:", filename, '>');
|
||||
}
|
||||
stubSources.emplace_back(std::move(filename));
|
||||
}
|
||||
}
|
||||
|
||||
if (!dirCgesContextSensitive && !fileCgesContextSensitive) {
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmGeneratorTarget::GenerateHeaderSetVerificationFile(
|
||||
cmSourceFile& source, std::string const& dir,
|
||||
std::string const& verifyTargetName,
|
||||
cm::optional<cm::optional<std::string>>& defaultLanguage) const
|
||||
{
|
||||
if (source.GetPropertyAsBool("SKIP_LINTING")) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
cm::optional<std::string> language =
|
||||
this->ResolveHeaderLanguage(source, defaultLanguage);
|
||||
if (!language) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
std::string headerFilename = dir;
|
||||
if (!headerFilename.empty()) {
|
||||
headerFilename += '/';
|
||||
}
|
||||
headerFilename += source.GetLocation().GetName();
|
||||
|
||||
return this->GenerateStubForLanguage(*language, headerFilename,
|
||||
verifyTargetName, source);
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmGeneratorTarget::ResolveHeaderLanguage(
|
||||
cmSourceFile& source,
|
||||
cm::optional<cm::optional<std::string>>& defaultLanguage) const
|
||||
{
|
||||
static std::array<cm::string_view, 4> const supportedLangs{ {
|
||||
"C",
|
||||
"CXX",
|
||||
"OBJC",
|
||||
"OBJCXX",
|
||||
} };
|
||||
auto isSupported = [](cm::string_view lang) -> bool {
|
||||
return std::find(supportedLangs.begin(), supportedLangs.end(), lang) !=
|
||||
supportedLangs.end();
|
||||
};
|
||||
|
||||
// If the source has an explicit language, validate and return it.
|
||||
std::string language = source.GetOrDetermineLanguage();
|
||||
if (!language.empty()) {
|
||||
if (!isSupported(language)) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
return cm::optional<std::string>(std::move(language));
|
||||
}
|
||||
|
||||
/*
|
||||
Compute and cache the default language for unlanguaged headers.
|
||||
The lattice join is run once per file set, not once per header.
|
||||
Lattice: OBJCXX
|
||||
/ \
|
||||
CXX OBJC
|
||||
\ /
|
||||
C
|
||||
*/
|
||||
if (!defaultLanguage) {
|
||||
std::set<std::string> langs;
|
||||
for (AllConfigSource const& tgtSource : this->GetAllConfigSources()) {
|
||||
std::string const& lang = tgtSource.Source->GetOrDetermineLanguage();
|
||||
if (isSupported(lang)) {
|
||||
langs.insert(lang);
|
||||
}
|
||||
}
|
||||
if (langs.empty()) {
|
||||
std::vector<std::string> languagesVector;
|
||||
this->GlobalGenerator->GetEnabledLanguages(languagesVector);
|
||||
for (std::string const& lang : languagesVector) {
|
||||
if (isSupported(lang)) {
|
||||
langs.insert(lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cm::optional<std::string> resolved;
|
||||
if (langs.count("OBJCXX") || (langs.count("CXX") && langs.count("OBJC"))) {
|
||||
resolved = "OBJCXX"; // promote
|
||||
} else if (langs.count("CXX")) {
|
||||
resolved = "CXX";
|
||||
} else if (langs.count("OBJC")) {
|
||||
resolved = "OBJC";
|
||||
} else if (langs.count("C")) {
|
||||
resolved = "C";
|
||||
}
|
||||
defaultLanguage = resolved;
|
||||
}
|
||||
|
||||
return *defaultLanguage;
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmGeneratorTarget::GenerateStubForLanguage(
|
||||
std::string const& language, std::string const& headerFilename,
|
||||
std::string const& verifyTargetName, cmSourceFile& source) const
|
||||
{
|
||||
static std::array<std::pair<cm::string_view, cm::string_view>, 4> const
|
||||
langToExt = { {
|
||||
{ "C", ".c" },
|
||||
{ "CXX", ".cxx" },
|
||||
{ "OBJC", ".m" },
|
||||
{ "OBJCXX", ".mm" },
|
||||
} };
|
||||
|
||||
// NOLINTNEXTLINE(readability-qualified-auto)
|
||||
auto const it =
|
||||
std::find_if(langToExt.begin(), langToExt.end(),
|
||||
[&](std::pair<cm::string_view, cm::string_view> const& p) {
|
||||
return p.first == language;
|
||||
});
|
||||
if (it == langToExt.end()) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
std::string filename =
|
||||
cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/',
|
||||
verifyTargetName, '/', headerFilename, it->second);
|
||||
|
||||
cmSourceFile* verificationSource =
|
||||
this->Makefile->GetOrCreateSource(filename);
|
||||
source.SetSpecialSourceType(
|
||||
cmSourceFile::SpecialSourceType::HeaderSetVerificationSource);
|
||||
verificationSource->SetProperty("LANGUAGE", language);
|
||||
|
||||
cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(filename));
|
||||
|
||||
cmGeneratedFileStream fout(filename);
|
||||
fout.SetCopyIfDifferent(true);
|
||||
// The IWYU "associated" pragma tells include-what-you-use to
|
||||
// consider the headerFile as part of the entire language
|
||||
// unit within include-what-you-use and as a result allows
|
||||
// one to get IWYU advice for headers.
|
||||
// Also suppress clang-tidy include checks in generated code.
|
||||
fout
|
||||
<< "/* NOLINTNEXTLINE(misc-header-include-cycle,misc-include-cleaner) */\n"
|
||||
<< "#include <" << headerFilename << "> /* IWYU pragma: associated */\n";
|
||||
fout.close();
|
||||
|
||||
return cm::optional<std::string>(std::move(filename));
|
||||
}
|
||||
Reference in New Issue
Block a user