| /* 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 "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<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, { cm::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() == 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<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, | |
| ">:", std::move(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 (cmValue mode = this->Target->GetProperty("SYCL_EXTENSION_MODE")) { | |
| verifyTarget->SetProperty("SYCL_EXTENSION_MODE", mode); | |
| } | |
| 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, 22> 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", | |
| "SYCL_CXX_STANDARD", | |
| "SYCL_CXX_STANDARD_REQUIRED", | |
| "SYCL_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, 5> const supportedLangs{ { | |
| "C", | |
| "CXX", | |
| "OBJC", | |
| "OBJCXX", | |
| "SYCL", | |
| } }; | |
| 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 (std::string const& lang : this->GetAllConfigCompileLanguages()) { | |
| 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("SYCL")) && langs.count("OBJC"))) { | |
| resolved = "OBJCXX"; // promote | |
| } else if (langs.count("SYCL")) { | |
| resolved = "SYCL"; | |
| } 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>, 5> const | |
| langToExt = { { | |
| { "C", ".c" }, | |
| { "CXX", ".cxx" }, | |
| { "OBJC", ".m" }, | |
| { "OBJCXX", ".mm" }, | |
| { "SYCL", ".sycl" }, | |
| } }; | |
| // 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)); | |
| } |