From cedfaa010fc7c084c676be079c9845c84ac302ce Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 6 May 2024 18:06:11 -0400 Subject: [PATCH] cmGeneratorTarget: add a method to build classified command lines This is essentially an extraction of the `Ninja` generator's command line building logic. Porting generators to reuse this construction is a task for the future. --- Source/cmGeneratorTarget.cxx | 420 +++++++++++++++++++++++++++++++++++ Source/cmGeneratorTarget.h | 59 +++++ 2 files changed, 479 insertions(+) diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 312f570153..293a4b7111 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -37,6 +37,7 @@ #include "cmMessageType.h" #include "cmOutputConverter.h" #include "cmPropertyMap.h" +#include "cmRulePlaceholderExpander.h" #include "cmSourceFile.h" #include "cmSourceFileLocation.h" #include "cmSourceFileLocationKind.h" @@ -2024,6 +2025,425 @@ std::vector cmGeneratorTarget::GetAppleArchs( return std::move(archList.data()); } +namespace { + +bool IsSupportedClassifiedFlagsLanguage(std::string const& lang) +{ + return lang == "CXX"_s; +} + +bool CanUseCompilerLauncher(std::string const& lang) +{ + // Also found in `cmCommonTargetGenerator::GetCompilerLauncher`. + return lang == "C"_s || lang == "CXX"_s || lang == "Fortran"_s || + lang == "CUDA"_s || lang == "HIP"_s || lang == "ISPC"_s || + lang == "OBJC"_s || lang == "OBJCXX"_s; +} + +// FIXME: return a vector of `cm::string_view` instead to avoid lots of tiny +// allocations. +std::vector SplitFlags(std::string const& flags) +{ + std::vector options; + +#ifdef _WIN32 + cmSystemTools::ParseWindowsCommandLine(flags.c_str(), options); +#else + cmSystemTools::ParseUnixCommandLine(flags.c_str(), options); +#endif + + return options; +} + +} + +cmGeneratorTarget::ClassifiedFlags +cmGeneratorTarget::GetClassifiedFlagsForSource(cmSourceFile const* sf, + std::string const& config) +{ + auto& sourceFlagsCache = this->Configs[config].SourceFlags; + auto cacheEntry = sourceFlagsCache.lower_bound(sf); + if (cacheEntry != sourceFlagsCache.end() && cacheEntry->first == sf) { + return cacheEntry->second; + } + + ClassifiedFlags flags; + std::string const& lang = sf->GetLanguage(); + + if (!IsSupportedClassifiedFlagsLanguage(lang)) { + return flags; + } + + auto* const lg = this->GetLocalGenerator(); + auto const* const mf = this->Makefile; + + // Compute the compiler launcher flags. + if (CanUseCompilerLauncher(lang)) { + // Compiler launchers are all execution flags and should not be relevant to + // the actual compilation. + FlagClassification cls = FlagClassification::ExecutionFlag; + FlagKind kind = FlagKind::NotAFlag; + + std::string const clauncher_prop = cmStrCat(lang, "_COMPILER_LAUNCHER"); + cmValue clauncher = this->GetProperty(clauncher_prop); + std::string const evaluatedClauncher = cmGeneratorExpression::Evaluate( + *clauncher, lg, config, this, nullptr, this, lang); + + for (auto const& flag : SplitFlags(evaluatedClauncher)) { + flags.emplace_back(cls, kind, flag); + } + } + + ClassifiedFlags define_flags; + ClassifiedFlags include_flags; + ClassifiedFlags compile_flags; + + SourceVariables sfVars = this->GetSourceVariables(sf, config); + + // Compute language flags. + { + FlagClassification cls = FlagClassification::BaselineFlag; + FlagKind kind = FlagKind::Compile; + + std::string mfFlags; + // Explicitly add the explicit language flag before any other flag + // so user flags can override it. + this->AddExplicitLanguageFlags(mfFlags, *sf); + + for (auto const& flag : SplitFlags(mfFlags)) { + flags.emplace_back(cls, kind, flag); + } + } + + std::unordered_map pchSources; + std::string filterArch; + + { + std::vector pchArchs = this->GetPchArchs(config, lang); + + for (const std::string& arch : pchArchs) { + const std::string pchSource = this->GetPchSource(config, lang, arch); + if (pchSource == sf->GetFullPath()) { + filterArch = arch; + } + if (!pchSource.empty()) { + pchSources.insert(std::make_pair(pchSource, arch)); + } + } + } + + // Compute target-wide flags. + { + FlagClassification cls = FlagClassification::BaselineFlag; + + // Compile flags + { + FlagKind kind = FlagKind::Compile; + std::string targetFlags; + + lg->GetTargetCompileFlags(this, config, lang, targetFlags, filterArch); + + for (auto&& flag : SplitFlags(targetFlags)) { + compile_flags.emplace_back(cls, kind, std::move(flag)); + } + } + + // Define flags + { + FlagKind kind = FlagKind::Definition; + std::set defines; + + lg->GetTargetDefines(this, config, lang, defines); + + std::string defineFlags; + lg->JoinDefines(defines, defineFlags, lang); + + for (auto&& flag : SplitFlags(defineFlags)) { + define_flags.emplace_back(cls, kind, std::move(flag)); + } + } + + // Include flags + { + FlagKind kind = FlagKind::Include; + std::vector includes; + + lg->GetIncludeDirectories(includes, this, lang, config); + auto includeFlags = + lg->GetIncludeFlags(includes, this, lang, config, false); + + for (auto&& flag : SplitFlags(includeFlags)) { + include_flags.emplace_back(cls, kind, std::move(flag)); + } + } + } + + const std::string COMPILE_FLAGS("COMPILE_FLAGS"); + const std::string COMPILE_OPTIONS("COMPILE_OPTIONS"); + + cmGeneratorExpressionInterpreter genexInterpreter(lg, config, this, lang); + + // Source-specific flags. + { + FlagClassification cls = FlagClassification::PrivateFlag; + FlagKind kind = FlagKind::Compile; + + std::string sourceFlags; + + if (cmValue cflags = sf->GetProperty(COMPILE_FLAGS)) { + lg->AppendFlags(sourceFlags, + genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS)); + } + + if (cmValue coptions = sf->GetProperty(COMPILE_OPTIONS)) { + lg->AppendCompileOptions( + sourceFlags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS)); + } + + for (auto&& flag : SplitFlags(sourceFlags)) { + compile_flags.emplace_back(cls, kind, std::move(flag)); + } + + // Dependency tracking flags. + { + if (!sfVars.DependencyFlags.empty()) { + cmRulePlaceholderExpander::RuleVariables vars; + auto rulePlaceholderExpander = lg->CreateRulePlaceholderExpander(); + + vars.DependencyFile = sfVars.DependencyFile.c_str(); + vars.DependencyTarget = sfVars.DependencyTarget.c_str(); + + std::string depfileFlags = sfVars.DependencyFlags; + rulePlaceholderExpander->ExpandRuleVariables(lg, depfileFlags, vars); + for (auto&& flag : SplitFlags(depfileFlags)) { + compile_flags.emplace_back(FlagClassification::LocationFlag, + FlagKind::BuildSystem, std::move(flag)); + } + } + } + } + + // Precompiled headers. + { + FlagClassification cls = FlagClassification::PrivateFlag; + FlagKind kind = FlagKind::Compile; + + std::string pchFlags; + + // Add precompile headers compile options. + if (!sf->GetProperty("SKIP_PRECOMPILE_HEADERS")) { + if (!pchSources.empty()) { + std::string pchOptions; + auto pchIt = pchSources.find(sf->GetFullPath()); + if (pchIt != pchSources.end()) { + pchOptions = + this->GetPchCreateCompileOptions(config, lang, pchIt->second); + } else { + pchOptions = this->GetPchUseCompileOptions(config, lang); + } + + this->LocalGenerator->AppendCompileOptions( + pchFlags, genexInterpreter.Evaluate(pchOptions, COMPILE_OPTIONS)); + } + } + + for (auto&& flag : SplitFlags(pchFlags)) { + compile_flags.emplace_back(cls, kind, std::move(flag)); + } + } + + // C++ module flags. + if (lang == "CXX"_s) { + FlagClassification cls = FlagClassification::LocationFlag; + FlagKind kind = FlagKind::BuildSystem; + + std::string bmiFlags; + + auto const* fs = this->GetFileSetForSource(config, sf); + if (fs && fs->GetType() == "CXX_MODULES"_s) { + if (lang != "CXX"_s) { + mf->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat( + "Target \"", this->Target->GetName(), "\" contains the source\n ", + sf->GetFullPath(), "\nin a file set of type \"", fs->GetType(), + R"(" but the source is not classified as a "CXX" source.)")); + } + + if (!this->Target->IsNormal()) { + auto flag = mf->GetSafeDefinition("CMAKE_CXX_MODULE_BMI_ONLY_FLAG"); + cmRulePlaceholderExpander::RuleVariables compileObjectVars; + compileObjectVars.Object = sfVars.ObjectFileDir.c_str(); + auto rulePlaceholderExpander = lg->CreateRulePlaceholderExpander(); + rulePlaceholderExpander->ExpandRuleVariables(lg, flag, + compileObjectVars); + lg->AppendCompileOptions(bmiFlags, flag); + } + } + + for (auto&& flag : SplitFlags(bmiFlags)) { + compile_flags.emplace_back(cls, kind, std::move(flag)); + } + } + + cmRulePlaceholderExpander::RuleVariables vars; + vars.CMTargetName = this->GetName().c_str(); + vars.CMTargetType = cmState::GetTargetTypeName(this->GetType()).c_str(); + vars.Language = lang.c_str(); + auto const sfPath = this->LocalGenerator->ConvertToOutputFormat( + sf->GetFullPath(), cmOutputConverter::SHELL); + + // Compute the base compiler command line. We'll find placeholders and + // replace them with arguments later in this function. + { + FlagClassification cls = FlagClassification::ExecutionFlag; + FlagKind kind = FlagKind::NotAFlag; + + std::string const cmdVar = cmStrCat("CMAKE_", lang, "_COMPILE_OBJECT"); + std::string const& compileCmd = mf->GetRequiredDefinition(cmdVar); + cmList compileCmds(compileCmd); // FIXME: which command to use? + std::string& cmd = compileCmds[0]; + auto rulePlaceholderExpander = lg->CreateRulePlaceholderExpander(); + + static std::string const PlaceholderDefines = "__CMAKE_DEFINES"; + static std::string const PlaceholderIncludes = "__CMAKE_INCLUDES"; + static std::string const PlaceholderFlags = "__CMAKE_FLAGS"; + static std::string const PlaceholderSource = "__CMAKE_SOURCE"; + + vars.Defines = PlaceholderDefines.c_str(); + vars.Includes = PlaceholderIncludes.c_str(); + vars.TargetPDB = sfVars.TargetPDB.c_str(); + vars.TargetCompilePDB = sfVars.TargetCompilePDB.c_str(); + vars.Object = sfVars.ObjectFileDir.c_str(); + vars.ObjectDir = sfVars.ObjectDir.c_str(); + vars.ObjectFileDir = sfVars.ObjectFileDir.c_str(); + vars.Flags = PlaceholderFlags.c_str(); + vars.DependencyFile = sfVars.DependencyFile.c_str(); + vars.DependencyTarget = sfVars.DependencyTarget.c_str(); + vars.Source = PlaceholderSource.c_str(); + + rulePlaceholderExpander->ExpandRuleVariables(lg, cmd, vars); + for (auto&& flag : SplitFlags(cmd)) { + if (flag == PlaceholderDefines) { + flags.insert(flags.end(), define_flags.begin(), define_flags.end()); + } else if (flag == PlaceholderIncludes) { + flags.insert(flags.end(), include_flags.begin(), include_flags.end()); + } else if (flag == PlaceholderFlags) { + flags.insert(flags.end(), compile_flags.begin(), compile_flags.end()); + } else if (flag == PlaceholderSource) { + flags.emplace_back(FlagClassification::LocationFlag, + FlagKind::BuildSystem, sfPath); + } else { + flags.emplace_back(cls, kind, std::move(flag)); + // All remaining flags here are build system flags. + kind = FlagKind::BuildSystem; + } + } + } + + cacheEntry = sourceFlagsCache.emplace_hint(cacheEntry, sf, std::move(flags)); + return cacheEntry->second; +} + +cmGeneratorTarget::SourceVariables cmGeneratorTarget::GetSourceVariables( + cmSourceFile const* sf, std::string const& config) +{ + SourceVariables vars; + auto const language = sf->GetLanguage(); + auto const targetType = this->GetType(); + auto const* const lg = this->GetLocalGenerator(); + auto const* const gg = this->GetGlobalGenerator(); + auto const* const mf = this->Makefile; + + // PDB settings. + { + if (mf->GetDefinition("MSVC_C_ARCHITECTURE_ID") || + mf->GetDefinition("MSVC_CXX_ARCHITECTURE_ID") || + mf->GetDefinition("MSVC_CUDA_ARCHITECTURE_ID")) { + std::string pdbPath; + std::string compilePdbPath; + if (targetType <= cmStateEnums::OBJECT_LIBRARY) { + compilePdbPath = this->GetCompilePDBPath(config); + if (compilePdbPath.empty()) { + // Match VS default: `$(IntDir)vc$(PlatformToolsetVersion).pdb`. + // A trailing slash tells the toolchain to add its default file name. + compilePdbPath = this->GetSupportDirectory(); + if (gg->IsMultiConfig()) { + compilePdbPath = cmStrCat(compilePdbPath, '/', config); + } + compilePdbPath += '/'; + if (targetType == cmStateEnums::STATIC_LIBRARY) { + // Match VS default for static libs: `$(IntDir)$(ProjectName).pdb`. + compilePdbPath = cmStrCat(compilePdbPath, this->GetName(), ".pdb"); + } + } + } + + if (targetType == cmStateEnums::EXECUTABLE || + targetType == cmStateEnums::STATIC_LIBRARY || + targetType == cmStateEnums::SHARED_LIBRARY || + targetType == cmStateEnums::MODULE_LIBRARY) { + pdbPath = cmStrCat(this->GetPDBDirectory(config), '/', + this->GetPDBName(config)); + } + + vars.TargetPDB = lg->ConvertToOutputFormat( + gg->ConvertToOutputPath(std::move(pdbPath)), cmOutputConverter::SHELL); + vars.TargetCompilePDB = lg->ConvertToOutputFormat( + gg->ConvertToOutputPath(std::move(compilePdbPath)), + cmOutputConverter::SHELL); + } + } + + // Object settings. + { + std::string const objectDir = gg->ConvertToOutputPath( + cmStrCat(this->GetSupportDirectory(), gg->GetConfigDirectory(config))); + std::string const objectFileName = this->GetObjectName(sf); + std::string const objectFilePath = + cmStrCat(objectDir, '/', objectFileName); + + vars.ObjectDir = + lg->ConvertToOutputFormat(objectDir, cmOutputConverter::SHELL); + vars.ObjectFileDir = + lg->ConvertToOutputFormat(objectFilePath, cmOutputConverter::SHELL); + + // Dependency settings. + { + std::string const depfileFormatName = + cmStrCat("CMAKE_", language, "_DEPFILE_FORMAT"); + std::string const depfileFormat = + mf->GetSafeDefinition(depfileFormatName); + if (depfileFormat != "msvc"_s) { + std::string const flagsName = + cmStrCat("CMAKE_DEPFILE_FLAGS_", language); + std::string const depfileFlags = mf->GetSafeDefinition(flagsName); + if (!depfileFlags.empty()) { + bool replaceExt = false; + if (!language.empty()) { + std::string const repVar = + cmStrCat("CMAKE_", language, "_DEPFILE_EXTENSION_REPLACE"); + replaceExt = mf->IsOn(repVar); + } + std::string const depfilePath = cmStrCat( + objectDir, '/', + replaceExt + ? cmSystemTools::GetFilenameWithoutLastExtension(objectFileName) + : objectFileName, + ".d"); + + vars.DependencyFlags = depfileFlags; + vars.DependencyTarget = vars.ObjectFileDir; + vars.DependencyFile = + lg->ConvertToOutputFormat(depfilePath, cmOutputConverter::SHELL); + } + } + } + } + + return vars; +} + void cmGeneratorTarget::AddExplicitLanguageFlags(std::string& flags, cmSourceFile const& sf) const { diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index dcd4955959..210f0abb72 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -517,6 +517,64 @@ public: std::vector GetAppleArchs(std::string const& config, cm::optional lang) const; + // The classification of the flag. + enum class FlagClassification + { + // The flag is for the execution of the tool (e.g., the compiler itself, + // any launchers, etc.). + ExecutionFlag, + // The flag is "baseline" and should be apply to TUs which may interact + // with this compilation (e.g., imported modules). + BaselineFlag, + // The flag is "private" and doesn't need to apply to interacting TUs. + PrivateFlag, + // Flags for the TU itself (e.g., output paths, dependency scanning, etc.). + LocationFlag, + }; + enum class FlagKind + { + // Not a flag (executable or other entries). + NotAFlag, + // Flags for support of the build system. + BuildSystem, + // A compilation flag. + Compile, + // An include flag. + Include, + // A compile definition. + Definition, + }; + struct ClassifiedFlag + { + ClassifiedFlag(FlagClassification cls, FlagKind kind, std::string flag) + : Classification(cls) + , Kind(kind) + , Flag(std::move(flag)) + { + } + + FlagClassification Classification; + FlagKind Kind; + std::string Flag; + }; + using ClassifiedFlags = std::vector; + ClassifiedFlags GetClassifiedFlagsForSource(cmSourceFile const* sf, + std::string const& config); + struct SourceVariables + { + std::string TargetPDB; + std::string TargetCompilePDB; + std::string ObjectDir; + std::string ObjectFileDir; + std::string DependencyFile; + std::string DependencyTarget; + + // Dependency flags (if used) + std::string DependencyFlags; + }; + SourceVariables GetSourceVariables(cmSourceFile const* sf, + std::string const& config); + void AddExplicitLanguageFlags(std::string& flags, cmSourceFile const& sf) const; @@ -1452,6 +1510,7 @@ private: std::map FileSetCache; std::map> SyntheticDeps; + std::map SourceFlags; }; mutable std::map Configs; };