mirror of
https://github.com/Kitware/CMake.git
synced 2026-06-30 19:57:41 +00:00
Add and use a couple helper functions for issuing policy warnings. This improves consistency and allows some simplification of many call sites. (One or two instances in particular are greatly simplified.)
428 lines
15 KiB
C++
428 lines
15 KiB
C++
/* 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 <cmext/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())) {
|
|
verifySet.erase(fileSet->GetName());
|
|
if (fileSet->GetProperty("SKIP_LINTING").IsOn()) {
|
|
continue;
|
|
}
|
|
fileSets.insert(fileSet);
|
|
}
|
|
}
|
|
|
|
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->IssuePolicyWarning(
|
|
cmPolicies::CMP0209, {},
|
|
cmStrCat("Executable target \""_s, this->GetName(),
|
|
"\" has interface header file sets, but it does not "
|
|
"enable exports. Those headers would be verified under "
|
|
"CMP0209 NEW behavior.\n"_s));
|
|
}
|
|
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));
|
|
}
|