/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmNinjaTargetGenerator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmBuildDatabase.h" #include "cmComputeLinkInformation.h" #include "cmCustomCommandGenerator.h" #include "cmDyndepCollation.h" #include "cmFileSet.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" #include "cmGlobalCommonGenerator.h" #include "cmGlobalNinjaGenerator.h" #include "cmList.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmLocalNinjaGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmNinjaNormalTargetGenerator.h" #include "cmNinjaUtilityTargetGenerator.h" #include "cmOutputConverter.h" #include "cmPolicies.h" #include "cmRange.h" #include "cmRulePlaceholderExpander.h" #include "cmSourceFile.h" #include "cmState.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetDepend.h" #include "cmValue.h" #include "cmake.h" class cmCustomCommand; std::unique_ptr cmNinjaTargetGenerator::New( cmGeneratorTarget* target) { switch (target->GetType()) { case cmStateEnums::EXECUTABLE: case cmStateEnums::SHARED_LIBRARY: case cmStateEnums::STATIC_LIBRARY: case cmStateEnums::MODULE_LIBRARY: case cmStateEnums::OBJECT_LIBRARY: return cm::make_unique(target); case cmStateEnums::INTERFACE_LIBRARY: if (target->HaveCxx20ModuleSources()) { return cm::make_unique(target); } CM_FALLTHROUGH; case cmStateEnums::UTILITY: case cmStateEnums::GLOBAL_TARGET: return cm::make_unique(target); default: return std::unique_ptr(); } } cmNinjaTargetGenerator::cmNinjaTargetGenerator(cmGeneratorTarget* target) : cmCommonTargetGenerator(target) , OSXBundleGenerator(nullptr) , LocalGenerator( static_cast(target->GetLocalGenerator())) { for (auto const& fileConfig : this->LocalGenerator->GetConfigNames()) { this->Configs[fileConfig].MacOSXContentGenerator = cm::make_unique(this, fileConfig); } } cmNinjaTargetGenerator::~cmNinjaTargetGenerator() = default; cmGeneratedFileStream& cmNinjaTargetGenerator::GetImplFileStream( const std::string& config) const { return *this->GetGlobalGenerator()->GetImplFileStream(config); } cmGeneratedFileStream& cmNinjaTargetGenerator::GetCommonFileStream() const { return *this->GetGlobalGenerator()->GetCommonFileStream(); } cmGeneratedFileStream& cmNinjaTargetGenerator::GetRulesFileStream() const { return *this->GetGlobalGenerator()->GetRulesFileStream(); } cmGlobalNinjaGenerator* cmNinjaTargetGenerator::GetGlobalGenerator() const { return this->LocalGenerator->GetGlobalNinjaGenerator(); } std::string cmNinjaTargetGenerator::LanguageCompilerRule( const std::string& lang, const std::string& config, WithScanning withScanning) const { return cmStrCat( lang, "_COMPILER__", cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()), withScanning == WithScanning::Yes ? "_scanned_" : "_unscanned_", config); } std::string cmNinjaTargetGenerator::LanguagePreprocessAndScanRule( std::string const& lang, const std::string& config) const { return cmStrCat( lang, "_PREPROCESS_SCAN__", cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()), '_', config); } std::string cmNinjaTargetGenerator::LanguageScanRule( std::string const& lang, const std::string& config) const { return cmStrCat( lang, "_SCAN__", cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()), '_', config); } bool cmNinjaTargetGenerator::NeedExplicitPreprocessing( std::string const& lang) const { return lang == "Fortran" || lang == "Swift"; } bool cmNinjaTargetGenerator::CompileWithDefines(std::string const& lang) const { return this->Makefile->IsOn( cmStrCat("CMAKE_", lang, "_COMPILE_WITH_DEFINES")); } std::string cmNinjaTargetGenerator::LanguageDyndepRule( const std::string& lang, const std::string& config) const { return cmStrCat( lang, "_DYNDEP__", cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()), '_', config); } std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget( const std::string& config) { return this->GetGlobalGenerator()->OrderDependsTargetForTarget( this->GeneratorTarget, config); } std::string cmNinjaTargetGenerator::OrderDependsTargetForTargetPrivate( const std::string& config) { return this->GetGlobalGenerator()->OrderDependsTargetForTargetPrivate( this->GeneratorTarget, config); } // TODO: Most of the code is picked up from // void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink), // void cmMakefileTargetGenerator::WriteTargetLanguageFlags() // Refactor it. std::string cmNinjaTargetGenerator::ComputeFlagsForObject( cmSourceFile const* source, const std::string& language, const std::string& config, const std::string& objectFileName) { std::unordered_map pchSources; std::vector pchArchs = this->GeneratorTarget->GetPchArchs(config, language); std::string filterArch; for (const std::string& arch : pchArchs) { const std::string pchSource = this->GeneratorTarget->GetPchSource(config, language, arch); if (pchSource == source->GetFullPath()) { filterArch = arch; } if (!pchSource.empty()) { pchSources.insert(std::make_pair(pchSource, arch)); } } std::string flags; // Explicitly add the explicit language flag before any other flag // so user flags can override it. this->GeneratorTarget->AddExplicitLanguageFlags(flags, *source); if (!flags.empty()) { flags += " "; } flags += this->GetFlags(language, config, filterArch); // Add Fortran format flags. if (language == "Fortran") { this->AppendFortranFormatFlags(flags, *source); this->AppendFortranPreprocessFlags(flags, *source, PreprocessFlagsRequired::NO); } // Add source file specific flags. cmGeneratorExpressionInterpreter genexInterpreter( this->LocalGenerator, config, this->GeneratorTarget, language); const std::string COMPILE_FLAGS("COMPILE_FLAGS"); if (cmValue cflags = source->GetProperty(COMPILE_FLAGS)) { this->LocalGenerator->AppendFlags( flags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS)); } const std::string COMPILE_OPTIONS("COMPILE_OPTIONS"); if (cmValue coptions = source->GetProperty(COMPILE_OPTIONS)) { this->LocalGenerator->AppendCompileOptions( flags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS)); } // Add precompile headers compile options. if (!pchSources.empty() && !source->GetProperty("SKIP_PRECOMPILE_HEADERS")) { std::string pchOptions; auto pchIt = pchSources.find(source->GetFullPath()); if (pchIt != pchSources.end()) { pchOptions = this->GeneratorTarget->GetPchCreateCompileOptions( config, language, pchIt->second); } else { pchOptions = this->GeneratorTarget->GetPchUseCompileOptions(config, language); } this->LocalGenerator->AppendCompileOptions( flags, genexInterpreter.Evaluate(pchOptions, COMPILE_OPTIONS)); } auto const* fs = this->GeneratorTarget->GetFileSetForSource(config, source); if (fs && fs->GetType() == "CXX_MODULES"_s) { if (source->GetLanguage() != "CXX"_s) { this->GetMakefile()->IssueMessage( MessageType::FATAL_ERROR, cmStrCat("Target \"", this->GeneratorTarget->Target->GetName(), "\" contains the source\n ", source->GetFullPath(), "\nin a file set of type \"", fs->GetType(), R"(" but the source is not classified as a "CXX" source.)")); } if (!this->GeneratorTarget->Target->IsNormal()) { auto flag = this->GetMakefile()->GetSafeDefinition( "CMAKE_CXX_MODULE_BMI_ONLY_FLAG"); cmRulePlaceholderExpander::RuleVariables compileObjectVars; compileObjectVars.Object = objectFileName.c_str(); auto rulePlaceholderExpander = this->GetLocalGenerator()->CreateRulePlaceholderExpander(); rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), flag, compileObjectVars); this->LocalGenerator->AppendCompileOptions(flags, flag); } } return flags; } void cmNinjaTargetGenerator::AddIncludeFlags(std::string& languageFlags, std::string const& language, const std::string& config) { std::vector includes; this->LocalGenerator->GetIncludeDirectories(includes, this->GeneratorTarget, language, config); // Add include directory flags. std::string includeFlags = this->LocalGenerator->GetIncludeFlags( includes, this->GeneratorTarget, language, config, false); if (this->GetGlobalGenerator()->IsGCCOnWindows()) { std::replace(includeFlags.begin(), includeFlags.end(), '\\', '/'); } this->LocalGenerator->AppendFlags(languageFlags, includeFlags); } // TODO: Refactor with // void cmMakefileTargetGenerator::WriteTargetLanguageFlags(). std::string cmNinjaTargetGenerator::ComputeDefines(cmSourceFile const* source, const std::string& language, const std::string& config) { std::set defines; cmGeneratorExpressionInterpreter genexInterpreter( this->LocalGenerator, config, this->GeneratorTarget, language); // Seriously?? if (this->GetGlobalGenerator()->IsMultiConfig()) { defines.insert(cmStrCat("CMAKE_INTDIR=\"", config, '"')); } const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS"); if (cmValue compile_defs = source->GetProperty(COMPILE_DEFINITIONS)) { this->LocalGenerator->AppendDefines( defines, genexInterpreter.Evaluate(*compile_defs, COMPILE_DEFINITIONS)); } std::string defPropName = cmStrCat("COMPILE_DEFINITIONS_", cmSystemTools::UpperCase(config)); if (cmValue config_compile_defs = source->GetProperty(defPropName)) { this->LocalGenerator->AppendDefines( defines, genexInterpreter.Evaluate(*config_compile_defs, COMPILE_DEFINITIONS)); } std::string definesString = this->GetDefines(language, config); this->LocalGenerator->JoinDefines(defines, definesString, language); return definesString; } std::string cmNinjaTargetGenerator::ComputeIncludes( cmSourceFile const* source, const std::string& language, const std::string& config) { std::vector includes; cmGeneratorExpressionInterpreter genexInterpreter( this->LocalGenerator, config, this->GeneratorTarget, language); const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES"); if (cmValue cincludes = source->GetProperty(INCLUDE_DIRECTORIES)) { this->LocalGenerator->AppendIncludeDirectories( includes, genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES), *source); } std::string includesString = this->LocalGenerator->GetIncludeFlags( includes, this->GeneratorTarget, language, config, false); this->LocalGenerator->AppendFlags(includesString, this->GetIncludes(language, config)); return includesString; } cmNinjaDeps cmNinjaTargetGenerator::ComputeLinkDeps( const std::string& linkLanguage, const std::string& config, bool ignoreType) const { // Static libraries never depend on other targets for linking. if (!ignoreType && (this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY || this->GeneratorTarget->GetType() == cmStateEnums::OBJECT_LIBRARY)) { return cmNinjaDeps(); } cmComputeLinkInformation* cli = this->GeneratorTarget->GetLinkInformation(config); if (!cli) { return cmNinjaDeps(); } const std::vector& deps = cli->GetDepends(); cmNinjaDeps result(deps.size()); std::transform(deps.begin(), deps.end(), result.begin(), this->MapToNinjaPath()); // Add a dependency on the link definitions file, if any. if (cmGeneratorTarget::ModuleDefinitionInfo const* mdi = this->GeneratorTarget->GetModuleDefinitionInfo(config)) { for (cmSourceFile const* src : mdi->Sources) { result.push_back(this->ConvertToNinjaPath(src->GetFullPath())); } } // Add a dependency on user-specified manifest files, if any. std::vector manifest_srcs; this->GeneratorTarget->GetManifests(manifest_srcs, config); for (cmSourceFile const* manifest_src : manifest_srcs) { result.push_back(this->ConvertToNinjaPath(manifest_src->GetFullPath())); } // Add user-specified dependencies. std::vector linkDeps; this->GeneratorTarget->GetLinkDepends(linkDeps, config, linkLanguage); std::transform(linkDeps.begin(), linkDeps.end(), std::back_inserter(result), this->MapToNinjaPath()); return result; } std::string cmNinjaTargetGenerator::GetCompiledSourceNinjaPath( cmSourceFile const* source) const { // Pass source files to the compiler by absolute path. return this->ConvertToNinjaAbsPath(source->GetFullPath()); } std::string cmNinjaTargetGenerator::GetObjectFileDir( const std::string& config) const { std::string path = this->LocalGenerator->GetHomeRelativeOutputPath(); if (!path.empty()) { path += '/'; } path += cmStrCat(this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), this->GetGlobalGenerator()->ConfigDirectory(config)); return path; } std::string cmNinjaTargetGenerator::GetObjectFilePath( cmSourceFile const* source, const std::string& config) const { std::string const& objectName = this->GeneratorTarget->GetObjectName(source); return cmStrCat(this->GetObjectFileDir(config), '/', objectName); } std::string cmNinjaTargetGenerator::GetBmiFilePath( cmSourceFile const* source, const std::string& config) const { auto& importedConfigInfo = this->Configs.at(config).ImportedCxxModules; if (!importedConfigInfo.Initialized()) { std::string configUpper = cmSystemTools::UpperCase(config); std::string propName = cmStrCat("IMPORTED_CXX_MODULES_", configUpper); auto value = this->GeneratorTarget->GetSafeProperty(propName); importedConfigInfo.Initialize(value); } std::string bmiName = importedConfigInfo.BmiNameForSource(source->GetFullPath()); return cmStrCat(this->GetObjectFileDir(config), '/', bmiName); } std::string cmNinjaTargetGenerator::GetClangTidyReplacementsFilePath( std::string const& directory, cmSourceFile const& source, std::string const& config) const { auto path = this->LocalGenerator->GetHomeRelativeOutputPath(); if (!path.empty()) { path += '/'; } path = cmStrCat(directory, '/', path); auto const& objectName = this->GeneratorTarget->GetObjectName(&source); path = cmStrCat(std::move(path), this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), this->GetGlobalGenerator()->ConfigDirectory(config), '/', objectName, ".yaml"); return path; } std::string cmNinjaTargetGenerator::GetPreprocessedFilePath( cmSourceFile const* source, const std::string& config) const { // Choose an extension to compile already-preprocessed source. std::string ppExt = source->GetExtension(); if (cmHasLiteralPrefix(ppExt, "F")) { // Some Fortran compilers automatically enable preprocessing for // upper-case extensions. Since the source is already preprocessed, // use a lower-case extension. ppExt = cmSystemTools::LowerCase(ppExt); } if (ppExt == "fpp") { // Some Fortran compilers automatically enable preprocessing for // the ".fpp" extension. Since the source is already preprocessed, // use the ".f" extension. ppExt = "f"; } // Take the object file name and replace the extension. std::string const& objName = this->GeneratorTarget->GetObjectName(source); std::string const& objExt = this->GetGlobalGenerator()->GetLanguageOutputExtension(*source); assert(objName.size() >= objExt.size()); std::string const ppName = cmStrCat(objName.substr(0, objName.size() - objExt.size()), "-pp.", ppExt); std::string path = this->LocalGenerator->GetHomeRelativeOutputPath(); if (!path.empty()) { path += '/'; } path += cmStrCat(this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), this->GetGlobalGenerator()->ConfigDirectory(config), '/', ppName); return path; } std::string cmNinjaTargetGenerator::GetDyndepFilePath( std::string const& lang, const std::string& config) const { std::string path = this->LocalGenerator->GetHomeRelativeOutputPath(); if (!path.empty()) { path += '/'; } path += cmStrCat( this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), this->GetGlobalGenerator()->ConfigDirectory(config), '/', lang, ".dd"); return path; } std::string cmNinjaTargetGenerator::GetTargetDependInfoPath( std::string const& lang, const std::string& config) const { std::string path = cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), this->GetGlobalGenerator()->ConfigDirectory(config), '/', lang, "DependInfo.json"); return path; } std::string cmNinjaTargetGenerator::GetTargetOutputDir( const std::string& config) const { std::string dir = this->GeneratorTarget->GetDirectory(config); return this->ConvertToNinjaPath(dir); } std::string cmNinjaTargetGenerator::GetTargetFilePath( const std::string& name, const std::string& config) const { std::string path = this->GetTargetOutputDir(config); if (path.empty() || path == ".") { return name; } path += cmStrCat('/', name); return path; } std::string cmNinjaTargetGenerator::GetTargetName() const { return this->GeneratorTarget->GetName(); } bool cmNinjaTargetGenerator::SetMsvcTargetPdbVariable( cmNinjaVars& vars, const std::string& config) const { cmMakefile* mf = this->GetMakefile(); 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 = this->ComputeTargetCompilePDB(config); if (this->GeneratorTarget->GetType() == cmStateEnums::EXECUTABLE || this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY || this->GeneratorTarget->GetType() == cmStateEnums::SHARED_LIBRARY || this->GeneratorTarget->GetType() == cmStateEnums::MODULE_LIBRARY) { pdbPath = cmStrCat(this->GeneratorTarget->GetPDBDirectory(config), '/', this->GeneratorTarget->GetPDBName(config)); } vars["TARGET_PDB"] = this->GetLocalGenerator()->ConvertToOutputFormat( this->ConvertToNinjaPath(pdbPath), cmOutputConverter::SHELL); vars["TARGET_COMPILE_PDB"] = this->GetLocalGenerator()->ConvertToOutputFormat( this->ConvertToNinjaPath(compilePdbPath), cmOutputConverter::SHELL); this->EnsureParentDirectoryExists(pdbPath); this->EnsureParentDirectoryExists(compilePdbPath); return true; } return false; } void cmNinjaTargetGenerator::WriteLanguageRules(const std::string& language, const std::string& config) { #ifdef NINJA_GEN_VERBOSE_FILES this->GetRulesFileStream() << "# Rules for language " << language << "\n\n"; #endif this->WriteCompileRule(language, config); } namespace { // Create the command to run the dependency scanner std::string GetScanCommand( cm::string_view cmakeCmd, cm::string_view tdi, cm::string_view lang, cm::string_view srcFile, cm::string_view ddiFile, cm::optional srcOrigFile = cm::nullopt) { return cmStrCat(cmakeCmd, " -E cmake_ninja_depends --tdi=", tdi, " --lang=", lang, " --src=", srcFile, " --out=$out", " --dep=$DEP_FILE --obj=$OBJ_FILE --ddi=", ddiFile, srcOrigFile ? cmStrCat(" --src-orig=", *srcOrigFile) : ""); } // Helper function to create dependency scanning rule that may or may // not perform explicit preprocessing too. cmNinjaRule GetScanRule( std::string const& ruleName, std::string const& ppFileName, std::string const& deptype, cmRulePlaceholderExpander::RuleVariables const& vars, const std::string& responseFlag, const std::string& flags, cmRulePlaceholderExpander* const rulePlaceholderExpander, cmLocalNinjaGenerator* generator, std::vector scanCmds, const std::string& outputConfig) { cmNinjaRule rule(ruleName); // Scanning always uses a depfile for preprocessor dependencies. if (deptype == "msvc"_s) { rule.DepType = deptype; rule.DepFile.clear(); } else { rule.DepType.clear(); // no deps= for multiple outputs rule.DepFile = "$DEP_FILE"; } cmRulePlaceholderExpander::RuleVariables scanVars; scanVars.CMTargetName = vars.CMTargetName; scanVars.CMTargetType = vars.CMTargetType; scanVars.Language = vars.Language; scanVars.Object = "$OBJ_FILE"; scanVars.PreprocessedSource = ppFileName.c_str(); scanVars.DynDepFile = "$DYNDEP_INTERMEDIATE_FILE"; scanVars.DependencyFile = rule.DepFile.c_str(); scanVars.DependencyTarget = "$out"; // Scanning needs the same preprocessor settings as direct compilation would. scanVars.Source = vars.Source; scanVars.Defines = vars.Defines; scanVars.Includes = vars.Includes; // Scanning needs the compilation flags too. std::string scanFlags = flags; // If using a response file, move defines, includes, and flags into it. if (!responseFlag.empty()) { rule.RspFile = "$RSP_FILE"; rule.RspContent = cmStrCat(' ', scanVars.Defines, ' ', scanVars.Includes, ' ', scanFlags); scanFlags = cmStrCat(responseFlag, rule.RspFile); scanVars.Defines = ""; scanVars.Includes = ""; } scanVars.Flags = scanFlags.c_str(); // Rule for scanning a source file. for (std::string& scanCmd : scanCmds) { rulePlaceholderExpander->ExpandRuleVariables(generator, scanCmd, scanVars); } rule.Command = generator->BuildCommandLine(scanCmds, outputConfig, outputConfig); return rule; } } void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, const std::string& config) { // For some cases we scan to dynamically discover dependencies. bool const needDyndep = this->GetGeneratorTarget()->NeedDyndep(lang, config); if (needDyndep) { this->WriteCompileRule(lang, config, WithScanning::Yes); } this->WriteCompileRule(lang, config, WithScanning::No); } void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, const std::string& config, WithScanning withScanning) { cmRulePlaceholderExpander::RuleVariables vars; vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str(); vars.CMTargetType = cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType()).c_str(); vars.Language = lang.c_str(); vars.Source = "$in"; vars.Object = "$out"; vars.Defines = "$DEFINES"; vars.Includes = "$INCLUDES"; vars.TargetPDB = "$TARGET_PDB"; vars.TargetCompilePDB = "$TARGET_COMPILE_PDB"; vars.ObjectDir = "$OBJECT_DIR"; vars.ObjectFileDir = "$OBJECT_FILE_DIR"; vars.CudaCompileMode = "$CUDA_COMPILE_MODE"; vars.ISPCHeader = "$ISPC_HEADER_FILE"; cmMakefile* mf = this->GetMakefile(); // For some cases we scan to dynamically discover dependencies. bool const compilationPreprocesses = !this->NeedExplicitPreprocessing(lang); std::string flags = "$FLAGS"; std::string responseFlag; bool const lang_supports_response = lang != "RC"; if (lang_supports_response && this->ForceResponseFile()) { std::string const responseFlagVar = cmStrCat("CMAKE_", lang, "_RESPONSE_FILE_FLAG"); responseFlag = this->Makefile->GetSafeDefinition(responseFlagVar); if (responseFlag.empty() && lang != "CUDA") { responseFlag = "@"; } } std::string const modmapFormatVar = cmStrCat("CMAKE_", lang, "_MODULE_MAP_FORMAT"); std::string const modmapFormat = this->Makefile->GetSafeDefinition(modmapFormatVar); auto rulePlaceholderExpander = this->GetLocalGenerator()->CreateRulePlaceholderExpander(); std::string const tdi = this->GetLocalGenerator()->ConvertToOutputFormat( this->ConvertToNinjaPath(this->GetTargetDependInfoPath(lang, config)), cmLocalGenerator::SHELL); std::string launcher; std::string val = this->GetLocalGenerator()->GetRuleLauncher( this->GetGeneratorTarget(), "RULE_LAUNCH_COMPILE", config); if (cmNonempty(val)) { launcher = cmStrCat(val, ' '); } std::string const cmakeCmd = this->GetLocalGenerator()->ConvertToOutputFormat( cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL); if (withScanning == WithScanning::Yes) { const auto& scanDepType = this->GetMakefile()->GetSafeDefinition( cmStrCat("CMAKE_", lang, "_SCANDEP_DEPFILE_FORMAT")); // Rule to scan dependencies of sources that need preprocessing. { cmList scanCommands; std::string scanRuleName; std::string ppFileName; if (compilationPreprocesses) { scanRuleName = this->LanguageScanRule(lang, config); ppFileName = "$PREPROCESSED_OUTPUT_FILE"; std::string const& scanCommand = mf->GetRequiredDefinition( cmStrCat("CMAKE_", lang, "_SCANDEP_SOURCE")); scanCommands.assign(scanCommand); for (auto& i : scanCommands) { i = cmStrCat(launcher, i); } } else { scanRuleName = this->LanguagePreprocessAndScanRule(lang, config); ppFileName = "$out"; std::string const& ppCommmand = mf->GetRequiredDefinition( cmStrCat("CMAKE_", lang, "_PREPROCESS_SOURCE")); scanCommands.assign(ppCommmand); for (auto& i : scanCommands) { i = cmStrCat(launcher, i); } scanCommands.emplace_back(GetScanCommand( cmakeCmd, tdi, lang, "$out", "$DYNDEP_INTERMEDIATE_FILE", "$in")); } auto scanRule = GetScanRule( scanRuleName, ppFileName, scanDepType, vars, responseFlag, flags, rulePlaceholderExpander.get(), this->GetLocalGenerator(), std::move(scanCommands), config); scanRule.Comment = cmStrCat("Rule for generating ", lang, " dependencies."); if (compilationPreprocesses) { scanRule.Description = cmStrCat("Scanning $in for ", lang, " dependencies"); } else { scanRule.Description = cmStrCat("Building ", lang, " preprocessed $out"); } this->GetGlobalGenerator()->AddRule(scanRule); } if (!compilationPreprocesses) { // Compilation will not preprocess, so it does not need the defines // unless the compiler wants them for some other purpose. if (!this->CompileWithDefines(lang)) { vars.Defines = ""; } // Rule to scan dependencies of sources that do not need preprocessing. std::string const& scanRuleName = this->LanguageScanRule(lang, config); std::vector scanCommands; scanCommands.emplace_back( GetScanCommand(cmakeCmd, tdi, lang, "$in", "$out")); auto scanRule = GetScanRule(scanRuleName, "", scanDepType, vars, "", flags, rulePlaceholderExpander.get(), this->GetLocalGenerator(), std::move(scanCommands), config); // Write the rule for generating dependencies for the given language. scanRule.Comment = cmStrCat("Rule for generating ", lang, " dependencies on non-preprocessed files."); scanRule.Description = cmStrCat("Generating ", lang, " dependencies for $in"); this->GetGlobalGenerator()->AddRule(scanRule); } // Write the rule for ninja dyndep file generation. cmNinjaRule rule(this->LanguageDyndepRule(lang, config)); // Command line length is almost always limited -> use response file for // dyndep rules rule.RspFile = "$out.rsp"; rule.RspContent = "$in"; // Ninja's collator writes all outputs using `cmGeneratedFileStream`, so // they are only updated if contents actually change. Avoid running // dependent jobs if the contents don't change by telling `ninja` to check // the timestamp again. rule.Restat = "1"; // Run CMake dependency scanner on the source file (using the preprocessed // source if that was performed). std::string ddModmapArg; if (!modmapFormat.empty()) { ddModmapArg += cmStrCat(" --modmapfmt=", modmapFormat); } { std::vector ddCmds; { std::string ccmd = cmStrCat( cmakeCmd, " -E cmake_ninja_dyndep --tdi=", tdi, " --lang=", lang, ddModmapArg, " --dd=$out @", rule.RspFile); ddCmds.emplace_back(std::move(ccmd)); } rule.Command = this->GetLocalGenerator()->BuildCommandLine(ddCmds, config, config); } rule.Comment = cmStrCat("Rule to generate ninja dyndep files for ", lang, '.'); rule.Description = cmStrCat("Generating ", lang, " dyndep file $out"); this->GetGlobalGenerator()->AddRule(rule); } cmNinjaRule rule(this->LanguageCompilerRule(lang, config, withScanning)); // If using a response file, move defines, includes, and flags into it. if (!responseFlag.empty()) { rule.RspFile = "$RSP_FILE"; rule.RspContent = cmStrCat(' ', vars.Defines, ' ', vars.Includes, ' ', flags); flags = cmStrCat(responseFlag, rule.RspFile); vars.Defines = ""; vars.Includes = ""; // Swift consumes all source files in a module at once, which reaches // command line length limits pretty quickly. Inject source files into the // response file in this case as well. if (lang == "Swift") { rule.RspContent = cmStrCat(rule.RspContent, ' ', vars.Source); vars.Source = ""; } } // Tell ninja dependency format so all deps can be loaded into a database std::string cldeps; if (!compilationPreprocesses) { // The compiler will not do preprocessing, so it has no such dependencies. } else if (mf->IsOn(cmStrCat("CMAKE_NINJA_CMCLDEPS_", lang))) { // For the MS resource compiler we need cmcldeps, but skip dependencies // for source-file try_compile cases because they are always fresh. if (!mf->GetIsSourceFileTryCompile()) { rule.DepType = "gcc"; rule.DepFile = "$DEP_FILE"; cmValue d = mf->GetDefinition("CMAKE_C_COMPILER"); const std::string cl = d ? *d : mf->GetSafeDefinition("CMAKE_CXX_COMPILER"); std::string cmcldepsPath; cmSystemTools::GetShortPath(cmSystemTools::GetCMClDepsCommand(), cmcldepsPath); cldeps = cmStrCat(cmcldepsPath, ' ', lang, ' ', vars.Source, " $DEP_FILE $out \"", mf->GetSafeDefinition("CMAKE_CL_SHOWINCLUDES_PREFIX"), "\" \"", cl, "\" "); } } else { const auto& depType = this->GetMakefile()->GetSafeDefinition( cmStrCat("CMAKE_", lang, "_DEPFILE_FORMAT")); if (depType == "msvc"_s) { rule.DepType = "msvc"; rule.DepFile.clear(); } else { rule.DepType = "gcc"; rule.DepFile = "$DEP_FILE"; } vars.DependencyFile = rule.DepFile.c_str(); vars.DependencyTarget = "$out"; const std::string flagsName = cmStrCat("CMAKE_DEPFILE_FLAGS_", lang); std::string depfileFlags = mf->GetSafeDefinition(flagsName); if (!depfileFlags.empty()) { rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), depfileFlags, vars); flags += cmStrCat(' ', depfileFlags); } } if (withScanning == WithScanning::Yes && !modmapFormat.empty()) { std::string modmapFlags = mf->GetRequiredDefinition(cmStrCat("CMAKE_", lang, "_MODULE_MAP_FLAG")); cmSystemTools::ReplaceString(modmapFlags, "", "$DYNDEP_MODULE_MAP_FILE"); flags += cmStrCat(' ', modmapFlags); } vars.Flags = flags.c_str(); vars.DependencyFile = rule.DepFile.c_str(); std::string cudaCompileMode; if (lang == "CUDA") { if (this->GeneratorTarget->GetPropertyAsBool( "CUDA_SEPARABLE_COMPILATION")) { const std::string& rdcFlag = this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_RDC_FLAG"); cudaCompileMode = cmStrCat(cudaCompileMode, rdcFlag, " "); } static std::array const compileModes{ { "PTX"_s, "CUBIN"_s, "FATBIN"_s, "OPTIX"_s } }; bool useNormalCompileMode = true; for (cm::string_view mode : compileModes) { auto propName = cmStrCat("CUDA_", mode, "_COMPILATION"); auto defName = cmStrCat("_CMAKE_CUDA_", mode, "_FLAG"); if (this->GeneratorTarget->GetPropertyAsBool(propName)) { const std::string& flag = this->Makefile->GetRequiredDefinition(defName); cudaCompileMode = cmStrCat(cudaCompileMode, flag); useNormalCompileMode = false; break; } } if (useNormalCompileMode) { const std::string& wholeFlag = this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_WHOLE_FLAG"); cudaCompileMode = cmStrCat(cudaCompileMode, wholeFlag); } vars.CudaCompileMode = cudaCompileMode.c_str(); } // Rule for compiling object file. const std::string cmdVar = cmStrCat("CMAKE_", lang, "_COMPILE_OBJECT"); const std::string& compileCmd = mf->GetRequiredDefinition(cmdVar); cmList compileCmds(compileCmd); if (!compileCmds.empty()) { compileCmds.front().insert(0, "${CODE_CHECK}"); compileCmds.front().insert(0, "${LAUNCHER}"); } if (!compileCmds.empty()) { compileCmds.front().insert(0, cldeps); } const auto& extraCommands = this->GetMakefile()->GetSafeDefinition( cmStrCat("CMAKE_", lang, "_DEPENDS_EXTRA_COMMANDS")); if (!extraCommands.empty()) { compileCmds.append(extraCommands); } for (auto& i : compileCmds) { i = cmStrCat(launcher, i); rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), i, vars); } rule.Command = this->GetLocalGenerator()->BuildCommandLine(compileCmds, config, config); // Write the rule for compiling file of the given language. rule.Comment = cmStrCat("Rule for compiling ", lang, " files."); rule.Description = cmStrCat("Building ", lang, " object $out"); this->GetGlobalGenerator()->AddRule(rule); } void cmNinjaTargetGenerator::WriteObjectBuildStatements( const std::string& config, const std::string& fileConfig, bool firstForConfig) { this->GeneratorTarget->CheckCxxModuleStatus(config); // Write comments. cmGlobalNinjaGenerator::WriteDivider(this->GetImplFileStream(fileConfig)); this->GetImplFileStream(fileConfig) << "# Object build statements for " << cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType()) << " target " << this->GetTargetName() << "\n\n"; std::vector customCommands; { std::vector customCommandSources; this->GeneratorTarget->GetCustomCommands(customCommandSources, config); for (cmSourceFile const* sf : customCommandSources) { cmCustomCommand const* cc = sf->GetCustomCommand(); this->GetLocalGenerator()->AddCustomCommandTarget( cc, this->GetGeneratorTarget()); customCommands.push_back(cc); } } { std::vector headerSources; this->GeneratorTarget->GetHeaderSources(headerSources, config); this->OSXBundleGenerator->GenerateMacOSXContentStatements( headerSources, this->Configs[fileConfig].MacOSXContentGenerator.get(), config); } { std::vector extraSources; this->GeneratorTarget->GetExtraSources(extraSources, config); this->OSXBundleGenerator->GenerateMacOSXContentStatements( extraSources, this->Configs[fileConfig].MacOSXContentGenerator.get(), config); } if (firstForConfig) { cmValue pchExtension = this->GetMakefile()->GetDefinition("CMAKE_PCH_EXTENSION"); std::vector externalObjects; this->GeneratorTarget->GetExternalObjects(externalObjects, config); for (cmSourceFile const* sf : externalObjects) { auto objectFileName = this->GetGlobalGenerator()->ExpandCFGIntDir( this->ConvertToNinjaPath(sf->GetFullPath()), config); if (!cmHasSuffix(objectFileName, pchExtension)) { this->Configs[config].Objects.push_back(objectFileName); } } } if (!this->GetGlobalGenerator()->SupportsCWDDepend()) { // Ensure that the object directory exists. If there are no objects in the // target (e.g., an empty `OBJECT` library), the directory is still listed // as an order-only depends in the build files. Alternate `ninja` // implementations may not allow this (such as `samu`). See #25526. auto const objectDir = this->GetObjectFileDir(config); this->EnsureDirectoryExists(objectDir); } { cmNinjaBuild build("phony"); build.Comment = cmStrCat("Order-only phony target for ", this->GetTargetName()); build.Outputs.push_back(this->OrderDependsTargetForTarget(config)); // Gather order-only dependencies on custom command outputs. std::vector ccouts; std::vector ccouts_private; bool usePrivateGeneratedSources = false; if (this->GeneratorTarget->Target->HasFileSets()) { switch (this->GetGeneratorTarget()->GetPolicyStatusCMP0154()) { case cmPolicies::WARN: case cmPolicies::OLD: break; case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::REQUIRED_IF_USED: case cmPolicies::NEW: usePrivateGeneratedSources = true; break; } } for (cmCustomCommand const* cc : customCommands) { cmCustomCommandGenerator ccg(*cc, config, this->GetLocalGenerator()); const std::vector& ccoutputs = ccg.GetOutputs(); const std::vector& ccbyproducts = ccg.GetByproducts(); auto const nPreviousOutputs = ccouts.size(); ccouts.insert(ccouts.end(), ccoutputs.begin(), ccoutputs.end()); ccouts.insert(ccouts.end(), ccbyproducts.begin(), ccbyproducts.end()); if (usePrivateGeneratedSources) { auto it = ccouts.begin(); // Skip over outputs that were already detected. std::advance(it, nPreviousOutputs); while (it != ccouts.end()) { cmFileSet const* fileset = this->GeneratorTarget->GetFileSetForSource( config, this->Makefile->GetOrCreateGeneratedSource(*it)); bool isVisible = fileset && cmFileSetVisibilityIsForInterface(fileset->GetVisibility()); bool isIncludeable = !fileset || cmFileSetTypeCanBeIncluded(fileset->GetType()); if (fileset && isVisible && isIncludeable) { ++it; continue; } if (!fileset || isIncludeable) { ccouts_private.push_back(*it); } it = ccouts.erase(it); } } } cmNinjaDeps& orderOnlyDeps = build.OrderOnlyDeps; this->GetLocalGenerator()->AppendTargetDepends( this->GeneratorTarget, orderOnlyDeps, config, fileConfig, DependOnTargetOrdering); // Add order-only dependencies on other files associated with the target. cm::append(orderOnlyDeps, this->Configs[config].ExtraFiles); // Add order-only dependencies on custom command outputs. std::transform(ccouts.begin(), ccouts.end(), std::back_inserter(orderOnlyDeps), this->MapToNinjaPath()); std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end()); orderOnlyDeps.erase( std::unique(orderOnlyDeps.begin(), orderOnlyDeps.end()), orderOnlyDeps.end()); // The phony target must depend on at least one input or ninja will explain // that "output ... of phony edge with no inputs doesn't exist" and // consider the phony output "dirty". if (orderOnlyDeps.empty()) { std::string tgtDir; if (this->GetGlobalGenerator()->SupportsCWDDepend()) { tgtDir = "."; } else { // Any path that always exists will work here. tgtDir = cmStrCat( this->LocalGenerator->GetCurrentBinaryDirectory(), '/', this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget)); } orderOnlyDeps.push_back(this->ConvertToNinjaPath(tgtDir)); } this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), build); // Add order-only dependencies on custom command outputs that are // private to this target. this->HasPrivateGeneratedSources = !ccouts_private.empty(); if (this->HasPrivateGeneratedSources) { cmNinjaBuild buildPrivate("phony"); cmNinjaDeps& orderOnlyDepsPrivate = buildPrivate.OrderOnlyDeps; orderOnlyDepsPrivate.push_back( this->OrderDependsTargetForTarget(config)); buildPrivate.Outputs.push_back( this->OrderDependsTargetForTargetPrivate(config)); std::transform(ccouts_private.begin(), ccouts_private.end(), std::back_inserter(orderOnlyDepsPrivate), this->MapToNinjaPath()); std::sort(orderOnlyDepsPrivate.begin(), orderOnlyDepsPrivate.end()); orderOnlyDepsPrivate.erase( std::unique(orderOnlyDepsPrivate.begin(), orderOnlyDepsPrivate.end()), orderOnlyDepsPrivate.end()); this->GetGlobalGenerator()->WriteBuild( this->GetImplFileStream(fileConfig), buildPrivate); } } { std::vector objectSources; this->GeneratorTarget->GetObjectSources(objectSources, config); std::vector swiftSources; for (cmSourceFile const* sf : objectSources) { if (this->GetLocalGenerator()->IsSplitSwiftBuild() && sf->GetLanguage() == "Swift") { swiftSources.push_back(sf); } else { this->WriteObjectBuildStatement(sf, config, fileConfig, firstForConfig); } } WriteSwiftObjectBuildStatement(swiftSources, config, fileConfig, firstForConfig); } { std::vector bmiOnlySources; this->GeneratorTarget->GetCxxModuleSources(bmiOnlySources, config); for (cmSourceFile const* sf : bmiOnlySources) { this->WriteCxxModuleBmiBuildStatement(sf, config, fileConfig, firstForConfig); } } // Detect sources in `CXX_MODULES` which are not compiled. { std::vector sources; this->GeneratorTarget->GetSourceFiles(sources, config); for (cmSourceFile const* sf : sources) { cmFileSet const* fs = this->GeneratorTarget->GetFileSetForSource(config, sf); if (!fs) { continue; } if (fs->GetType() != "CXX_MODULES"_s) { continue; } if (sf->GetLanguage().empty()) { this->GeneratorTarget->Makefile->IssueMessage( MessageType::FATAL_ERROR, cmStrCat("Target \"", this->GeneratorTarget->GetName(), "\" has source file\n ", sf->GetFullPath(), "\nin a \"FILE_SET TYPE CXX_MODULES\" but it is not " "scheduled for compilation.")); } } } // Check if there are Fortran objects which need to participate in forwarding // module requirements. if (this->GeneratorTarget->HaveFortranSources(config) && !this->Configs[config].ScanningInfo.count("Fortran")) { ScanningFiles files; this->Configs[config].ScanningInfo["Fortran"].emplace_back(files); this->WriteCompileRule("Fortran", config, WithScanning::Yes); } for (auto const& langScanningFiles : this->Configs[config].ScanningInfo) { std::string const& language = langScanningFiles.first; std::vector const& scanningFiles = langScanningFiles.second; cmNinjaBuild build(this->LanguageDyndepRule(language, config)); build.Outputs.push_back(this->GetDyndepFilePath(language, config)); build.ImplicitOuts.emplace_back( cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget), this->GetGlobalGenerator()->ConfigDirectory(config), '/', language, "Modules.json")); build.ImplicitDeps.emplace_back( this->GetTargetDependInfoPath(language, config)); { auto bdb_path = this->GeneratorTarget->BuildDatabasePath(language, config); if (!bdb_path.empty()) { build.ImplicitOuts.emplace_back(this->ConvertToNinjaPath(bdb_path)); } } auto bdb_path = this->GeneratorTarget->BuildDatabasePath(language, config); if (!bdb_path.empty()) { auto db = cmBuildDatabase::ForTarget(this->GeneratorTarget, config); auto mcdb_template_path = cmStrCat(bdb_path, ".in"); db.Write(mcdb_template_path); build.ImplicitDeps.emplace_back(std::move(mcdb_template_path)); build.ImplicitOuts.emplace_back(std::move(bdb_path)); } for (auto const& scanFiles : scanningFiles) { if (!scanFiles.ScanningOutput.empty()) { build.ExplicitDeps.push_back(scanFiles.ScanningOutput); } if (!scanFiles.ModuleMapFile.empty()) { build.ImplicitOuts.push_back(scanFiles.ModuleMapFile); } } this->WriteTargetDependInfo(language, config); auto const linked_directories = this->GetLinkedTargetDirectories(language, config); for (std::string const& l : linked_directories.Direct) { build.ImplicitDeps.emplace_back( cmStrCat(l, '/', language, "Modules.json")); } for (std::string const& l : linked_directories.Forward) { build.ImplicitDeps.emplace_back( cmStrCat(l, '/', language, "Modules.json")); } this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), build); } this->GetImplFileStream(fileConfig) << "\n"; } void cmNinjaTargetGenerator::GenerateSwiftOutputFileMap( const std::string& config, std::string& flags) { if (this->Configs[config].SwiftOutputMap.empty()) { return; } std::string const targetSwiftDepsPath = [this, config]() -> std::string { cmGeneratorTarget const* target = this->GeneratorTarget; if (cmValue name = target->GetProperty("Swift_DEPENDENCIES_FILE")) { return *name; } return this->GetLocalGenerator()->ConvertToOutputFormat( this->ConvertToNinjaPath(cmStrCat(target->GetSupportDirectory(), '/', config, '/', target->GetName(), ".swiftdeps")), cmOutputConverter::SHELL); }(); std::string mapFilePath = cmStrCat(this->GeneratorTarget->GetSupportDirectory(), '/', config, '/', "output-file-map.json"); // build the global target dependencies // https://github.com/apple/swift/blob/master/docs/Driver.md#output-file-maps Json::Value deps(Json::objectValue); deps["swift-dependencies"] = targetSwiftDepsPath; this->Configs[config].SwiftOutputMap[""] = deps; cmGeneratedFileStream output(mapFilePath); output << this->Configs[config].SwiftOutputMap; // Add flag this->LocalGenerator->AppendFlags(flags, "-output-file-map"); this->LocalGenerator->AppendFlagEscape( flags, this->GetLocalGenerator()->ConvertToOutputFormat( ConvertToNinjaPath(mapFilePath), cmOutputConverter::SHELL)); } namespace { cmNinjaBuild GetScanBuildStatement(const std::string& ruleName, const std::string& ppFileName, bool compilePP, bool compilePPWithDefines, bool compilationPreprocesses, cmNinjaBuild& objBuild, cmNinjaVars& vars, const std::string& objectFileName, cmNinjaTargetGenerator* tg) { cmNinjaBuild scanBuild(ruleName); if (compilePP) { // Move compilation dependencies to the scan/preprocessing build statement. std::swap(scanBuild.ExplicitDeps, objBuild.ExplicitDeps); std::swap(scanBuild.ImplicitDeps, objBuild.ImplicitDeps); std::swap(scanBuild.OrderOnlyDeps, objBuild.OrderOnlyDeps); std::swap(scanBuild.Variables["IN_ABS"], vars["IN_ABS"]); // The actual compilation will now use the preprocessed source. objBuild.ExplicitDeps.push_back(ppFileName); } else { // Copy compilation dependencies to the scan/preprocessing build statement. scanBuild.ExplicitDeps = objBuild.ExplicitDeps; scanBuild.ImplicitDeps = objBuild.ImplicitDeps; scanBuild.OrderOnlyDeps = objBuild.OrderOnlyDeps; scanBuild.Variables["IN_ABS"] = vars["IN_ABS"]; } // Scanning and compilation generally use the same flags. scanBuild.Variables["FLAGS"] = vars["FLAGS"]; if (compilePP && !compilePPWithDefines) { // Move preprocessor definitions to the scan/preprocessor build statement. std::swap(scanBuild.Variables["DEFINES"], vars["DEFINES"]); } else { // Copy preprocessor definitions to the scan/preprocessor build statement. scanBuild.Variables["DEFINES"] = vars["DEFINES"]; } // Copy include directories to the preprocessor build statement. The // Fortran compilation build statement still needs them for the INCLUDE // directive. scanBuild.Variables["INCLUDES"] = vars["INCLUDES"]; // Tell dependency scanner the object file that will result from // compiling the source. scanBuild.Variables["OBJ_FILE"] = objectFileName; // Tell dependency scanner where to store dyndep intermediate results. std::string ddiFileName = cmStrCat(objectFileName, ".ddi"); scanBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFileName; scanBuild.RspFile = cmStrCat(ddiFileName, ".rsp"); // Outputs of the scan/preprocessor build statement. if (compilePP) { scanBuild.Outputs.push_back(ppFileName); scanBuild.ImplicitOuts.push_back(ddiFileName); } else { scanBuild.Outputs.push_back(ddiFileName); scanBuild.Variables["PREPROCESSED_OUTPUT_FILE"] = ppFileName; if (!compilationPreprocesses) { // Compilation does not preprocess and we are not compiling an // already-preprocessed source. Make compilation depend on the scan // results to honor implicit dependencies discovered during scanning // (such as Fortran INCLUDE directives). objBuild.ImplicitDeps.emplace_back(ddiFileName); } } // Scanning always provides a depfile for preprocessor dependencies. This // variable is unused in `msvc`-deptype scanners. tg->AddDepfileBinding(scanBuild.Variables, cmStrCat(scanBuild.Outputs.front(), ".d")); if (compilePP) { // The actual compilation does not need a depfile because it // depends on the already-preprocessed source. tg->RemoveDepfileBinding(vars); } return scanBuild; } } void cmNinjaTargetGenerator::WriteObjectBuildStatement( cmSourceFile const* source, const std::string& config, const std::string& fileConfig, bool firstForConfig) { std::string const language = source->GetLanguage(); std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(source); std::string const objectDir = this->ConvertToNinjaPath( cmStrCat(this->GeneratorTarget->GetSupportDirectory(), this->GetGlobalGenerator()->ConfigDirectory(config))); std::string const objectFileName = this->ConvertToNinjaPath(this->GetObjectFilePath(source, config)); std::string const objectFileDir = cmSystemTools::GetFilenamePath(objectFileName); std::string cmakeVarLang = cmStrCat("CMAKE_", language); // build response file name std::string cmakeLinkVar = cmStrCat(cmakeVarLang, "_RESPONSE_FILE_FLAG"); cmValue flag = this->GetMakefile()->GetDefinition(cmakeLinkVar); bool const lang_supports_response = !(language == "RC" || (language == "CUDA" && !flag)); int const commandLineLengthLimit = ((lang_supports_response && this->ForceResponseFile())) ? -1 : 0; cmValue pchExtension = this->GetMakefile()->GetDefinition("CMAKE_PCH_EXTENSION"); bool const isPch = cmHasSuffix(objectFileName, pchExtension); bool const needDyndep = !isPch && this->GeneratorTarget->NeedDyndepForSource(language, config, source); WithScanning withScanning = needDyndep ? WithScanning::Yes : WithScanning::No; cmNinjaBuild objBuild( this->LanguageCompilerRule(language, config, withScanning)); cmNinjaVars& vars = objBuild.Variables; vars["FLAGS"] = this->ComputeFlagsForObject(source, language, config, objectFileName); vars["DEFINES"] = this->ComputeDefines(source, language, config); vars["INCLUDES"] = this->ComputeIncludes(source, language, config); auto compilerLauncher = this->GetCompilerLauncher(language, config); cmValue const skipCodeCheck = source->GetProperty("SKIP_LINTING"); if (!skipCodeCheck.IsOn()) { auto const cmakeCmd = this->GetLocalGenerator()->ConvertToOutputFormat( cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL); vars["CODE_CHECK"] = this->GenerateCodeCheckRules(*source, compilerLauncher, cmakeCmd, config, [this](const std::string& path) { return this->ConvertToNinjaPath(path); }); } // If compiler launcher was specified and not consumed above, it // goes to the beginning of the command line. if (!compilerLauncher.empty()) { cmList args{ compilerLauncher, cmList::EmptyElements::Yes }; if (!args.empty()) { args[0] = this->LocalGenerator->ConvertToOutputFormat( args[0], cmOutputConverter::SHELL); for (std::string& i : cmMakeRange(args.begin() + 1, args.end())) { i = this->LocalGenerator->EscapeForShell(i); } vars["LAUNCHER"] = args.join(" ") + " "; } } if (this->GetMakefile()->GetSafeDefinition( cmStrCat("CMAKE_", language, "_DEPFILE_FORMAT")) != "msvc"_s) { bool replaceExt = false; if (!language.empty()) { std::string repVar = cmStrCat("CMAKE_", language, "_DEPFILE_EXTENSION_REPLACE"); replaceExt = this->Makefile->IsOn(repVar); } this->AddDepfileBinding( vars, replaceExt ? cmStrCat(objectFileDir, '/', cmSystemTools::GetFilenameWithoutLastExtension( objectFileName), ".d") : cmStrCat(objectFileName, ".d")); } this->SetMsvcTargetPdbVariable(vars, config); if (firstForConfig) { this->ExportObjectCompileCommand( language, sourceFilePath, objectDir, objectFileName, objectFileDir, vars["FLAGS"], vars["DEFINES"], vars["INCLUDES"], vars["TARGET_COMPILE_PDB"], vars["TARGET_PDB"], config, withScanning); } objBuild.Outputs.push_back(objectFileName); if (firstForConfig && !isPch) { // Add this object to the list of object files. this->Configs[config].Objects.push_back(objectFileName); } objBuild.ExplicitDeps.push_back(sourceFilePath); // Add precompile headers dependencies std::vector depList; std::vector pchArchs = this->GeneratorTarget->GetPchArchs(config, language); std::unordered_set pchSources; for (const std::string& arch : pchArchs) { const std::string pchSource = this->GeneratorTarget->GetPchSource(config, language, arch); if (!pchSource.empty()) { pchSources.insert(pchSource); } } if (!pchSources.empty() && !source->GetProperty("SKIP_PRECOMPILE_HEADERS")) { for (const std::string& arch : pchArchs) { depList.push_back( this->GeneratorTarget->GetPchHeader(config, language, arch)); if (pchSources.find(source->GetFullPath()) == pchSources.end()) { depList.push_back( this->GeneratorTarget->GetPchFile(config, language, arch)); } } } if (cmValue objectDeps = source->GetProperty("OBJECT_DEPENDS")) { cmList objDepList{ *objectDeps }; std::copy(objDepList.begin(), objDepList.end(), std::back_inserter(depList)); } if (!depList.empty()) { for (std::string& odi : depList) { if (cmSystemTools::FileIsFullPath(odi)) { odi = cmSystemTools::CollapseFullPath(odi); } } std::transform(depList.begin(), depList.end(), std::back_inserter(objBuild.ImplicitDeps), this->MapToNinjaPath()); } if (this->HasPrivateGeneratedSources) { objBuild.OrderOnlyDeps.push_back( this->OrderDependsTargetForTargetPrivate(config)); } else { objBuild.OrderOnlyDeps.push_back( this->OrderDependsTargetForTarget(config)); } // If the source file is GENERATED and does not have a custom command // (either attached to this source file or another one), assume that one of // the target dependencies, OBJECT_DEPENDS or header file custom commands // will rebuild the file. if (source->GetIsGenerated() && !source->GetPropertyAsBool("__CMAKE_GENERATED_BY_CMAKE") && !source->GetCustomCommand() && !this->GetGlobalGenerator()->HasCustomCommandOutput(sourceFilePath)) { this->GetGlobalGenerator()->AddAssumedSourceDependencies( sourceFilePath, objBuild.OrderOnlyDeps); } // For some cases we scan to dynamically discover dependencies. bool const compilationPreprocesses = !this->NeedExplicitPreprocessing(language); std::string modmapFormat; if (needDyndep) { std::string const modmapFormatVar = cmStrCat("CMAKE_", language, "_MODULE_MAP_FORMAT"); modmapFormat = this->Makefile->GetSafeDefinition(modmapFormatVar); } if (needDyndep) { // If source/target has preprocessing turned off, we still need to // generate an explicit dependency step const auto srcpp = source->GetSafeProperty("Fortran_PREPROCESS"); cmOutputConverter::FortranPreprocess preprocess = cmOutputConverter::GetFortranPreprocess(srcpp); if (preprocess == cmOutputConverter::FortranPreprocess::Unset) { const auto& tgtpp = this->GeneratorTarget->GetSafeProperty("Fortran_PREPROCESS"); preprocess = cmOutputConverter::GetFortranPreprocess(tgtpp); } bool const compilePP = !compilationPreprocesses && (preprocess != cmOutputConverter::FortranPreprocess::NotNeeded); bool const compilePPWithDefines = compilePP && this->CompileWithDefines(language); std::string scanRuleName; std::string ppFileName; if (compilePP) { scanRuleName = this->LanguagePreprocessAndScanRule(language, config); ppFileName = this->ConvertToNinjaPath( this->GetPreprocessedFilePath(source, config)); } else { scanRuleName = this->LanguageScanRule(language, config); ppFileName = cmStrCat(objectFileName, ".ddi.i"); } cmNinjaBuild ppBuild = GetScanBuildStatement( scanRuleName, ppFileName, compilePP, compilePPWithDefines, compilationPreprocesses, objBuild, vars, objectFileName, this); if (compilePP) { // In case compilation requires flags that are incompatible with // preprocessing, include them here. std::string const& postFlag = this->Makefile->GetSafeDefinition( cmStrCat("CMAKE_", language, "_POSTPROCESS_FLAG")); this->LocalGenerator->AppendFlags(vars["FLAGS"], postFlag); // Prepend source file's original directory as an include directory // so e.g. Fortran INCLUDE statements can look for files in it. std::vector sourceDirectory; sourceDirectory.push_back( cmSystemTools::GetParentDirectory(source->GetFullPath())); std::string sourceDirectoryFlag = this->LocalGenerator->GetIncludeFlags( sourceDirectory, this->GeneratorTarget, language, config, false); vars["INCLUDES"] = cmStrCat(sourceDirectoryFlag, ' ', vars["INCLUDES"]); } ScanningFiles scanningFiles; if (firstForConfig) { scanningFiles.ScanningOutput = cmStrCat(objectFileName, ".ddi"); } this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(), ppBuild.Variables); this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), ppBuild, commandLineLengthLimit); std::string const dyndep = this->GetDyndepFilePath(language, config); objBuild.OrderOnlyDeps.push_back(dyndep); vars["dyndep"] = dyndep; if (!modmapFormat.empty()) { // XXX(modmap): If changing this path construction, change // `cmGlobalNinjaGenerator::WriteDyndep` and // `cmNinjaTargetGenerator::ExportObjectCompileCommand` to expect the // corresponding file path. std::string ddModmapFile = cmStrCat(objectFileName, ".modmap"); vars["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile; objBuild.ImplicitDeps.push_back(ddModmapFile); scanningFiles.ModuleMapFile = std::move(ddModmapFile); } if (!scanningFiles.IsEmpty()) { this->Configs[config].ScanningInfo[language].emplace_back(scanningFiles); } } this->EnsureParentDirectoryExists(objectFileName); vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat( objectDir, cmOutputConverter::SHELL); vars["OBJECT_FILE_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat( objectFileDir, cmOutputConverter::SHELL); this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(), vars); if (!pchSources.empty() && !source->GetProperty("SKIP_PRECOMPILE_HEADERS")) { auto pchIt = pchSources.find(source->GetFullPath()); if (pchIt != pchSources.end()) { this->addPoolNinjaVariable("JOB_POOL_PRECOMPILE_HEADER", this->GetGeneratorTarget(), vars); } } objBuild.RspFile = cmStrCat(objectFileName, ".rsp"); if (language == "ISPC") { std::string const& objectName = this->GeneratorTarget->GetObjectName(source); std::string ispcSource = cmSystemTools::GetFilenameWithoutLastExtension(objectName); ispcSource = cmSystemTools::GetFilenameWithoutLastExtension(ispcSource); cmValue ispcSuffixProp = this->GeneratorTarget->GetProperty("ISPC_HEADER_SUFFIX"); assert(ispcSuffixProp); std::string ispcHeaderDirectory = this->GeneratorTarget->GetObjectDirectory(config); if (cmValue prop = this->GeneratorTarget->GetProperty("ISPC_HEADER_DIRECTORY")) { ispcHeaderDirectory = cmStrCat(this->LocalGenerator->GetBinaryDirectory(), '/', *prop); } std::string ispcHeader = cmStrCat(ispcHeaderDirectory, '/', ispcSource, *ispcSuffixProp); ispcHeader = this->ConvertToNinjaPath(ispcHeader); // Make sure ninja knows what command generates the header objBuild.ImplicitOuts.push_back(ispcHeader); // Make sure ninja knows how to clean the generated header this->GetGlobalGenerator()->AddAdditionalCleanFile(ispcHeader, config); auto ispcSuffixes = detail::ComputeISPCObjectSuffixes(this->GeneratorTarget); if (ispcSuffixes.size() > 1) { std::string rootObjectDir = this->GeneratorTarget->GetObjectDirectory(config); auto ispcSideEfffectObjects = detail::ComputeISPCExtraObjects( objectName, rootObjectDir, ispcSuffixes); for (auto sideEffect : ispcSideEfffectObjects) { sideEffect = this->ConvertToNinjaPath(sideEffect); objBuild.ImplicitOuts.emplace_back(sideEffect); this->GetGlobalGenerator()->AddAdditionalCleanFile(sideEffect, config); } } vars["ISPC_HEADER_FILE"] = this->GetLocalGenerator()->ConvertToOutputFormat( ispcHeader, cmOutputConverter::SHELL); } else { auto headers = this->GeneratorTarget->GetGeneratedISPCHeaders(config); if (!headers.empty()) { std::transform(headers.begin(), headers.end(), headers.begin(), this->MapToNinjaPath()); objBuild.OrderOnlyDeps.insert(objBuild.OrderOnlyDeps.end(), headers.begin(), headers.end()); } } if (language == "Swift") { this->EmitSwiftDependencyInfo(source, config); } else { this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), objBuild, commandLineLengthLimit); } if (cmValue objectOutputs = source->GetProperty("OBJECT_OUTPUTS")) { std::string evaluatedObjectOutputs = cmGeneratorExpression::Evaluate( *objectOutputs, this->LocalGenerator, config); if (!evaluatedObjectOutputs.empty()) { cmNinjaBuild build("phony"); build.Comment = "Additional output files."; build.Outputs = cmList{ evaluatedObjectOutputs }.data(); std::transform(build.Outputs.begin(), build.Outputs.end(), build.Outputs.begin(), this->MapToNinjaPath()); build.ExplicitDeps = objBuild.Outputs; this->GetGlobalGenerator()->WriteBuild( this->GetImplFileStream(fileConfig), build); } } } void cmNinjaTargetGenerator::WriteCxxModuleBmiBuildStatement( cmSourceFile const* source, const std::string& config, const std::string& fileConfig, bool firstForConfig) { std::string const language = source->GetLanguage(); if (language != "CXX"_s) { this->GetMakefile()->IssueMessage( MessageType::FATAL_ERROR, cmStrCat("Source file '", source->GetFullPath(), "' of target '", this->GetTargetName(), "' is a '", language, "' source but must be 'CXX' in order to have a BMI build " "statement generated.")); return; } std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(source); std::string const bmiDir = this->ConvertToNinjaPath( cmStrCat(this->GeneratorTarget->GetSupportDirectory(), this->GetGlobalGenerator()->ConfigDirectory(config))); std::string const bmiFileName = this->ConvertToNinjaPath(this->GetBmiFilePath(source, config)); std::string const bmiFileDir = cmSystemTools::GetFilenamePath(bmiFileName); int const commandLineLengthLimit = this->ForceResponseFile() ? -1 : 0; cmNinjaBuild bmiBuild( this->LanguageCompilerRule(language, config, WithScanning::Yes)); cmNinjaVars& vars = bmiBuild.Variables; vars["FLAGS"] = this->ComputeFlagsForObject(source, language, config, bmiFileName); vars["DEFINES"] = this->ComputeDefines(source, language, config); vars["INCLUDES"] = this->ComputeIncludes(source, language, config); if (this->GetMakefile()->GetSafeDefinition( cmStrCat("CMAKE_", language, "_DEPFILE_FORMAT")) != "msvc"_s) { bool replaceExt = false; if (!language.empty()) { std::string repVar = cmStrCat("CMAKE_", language, "_DEPFILE_EXTENSION_REPLACE"); replaceExt = this->Makefile->IsOn(repVar); } this->AddDepfileBinding( vars, replaceExt ? cmStrCat(bmiFileDir, '/', cmSystemTools::GetFilenameWithoutLastExtension(bmiFileName), ".d") : cmStrCat(bmiFileName, ".d")); } std::string d = this->GeneratorTarget->GetClangTidyExportFixesDirectory(language); if (!d.empty()) { this->GlobalCommonGenerator->AddClangTidyExportFixesDir(d); std::string fixesFile = this->GetClangTidyReplacementsFilePath(d, *source, config); this->GlobalCommonGenerator->AddClangTidyExportFixesFile(fixesFile); cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(fixesFile)); fixesFile = this->ConvertToNinjaPath(fixesFile); vars["CLANG_TIDY_EXPORT_FIXES"] = fixesFile; } this->SetMsvcTargetPdbVariable(vars, config); if (firstForConfig) { this->ExportObjectCompileCommand( language, sourceFilePath, bmiDir, bmiFileName, bmiFileDir, vars["FLAGS"], vars["DEFINES"], vars["INCLUDES"], vars["TARGET_COMPILE_PDB"], vars["TARGET_PDB"], config, WithScanning::Yes); } bmiBuild.Outputs.push_back(bmiFileName); bmiBuild.ExplicitDeps.push_back(sourceFilePath); std::vector depList; bmiBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config)); // For some cases we scan to dynamically discover dependencies. std::string modmapFormat; if (true) { std::string const modmapFormatVar = cmStrCat("CMAKE_", language, "_MODULE_MAP_FORMAT"); modmapFormat = this->Makefile->GetSafeDefinition(modmapFormatVar); } { bool const compilePPWithDefines = this->CompileWithDefines(language); std::string scanRuleName = this->LanguageScanRule(language, config); std::string ppFileName = cmStrCat(bmiFileName, ".ddi.i"); cmNinjaBuild ppBuild = GetScanBuildStatement( scanRuleName, ppFileName, false, compilePPWithDefines, true, bmiBuild, vars, bmiFileName, this); ScanningFiles scanningFiles; if (firstForConfig) { scanningFiles.ScanningOutput = cmStrCat(bmiFileName, ".ddi"); } this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(), ppBuild.Variables); this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), ppBuild, commandLineLengthLimit); std::string const dyndep = this->GetDyndepFilePath(language, config); bmiBuild.OrderOnlyDeps.push_back(dyndep); vars["dyndep"] = dyndep; if (!modmapFormat.empty()) { std::string ddModmapFile = cmStrCat(bmiFileName, ".modmap"); vars["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile; scanningFiles.ModuleMapFile = std::move(ddModmapFile); } if (!scanningFiles.IsEmpty()) { this->Configs[config].ScanningInfo[language].emplace_back(scanningFiles); } } this->EnsureParentDirectoryExists(bmiFileName); vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat( bmiDir, cmOutputConverter::SHELL); vars["OBJECT_FILE_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat( bmiFileDir, cmOutputConverter::SHELL); this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(), vars); bmiBuild.RspFile = cmStrCat(bmiFileName, ".rsp"); this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), bmiBuild, commandLineLengthLimit); } void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( std::vector const& sources, std::string const& config, std::string const& fileConfig, bool firstForConfig) { // Swift sources are compiled as a module, not individually like with C/C++. // Flags, header search paths, and definitions are passed to the entire // module build, but we still need to emit compile-commands for each source // file in order to support CMAKE_EXPORT_COMPILE_COMMANDS. // In whole-module mode, with a single thread, the Swift compiler will // only emit a single object file, but if more than one thread is specified, // or building in other modes, the compiler will emit multiple object files. // When building a single-output, we do not provide an output-file-map (OFM), // and instead pass `-o` to tell the compiler where to write the object. // When building multiple outputs, we provide an OFM to tell the compiler // where to put each object. // // // Per-Target (module): // - Flags // - Definitions // - Include paths // - (single-output) output object filename // - Swiftmodule // // Per-File: // - compile-command // - (multi-output) OFM data // - (multi-output) output object filename // // Note: Due to the differences in the build models, we are only able to // build the object build-graph if we know what mode the target is built in. // For that, we need the "NEW" behavior for CMP0157. Otherwise, we have to // fall back on the old "linker" build. Otherwise, this should be // indistinguishable from the old behavior. if (sources.empty()) { return; } cmSwiftCompileMode compileMode; if (cm::optional optionalCompileMode = this->LocalGenerator->GetSwiftCompileMode(this->GeneratorTarget, config)) { compileMode = *optionalCompileMode; } else { // CMP0157 is not NEW, bailing early! return; } std::string const language = "Swift"; std::string const objectDir = this->ConvertToNinjaPath( cmStrCat(this->GeneratorTarget->GetSupportDirectory(), this->GetGlobalGenerator()->ConfigDirectory(config))); cmGeneratorTarget const& target = *this->GeneratorTarget; cmNinjaBuild objBuild( this->LanguageCompilerRule(language, config, WithScanning::No)); cmNinjaVars& vars = objBuild.Variables; // The swift toolchain leaves outputs untouched if there are no meaningful // changes to input files (e.g. addition of a comment). vars.emplace("restat", "1"); std::string const moduleName = target.GetSwiftModuleName(); std::string const moduleFilepath = this->ConvertToNinjaPath(target.GetSwiftModulePath(config)); vars.emplace("description", cmStrCat("Building Swift Module '", moduleName, "' with ", sources.size(), sources.size() == 1 ? " source" : " sources")); bool const isSingleOutput = [this, compileMode]() -> bool { bool isMultiThread = false; if (cmValue numThreadStr = this->GetMakefile()->GetDefinition("CMAKE_Swift_NUM_THREADS")) { unsigned long numThreads; cmStrToULong(*numThreadStr, &numThreads); // numThreads == 1 is multi-threaded according to swiftc isMultiThread = numThreads > 0; } return !isMultiThread && compileMode == cmSwiftCompileMode::Wholemodule; }(); // Without `-emit-library` or `-emit-executable`, targets with a single // source file parse as a Swift script instead of like normal source. For // non-executable targets, append this to ensure that they are parsed like a // normal source. if (target.GetType() != cmStateEnums::EXECUTABLE) { this->LocalGenerator->AppendFlags(vars["FLAGS"], "-parse-as-library"); } if (target.GetType() == cmStateEnums::STATIC_LIBRARY) { this->LocalGenerator->AppendFlags(vars["FLAGS"], "-static"); } // Does this swift target emit a module file for importing into other // targets? auto isImportableTarget = [](cmGeneratorTarget const& tgt) -> bool { // Everything except for executables that don't export anything should emit // some way to import them. if (tgt.GetType() == cmStateEnums::EXECUTABLE) { return tgt.IsExecutableWithExports(); } return true; }; // Swift modules only make sense to emit from things that can be imported. // Executables that don't export symbols can't be imported, so don't try to // emit a swiftmodule for them. It will break. if (isImportableTarget(target)) { std::string const emitModuleFlag = "-emit-module"; std::string const modulePathFlag = "-emit-module-path"; this->LocalGenerator->AppendFlags( vars["FLAGS"], { emitModuleFlag, modulePathFlag, moduleFilepath }); objBuild.Outputs.push_back(moduleFilepath); } this->LocalGenerator->AppendFlags(vars["FLAGS"], cmStrCat("-module-name ", moduleName)); if (target.GetType() != cmStateEnums::EXECUTABLE) { std::string const libraryLinkNameFlag = "-module-link-name"; std::string const libraryLinkName = this->GetGeneratorTarget()->GetLibraryNames(config).Base; this->LocalGenerator->AppendFlags( vars["FLAGS"], cmStrCat(libraryLinkNameFlag, ' ', libraryLinkName)); } this->LocalGenerator->AppendFlags(vars["FLAGS"], this->GetFlags(language, config)); vars["DEFINES"] = this->GetDefines(language, config); vars["INCLUDES"] = this->GetIncludes(language, config); // target-level object filename std::string const targetObjectFilename = this->ConvertToNinjaPath(cmStrCat( objectDir, '/', moduleName, this->GetGlobalGenerator()->GetLanguageOutputExtension(language))); objBuild.RspFile = cmStrCat(targetObjectFilename, ".swift.rsp"); if (isSingleOutput) { this->LocalGenerator->AppendFlags(vars["FLAGS"], cmStrCat("-o ", targetObjectFilename)); objBuild.Outputs.push_back(targetObjectFilename); this->Configs[config].Objects.push_back(targetObjectFilename); } for (cmSourceFile const* sf : sources) { // Add dependency to object build on each source file std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(sf); objBuild.ExplicitDeps.push_back(sourceFilePath); if (!isSingleOutput) { // Object outputs std::string const objectFilepath = this->ConvertToNinjaPath(this->GetObjectFilePath(sf, config)); this->EnsureParentDirectoryExists(objectFilepath); objBuild.Outputs.push_back(objectFilepath); this->Configs[config].Objects.push_back(objectFilepath); // Add OFM data this->EmitSwiftDependencyInfo(sf, config); } } if (!isSingleOutput) { this->GenerateSwiftOutputFileMap(config, vars["FLAGS"]); } if (firstForConfig) { this->ExportSwiftObjectCompileCommand( sources, targetObjectFilename, vars["FLAGS"], vars["DEFINES"], vars["INCLUDES"], config, isSingleOutput); } for (cmTargetDepend const& dep : this->GetGlobalGenerator()->GetTargetDirectDepends(&target)) { if (!dep->IsLanguageUsed("Swift", config)) { continue; } // If the dependency emits a swiftmodule, add a dependency edge on that // swiftmodule to the ninja build graph. if (isImportableTarget(*dep)) { std::string const depModuleFilepath = this->ConvertToNinjaPath(dep->GetSwiftModulePath(config)); objBuild.ImplicitDeps.push_back(depModuleFilepath); } } objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config)); // Write object build this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), objBuild, this->ForceResponseFile() ? -1 : 0); } void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang, const std::string& config) { Json::Value tdi(Json::objectValue); tdi["language"] = lang; tdi["compiler-id"] = this->Makefile->GetSafeDefinition( cmStrCat("CMAKE_", lang, "_COMPILER_ID")); tdi["compiler-simulate-id"] = this->Makefile->GetSafeDefinition( cmStrCat("CMAKE_", lang, "_SIMULATE_ID")); tdi["compiler-frontend-variant"] = this->Makefile->GetSafeDefinition( cmStrCat("CMAKE_", lang, "_COMPILER_FRONTEND_VARIANT")); std::string mod_dir; if (lang == "Fortran") { mod_dir = this->GeneratorTarget->GetFortranModuleDirectory( this->Makefile->GetHomeOutputDirectory()); } else if (lang == "CXX") { mod_dir = this->GetGlobalGenerator()->ExpandCFGIntDir( cmSystemTools::CollapseFullPath(this->GeneratorTarget->ObjectDirectory), config); } if (mod_dir.empty()) { mod_dir = this->Makefile->GetCurrentBinaryDirectory(); } tdi["module-dir"] = mod_dir; if (lang == "Fortran") { tdi["submodule-sep"] = this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP"); tdi["submodule-ext"] = this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT"); } else if (lang == "CXX") { // No extra information necessary. } tdi["dir-cur-bld"] = this->Makefile->GetCurrentBinaryDirectory(); tdi["dir-cur-src"] = this->Makefile->GetCurrentSourceDirectory(); tdi["dir-top-bld"] = this->Makefile->GetHomeOutputDirectory(); tdi["dir-top-src"] = this->Makefile->GetHomeDirectory(); Json::Value& tdi_include_dirs = tdi["include-dirs"] = Json::arrayValue; std::vector includes; this->LocalGenerator->GetIncludeDirectories(includes, this->GeneratorTarget, lang, config); for (std::string const& i : includes) { // Convert the include directories the same way we do for -I flags. // See upstream ninja issue 1251. tdi_include_dirs.append(this->ConvertToNinjaPath(i)); } Json::Value& tdi_linked_target_dirs = tdi["linked-target-dirs"] = Json::arrayValue; auto const linked_directories = this->GetLinkedTargetDirectories(lang, config); for (std::string const& l : linked_directories.Direct) { tdi_linked_target_dirs.append(l); } Json::Value& tdi_forward_modules_from_target_dirs = tdi["forward-modules-from-target-dirs"] = Json::arrayValue; for (std::string const& l : linked_directories.Forward) { tdi_forward_modules_from_target_dirs.append(l); } cmDyndepGeneratorCallbacks cb; cb.ObjectFilePath = [this](cmSourceFile const* sf, std::string const& cnf) { return this->GetObjectFilePath(sf, cnf); }; cb.BmiFilePath = [this](cmSourceFile const* sf, std::string const& cnf) { return this->GetBmiFilePath(sf, cnf); }; #if !defined(CMAKE_BOOTSTRAP) cmDyndepCollation::AddCollationInformation(tdi, this->GeneratorTarget, config, cb); #endif std::string const tdin = this->GetTargetDependInfoPath(lang, config); cmGeneratedFileStream tdif(tdin); tdif << tdi; } void cmNinjaTargetGenerator::EmitSwiftDependencyInfo( cmSourceFile const* source, const std::string& config) { std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(source); std::string const objectFilePath = this->ConvertToNinjaPath(this->GetObjectFilePath(source, config)); std::string const swiftDepsPath = [source, objectFilePath]() -> std::string { if (cmValue name = source->GetProperty("Swift_DEPENDENCIES_FILE")) { return *name; } return cmStrCat(objectFilePath, ".swiftdeps"); }(); std::string const swiftDiaPath = [source, objectFilePath]() -> std::string { if (cmValue name = source->GetProperty("Swift_DIAGNOSTICS_FILE")) { return *name; } return cmStrCat(objectFilePath, ".dia"); }(); std::string const makeDepsPath = [this, source, config]() -> std::string { cmLocalNinjaGenerator const* local = this->GetLocalGenerator(); std::string const objectFileName = this->ConvertToNinjaPath(this->GetObjectFilePath(source, config)); std::string const objectFileDir = cmSystemTools::GetFilenamePath(objectFileName); if (this->Makefile->IsOn("CMAKE_Swift_DEPFLE_EXTNSION_REPLACE")) { std::string dependFileName = cmStrCat( cmSystemTools::GetFilenameWithoutLastExtension(objectFileName), ".d"); return local->ConvertToOutputFormat( cmStrCat(objectFileDir, '/', dependFileName), cmOutputConverter::SHELL); } return local->ConvertToOutputFormat(cmStrCat(objectFileName, ".d"), cmOutputConverter::SHELL); }(); // build the source file mapping // https://github.com/apple/swift/blob/master/docs/Driver.md#output-file-maps Json::Value entry = Json::Value(Json::objectValue); entry["object"] = objectFilePath; entry["dependencies"] = makeDepsPath; entry["swift-dependencies"] = swiftDepsPath; entry["diagnostics"] = swiftDiaPath; this->Configs[config].SwiftOutputMap[sourceFilePath] = entry; } void cmNinjaTargetGenerator::ExportObjectCompileCommand( std::string const& language, std::string const& sourceFileName, std::string const& objectDir, std::string const& objectFileName, std::string const& objectFileDir, std::string const& flags, std::string const& defines, std::string const& includes, std::string const& targetCompilePdb, std::string const& targetPdb, std::string const& outputConfig, WithScanning withScanning) { if (!this->GeneratorTarget->GetPropertyAsBool("EXPORT_COMPILE_COMMANDS")) { return; } cmRulePlaceholderExpander::RuleVariables compileObjectVars; compileObjectVars.Language = language.c_str(); std::string escapedSourceFileName = sourceFileName; if (!cmSystemTools::FileIsFullPath(sourceFileName)) { escapedSourceFileName = cmSystemTools::CollapseFullPath(escapedSourceFileName, this->GetGlobalGenerator() ->GetCMakeInstance() ->GetHomeOutputDirectory()); } escapedSourceFileName = this->LocalGenerator->ConvertToOutputFormat( escapedSourceFileName, cmOutputConverter::SHELL); std::string fullFlags = flags; if (withScanning == WithScanning::Yes) { std::string const modmapFormatVar = cmStrCat("CMAKE_", language, "_MODULE_MAP_FORMAT"); std::string const modmapFormat = this->Makefile->GetSafeDefinition(modmapFormatVar); if (!modmapFormat.empty()) { std::string modmapFlags = this->GetMakefile()->GetRequiredDefinition( cmStrCat("CMAKE_", language, "_MODULE_MAP_FLAG")); // XXX(modmap): If changing this path construction, change // `cmGlobalNinjaGenerator::WriteDyndep` and // `cmNinjaTargetGenerator::WriteObjectBuildStatement` to expect the // corresponding file path. cmSystemTools::ReplaceString(modmapFlags, "", cmStrCat(objectFileName, ".modmap")); fullFlags += cmStrCat(' ', modmapFlags); } } compileObjectVars.Source = escapedSourceFileName.c_str(); compileObjectVars.Object = objectFileName.c_str(); compileObjectVars.ObjectDir = objectDir.c_str(); compileObjectVars.ObjectFileDir = objectFileDir.c_str(); compileObjectVars.Flags = fullFlags.c_str(); compileObjectVars.Defines = defines.c_str(); compileObjectVars.Includes = includes.c_str(); compileObjectVars.TargetCompilePDB = targetCompilePdb.c_str(); compileObjectVars.TargetPDB = targetPdb.c_str(); // Rule for compiling object file. std::string cudaCompileMode; if (language == "CUDA") { if (this->GeneratorTarget->GetPropertyAsBool( "CUDA_SEPARABLE_COMPILATION")) { const std::string& rdcFlag = this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_RDC_FLAG"); cudaCompileMode = cmStrCat(cudaCompileMode, rdcFlag, " "); } static std::array const compileModes{ { "PTX"_s, "CUBIN"_s, "FATBIN"_s, "OPTIX"_s } }; bool useNormalCompileMode = true; for (cm::string_view mode : compileModes) { auto propName = cmStrCat("CUDA_", mode, "_COMPILATION"); auto defName = cmStrCat("_CMAKE_CUDA_", mode, "_FLAG"); if (this->GeneratorTarget->GetPropertyAsBool(propName)) { const std::string& flag = this->Makefile->GetRequiredDefinition(defName); cudaCompileMode = cmStrCat(cudaCompileMode, flag); useNormalCompileMode = false; break; } } if (useNormalCompileMode) { const std::string& wholeFlag = this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_WHOLE_FLAG"); cudaCompileMode = cmStrCat(cudaCompileMode, wholeFlag); } compileObjectVars.CudaCompileMode = cudaCompileMode.c_str(); } const std::string cmdVar = cmStrCat("CMAKE_", language, "_COMPILE_OBJECT"); const std::string& compileCmd = this->Makefile->GetRequiredDefinition(cmdVar); cmList compileCmds(compileCmd); auto rulePlaceholderExpander = this->GetLocalGenerator()->CreateRulePlaceholderExpander(); for (auto& i : compileCmds) { // no launcher for CMAKE_EXPORT_COMPILE_COMMANDS rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), i, compileObjectVars); } std::string cmdLine = this->GetLocalGenerator()->BuildCommandLine( compileCmds, outputConfig, outputConfig); this->GetGlobalGenerator()->AddCXXCompileCommand(cmdLine, sourceFileName, objectFileName); } void cmNinjaTargetGenerator::ExportSwiftObjectCompileCommand( std::vector const& moduleSourceFiles, std::string const& moduleObjectFilename, std::string const& flags, std::string const& defines, std::string const& includes, std::string const& outputConfig, bool singleOutput) { if (!this->GeneratorTarget->GetPropertyAsBool("EXPORT_COMPILE_COMMANDS")) { return; } auto escapeSourceFileName = [this](std::string srcFilename) -> std::string { if (!cmSystemTools::FileIsFullPath(srcFilename)) { srcFilename = cmSystemTools::CollapseFullPath(srcFilename, this->GetGlobalGenerator() ->GetCMakeInstance() ->GetHomeOutputDirectory()); } return this->LocalGenerator->ConvertToOutputFormat( srcFilename, cmOutputConverter::SHELL); }; auto escapedModuleObjectFilename = this->ConvertToNinjaPath(moduleObjectFilename); cmRulePlaceholderExpander::RuleVariables compileObjectVars; compileObjectVars.Language = "Swift"; compileObjectVars.Flags = flags.c_str(); compileObjectVars.Defines = defines.c_str(); compileObjectVars.Includes = includes.c_str(); // Build up the list of source files in the module std::vector filenames; filenames.reserve(moduleSourceFiles.size()); for (cmSourceFile const* sf : moduleSourceFiles) { filenames.emplace_back( escapeSourceFileName(this->GetCompiledSourceNinjaPath(sf))); } // Note that `escapedSourceFilenames` must remain alive until the // compileObjectVars is consumed or Source will be a dangling pointer. std::string const escapedSourceFilenames = cmJoin(filenames, " "); compileObjectVars.Source = escapedSourceFilenames.c_str(); std::string const& compileCommand = this->Makefile->GetRequiredDefinition("CMAKE_Swift_COMPILE_OBJECT"); cmList compileCmds(compileCommand); auto rulePlaceholderExpander = this->GetLocalGenerator()->CreateRulePlaceholderExpander(); for (cmSourceFile const* sf : moduleSourceFiles) { std::string const sourceFilename = this->GetCompiledSourceNinjaPath(sf); std::string objectFilename = escapedModuleObjectFilename; if (!singleOutput) { // If it's not single-output, each source file gets a separate object objectFilename = this->ConvertToNinjaPath(this->GetObjectFilePath(sf, outputConfig)); } compileObjectVars.Objects = objectFilename.c_str(); for (std::string& cmd : compileCmds) { rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), cmd, compileObjectVars); } std::string commandLine = this->GetLocalGenerator()->BuildCommandLine( compileCmds, outputConfig, outputConfig); this->GetGlobalGenerator()->AddCXXCompileCommand( commandLine, sourceFilename, objectFilename); } } void cmNinjaTargetGenerator::AdditionalCleanFiles(const std::string& config) { if (cmValue prop_value = this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) { cmLocalNinjaGenerator* lg = this->LocalGenerator; cmList cleanFiles(cmGeneratorExpression::Evaluate(*prop_value, lg, config, this->GeneratorTarget)); std::string const& binaryDir = lg->GetCurrentBinaryDirectory(); cmGlobalNinjaGenerator* gg = lg->GetGlobalNinjaGenerator(); for (auto const& cleanFile : cleanFiles) { // Support relative paths gg->AddAdditionalCleanFile( cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config); } } } cmNinjaDeps cmNinjaTargetGenerator::GetObjects(const std::string& config) const { auto const it = this->Configs.find(config); if (it != this->Configs.end()) { return it->second.Objects; } return {}; } void cmNinjaTargetGenerator::EnsureDirectoryExists( const std::string& path) const { if (cmSystemTools::FileIsFullPath(path)) { cmSystemTools::MakeDirectory(path); } else { cmGlobalNinjaGenerator* gg = this->GetGlobalGenerator(); std::string fullPath = gg->GetCMakeInstance()->GetHomeOutputDirectory(); // Also ensures there is a trailing slash. gg->StripNinjaOutputPathPrefixAsSuffix(fullPath); fullPath += path; cmSystemTools::MakeDirectory(fullPath); } } void cmNinjaTargetGenerator::EnsureParentDirectoryExists( const std::string& path) const { this->EnsureDirectoryExists(cmSystemTools::GetParentDirectory(path)); } void cmNinjaTargetGenerator::MacOSXContentGeneratorType::operator()( cmSourceFile const& source, const char* pkgloc, const std::string& config) { // Skip OS X content when not building a Framework or Bundle. if (!this->Generator->GetGeneratorTarget()->IsBundleOnApple()) { return; } std::string macdir = this->Generator->OSXBundleGenerator->InitMacOSXContentDirectory(pkgloc, config); // Reject files that collide with files from the Ninja file's native config. if (config != this->FileConfig) { std::string nativeMacdir = this->Generator->OSXBundleGenerator->InitMacOSXContentDirectory( pkgloc, this->FileConfig); if (macdir == nativeMacdir) { return; } } // Get the input file location. std::string input = source.GetFullPath(); input = this->Generator->GetGlobalGenerator()->ConvertToNinjaPath(input); // Get the output file location. std::string output = cmStrCat(macdir, '/', cmSystemTools::GetFilenameName(input)); output = this->Generator->GetGlobalGenerator()->ConvertToNinjaPath(output); // Write a build statement to copy the content into the bundle. this->Generator->GetGlobalGenerator()->WriteMacOSXContentBuild( input, output, this->FileConfig); // Add as a dependency to the target so that it gets called. this->Generator->Configs[config].ExtraFiles.push_back(std::move(output)); } void cmNinjaTargetGenerator::AddDepfileBinding(cmNinjaVars& vars, std::string depfile) const { std::string depfileForShell = this->GetLocalGenerator()->ConvertToOutputFormat(depfile, cmOutputConverter::SHELL); if (depfile != depfileForShell) { vars["depfile"] = std::move(depfile); } vars["DEP_FILE"] = std::move(depfileForShell); } void cmNinjaTargetGenerator::RemoveDepfileBinding(cmNinjaVars& vars) const { vars.erase("DEP_FILE"); vars.erase("depfile"); } void cmNinjaTargetGenerator::addPoolNinjaVariable( const std::string& pool_property, cmGeneratorTarget* target, cmNinjaVars& vars) { cmValue pool = target->GetProperty(pool_property); if (pool) { vars["pool"] = *pool; } } bool cmNinjaTargetGenerator::ForceResponseFile() { static std::string const forceRspFile = "CMAKE_NINJA_FORCE_RESPONSE_FILE"; return (this->GetMakefile()->IsDefinitionSet(forceRspFile) || cmSystemTools::HasEnv(forceRspFile)); }