From f974ef0f751fa33d532999e3d86eb55028f8181c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Germain?= Date: Thu, 5 Mar 2026 15:56:47 -0800 Subject: [PATCH] cmGeneratorTarget: Refactor header set verification for C and CXX Split GenerateHeaderSetVerificationFile into three focused helpers: - ResolveHeaderLanguage: determines the language for a header; returns early for headers with an explicit LANGUAGE property (validated against the supported set), and otherwise computes and caches the target's default language using a lattice join (CXX > C) over target sources and globally enabled languages. - GenerateStubForLanguage: maps a resolved language to a stub file extension (.c or .cxx) and creates the verification source. - GenerateHeaderSetVerificationFile: orchestrates the above, moving the SKIP_LINTING guard here and switching the return type to cm::optional so callers can distinguish "no file" from an empty path. No behavior change; only C and CXX are supported at this point. --- Source/cmGeneratorTarget.cxx | 152 +++++++++++++++++++++++------------ Source/cmGeneratorTarget.h | 12 ++- 2 files changed, 111 insertions(+), 53 deletions(-) diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 638ed31a30..2ed3cabe59 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -5839,7 +5839,7 @@ bool cmGeneratorTarget::AddHeaderSetVerification() return false; } - cm::optional> languages; + cm::optional> defaultLanguage; for (auto const* fileSet : fileSets) { auto const& dirCges = fileSet->CompileDirectoryEntries(); auto const& fileCges = fileSet->CompileFileEntries(); @@ -5875,12 +5875,14 @@ bool cmGeneratorTarget::AddHeaderSetVerification() for (auto const& files : filesPerDir) { for (auto const& file : files.second) { - std::string filename = this->GenerateHeaderSetVerificationFile( - *this->Makefile->GetOrCreateSource(file), files.first, - verifyTargetName, languages); - if (filename.empty()) { + cm::optional filenameOpt = + this->GenerateHeaderSetVerificationFile( + *this->Makefile->GetOrCreateSource(file), files.first, + verifyTargetName, defaultLanguage); + if (!filenameOpt) { continue; } + std::string filename = *filenameOpt; if (!verifyTarget) { { @@ -5987,54 +5989,19 @@ bool cmGeneratorTarget::AddHeaderSetVerification() return true; } -std::string cmGeneratorTarget::GenerateHeaderSetVerificationFile( +cm::optional cmGeneratorTarget::GenerateHeaderSetVerificationFile( cmSourceFile& source, std::string const& dir, std::string const& verifyTargetName, - cm::optional>& languages) const + cm::optional>& defaultLanguage) const { - std::string extension; - std::string language = source.GetOrDetermineLanguage(); - if (source.GetPropertyAsBool("SKIP_LINTING")) { - return std::string{}; + return cm::nullopt; } - if (language.empty()) { - if (!languages) { - languages.emplace(); - for (auto const& tgtSource : this->GetAllConfigSources()) { - auto const& tgtSourceLanguage = - tgtSource.Source->GetOrDetermineLanguage(); - if (tgtSourceLanguage == "CXX") { - languages->insert("CXX"); - break; // C++ overrides everything else, so we don't need to keep - // checking. - } - if (tgtSourceLanguage == "C") { - languages->insert("C"); - } - } - - if (languages->empty()) { - std::vector languagesVector; - this->GlobalGenerator->GetEnabledLanguages(languagesVector); - languages->insert(languagesVector.begin(), languagesVector.end()); - } - } - - if (languages->count("CXX")) { - language = "CXX"; - } else if (languages->count("C")) { - language = "C"; - } - } - - if (language == "C") { - extension = ".c"; - } else if (language == "CXX") { - extension = ".cxx"; - } else { - return ""; + cm::optional language = + this->ResolveHeaderLanguage(source, defaultLanguage); + if (!language) { + return cm::nullopt; } std::string headerFilename = dir; @@ -6043,10 +6010,93 @@ std::string cmGeneratorTarget::GenerateHeaderSetVerificationFile( } headerFilename += source.GetLocation().GetName(); - auto filename = + return this->GenerateStubForLanguage(*language, headerFilename, + verifyTargetName, source); +} + +cm::optional cmGeneratorTarget::ResolveHeaderLanguage( + cmSourceFile& source, + cm::optional>& defaultLanguage) const +{ + static std::array const supportedLangs{ { + "C", + "CXX", + } }; + 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: CXX > 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("CXX")) { + resolved = "CXX"; + } 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, 2> const + langToExt = { { + { "C", ".c" }, + { "CXX", ".cxx" }, + } }; + + // 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, extension); - auto* verificationSource = this->Makefile->GetOrCreateSource(filename); + verifyTargetName, '/', headerFilename, it->second); + + cmSourceFile* verificationSource = + this->Makefile->GetOrCreateSource(filename); source.SetSpecialSourceType( cmSourceFile::SpecialSourceType::HeaderSetVerificationSource); verificationSource->SetProperty("LANGUAGE", language); @@ -6065,7 +6115,7 @@ std::string cmGeneratorTarget::GenerateHeaderSetVerificationFile( << "#include <" << headerFilename << "> /* IWYU pragma: associated */\n"; fout.close(); - return filename; + return cm::optional(std::move(filename)); } std::string cmGeneratorTarget::GetImportedXcFrameworkPath( diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index 818d92393e..fadd36afe6 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -1110,10 +1110,18 @@ public: void AddSystemIncludeDirectory(std::string const& inc, std::string const& lang); bool AddHeaderSetVerification(); - std::string GenerateHeaderSetVerificationFile( + cm::optional GenerateHeaderSetVerificationFile( cmSourceFile& source, std::string const& dir, std::string const& verifyTargetName, - cm::optional>& languages) const; + cm::optional>& defaultLanguage) const; + + cm::optional ResolveHeaderLanguage( + cmSourceFile& source, + cm::optional>& defaultLanguage) const; + + cm::optional GenerateStubForLanguage( + std::string const& language, std::string const& headerFilename, + std::string const& verifyTargetName, cmSourceFile& source) const; std::string GetImportedXcFrameworkPath(std::string const& config) const;