/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetLinkLibraryType.h" #include "cmTargetTypes.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() != cm::TargetType::STATIC_LIBRARY && this->GetType() != cm::TargetType::SHARED_LIBRARY && (this->GetType() != cm::TargetType::MODULE_LIBRARY || isInterface) && this->GetType() != cm::TargetType::UNKNOWN_LIBRARY && this->GetType() != cm::TargetType::OBJECT_LIBRARY && this->GetType() != cm::TargetType::INTERFACE_LIBRARY && this->GetType() != cm::TargetType::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 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, { cm::TargetDomain::NATIVE }); auto fileSetEntries = isInterface ? this->GetInterfaceFileSets(cm::FileSetMetadata::HEADERS) : this->GetFileSets(cm::FileSetMetadata::HEADERS); std::set 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() == cm::TargetType::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> defaultLanguage; // First, collect all verification stubs before creating the target, // so we know whether to create an OBJECT library or not. std::vector stubSources; for (auto const* fileSet : fileSets) { auto const& dirCges = fileSet->CompileDirectoryEntries(); auto const& fileCges = fileSet->CompileFileEntries(); static auto const contextSensitive = [](std::unique_ptr const& cge) { return cge->GetHadContextSensitiveCondition(); }; bool dirCgesContextSensitive = false; bool fileCgesContextSensitive = false; std::vector dirs; std::map> 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 filenameOpt = this->GenerateHeaderSetVerificationFile( *this->Makefile->GetOrCreateSource(file), files.first, verifyTargetName, defaultLanguage); if (!filenameOpt) { continue; } std::string filename = *filenameOpt; if (fileCgesContextSensitive) { filename = cmStrCat("$<$:", 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, cm::TargetType::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 _... 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 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(verifyTarget, this->LocalGenerator)); } return true; } cm::optional cmGeneratorTarget::GenerateHeaderSetVerificationFile( cmSourceFile& source, std::string const& dir, std::string const& verifyTargetName, cm::optional>& defaultLanguage) const { if (source.GetPropertyAsBool("SKIP_LINTING")) { return cm::nullopt; } cm::optional 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 cmGeneratorTarget::ResolveHeaderLanguage( cmSourceFile& source, cm::optional>& defaultLanguage) const { static std::array 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::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 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 languagesVector; this->GlobalGenerator->GetEnabledLanguages(languagesVector); for (std::string const& lang : languagesVector) { if (isSupported(lang)) { langs.insert(lang); } } } cm::optional 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 cmGeneratorTarget::GenerateStubForLanguage( std::string const& language, std::string const& headerFilename, std::string const& verifyTargetName, cmSourceFile& source) const { static std::array, 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 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::move(filename)); }