blob: a4420f1b0e3f162cff85c139aba718fa43c3b6ee [file] [log] [blame] [edit]
/* 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));
}