/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmGlobalNinjaGenerator.h" #include "cm_jsoncpp_reader.h" #include "cm_jsoncpp_value.h" #include "cm_jsoncpp_writer.h" #include "cmsys/FStream.hxx" #include #include #include #include #include #include "cm_memory.hxx" #include "cmAlgorithms.h" #include "cmDocumentationEntry.h" #include "cmFortranParser.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpressionEvaluationFile.h" #include "cmGeneratorTarget.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmLocalNinjaGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmNinjaLinkLineComputer.h" #include "cmOutputConverter.h" #include "cmRange.h" #include "cmState.h" #include "cmStateDirectory.h" #include "cmStateSnapshot.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetDepend.h" #include "cmVersion.h" #include "cmake.h" class cmLinkLineComputer; const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja"; const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE = "rules.ninja"; const char* cmGlobalNinjaGenerator::INDENT = " "; #ifdef _WIN32 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd ."; #else std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":"; #endif void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count) { for (int i = 0; i < count; ++i) { os << cmGlobalNinjaGenerator::INDENT; } } void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os) { os << "# ======================================" "=======================================\n"; } void cmGlobalNinjaGenerator::WriteComment(std::ostream& os, const std::string& comment) { if (comment.empty()) { return; } std::string::size_type lpos = 0; std::string::size_type rpos; os << "\n#############################################\n"; while ((rpos = comment.find('\n', lpos)) != std::string::npos) { os << "# " << comment.substr(lpos, rpos - lpos) << "\n"; lpos = rpos + 1; } os << "# " << comment.substr(lpos) << "\n\n"; } cmLinkLineComputer* cmGlobalNinjaGenerator::CreateLinkLineComputer( cmOutputConverter* outputConverter, cmStateDirectory const& /* stateDir */) const { return new cmNinjaLinkLineComputer( outputConverter, this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this); } std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name) { // Ninja rule names must match "[a-zA-Z0-9_.-]+". Use ".xx" to encode // "." and all invalid characters as hexadecimal. std::string encoded; for (char i : name) { if (isalnum(i) || i == '_' || i == '-') { encoded += i; } else { char buf[16]; sprintf(buf, ".%02x", static_cast(i)); encoded += buf; } } return encoded; } std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit) { std::string result = lit; cmSystemTools::ReplaceString(result, "$", "$$"); cmSystemTools::ReplaceString(result, "\n", "$\n"); return result; } std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path) { std::string result = path; #ifdef _WIN32 if (this->IsGCCOnWindows()) std::replace(result.begin(), result.end(), '\\', '/'); else std::replace(result.begin(), result.end(), '/', '\\'); #endif result = EncodeLiteral(result); cmSystemTools::ReplaceString(result, " ", "$ "); cmSystemTools::ReplaceString(result, ":", "$:"); return result; } void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os, cmNinjaBuild const& build, int cmdLineLimit, bool* usedResponseFile) { // Make sure there is a rule. if (build.Rule.empty()) { cmSystemTools::Error("No rule for WriteBuild! called with comment: " + build.Comment); return; } // Make sure there is at least one output file. if (build.Outputs.empty()) { cmSystemTools::Error( "No output files for WriteBuild! called with comment: " + build.Comment); return; } cmGlobalNinjaGenerator::WriteComment(os, build.Comment); // Write output files. std::string buildStr("build"); { // Write explicit outputs for (std::string const& output : build.Outputs) { buildStr += " " + EncodePath(output); if (this->ComputingUnknownDependencies) { this->CombinedBuildOutputs.insert(output); } } // Write implicit outputs if (!build.ImplicitOuts.empty()) { buildStr += " |"; for (std::string const& implicitOut : build.ImplicitOuts) { buildStr += " " + EncodePath(implicitOut); } } buildStr += ":"; // Write the rule. buildStr += " "; buildStr += build.Rule; } std::string arguments; { // TODO: Better formatting for when there are multiple input/output files. // Write explicit dependencies. for (std::string const& explicitDep : build.ExplicitDeps) { arguments += " " + EncodePath(explicitDep); } // Write implicit dependencies. if (!build.ImplicitDeps.empty()) { arguments += " |"; for (std::string const& implicitDep : build.ImplicitDeps) { arguments += " " + EncodePath(implicitDep); } } // Write order-only dependencies. if (!build.OrderOnlyDeps.empty()) { arguments += " ||"; for (std::string const& orderOnlyDep : build.OrderOnlyDeps) { arguments += " " + EncodePath(orderOnlyDep); } } arguments += "\n"; } // Write the variables bound to this build statement. std::string assignments; { std::ostringstream variable_assignments; for (auto const& variable : build.Variables) { cmGlobalNinjaGenerator::WriteVariable( variable_assignments, variable.first, variable.second, "", 1); } // check if a response file rule should be used assignments = variable_assignments.str(); bool useResponseFile = false; if (cmdLineLimit < 0 || (cmdLineLimit > 0 && (arguments.size() + buildStr.size() + assignments.size() + 1000) > static_cast(cmdLineLimit))) { variable_assignments.str(std::string()); cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE", build.RspFile, "", 1); assignments += variable_assignments.str(); useResponseFile = true; } if (usedResponseFile) { *usedResponseFile = useResponseFile; } } os << buildStr << arguments << assignments << "\n"; } void cmGlobalNinjaGenerator::AddCustomCommandRule() { cmNinjaRule rule("CUSTOM_COMMAND"); rule.Command = "$COMMAND"; rule.Description = "$DESC"; rule.Comment = "Rule for running custom commands."; this->AddRule(rule); } void cmGlobalNinjaGenerator::WriteCustomCommandBuild( const std::string& command, const std::string& description, const std::string& comment, const std::string& depfile, const std::string& job_pool, bool uses_terminal, bool restat, const cmNinjaDeps& outputs, const cmNinjaDeps& explicitDeps, const cmNinjaDeps& orderOnlyDeps) { this->AddCustomCommandRule(); { cmNinjaBuild build("CUSTOM_COMMAND"); build.Comment = comment; build.Outputs = outputs; build.ExplicitDeps = explicitDeps; build.OrderOnlyDeps = orderOnlyDeps; cmNinjaVars& vars = build.Variables; { std::string cmd = command; // NOLINT(*) #ifdef _WIN32 if (cmd.empty()) // TODO Shouldn't an empty command be handled by ninja? cmd = "cmd.exe /c"; #endif vars["COMMAND"] = std::move(cmd); } vars["DESC"] = EncodeLiteral(description); if (restat) { vars["restat"] = "1"; } if (uses_terminal && SupportsConsolePool()) { vars["pool"] = "console"; } else if (!job_pool.empty()) { vars["pool"] = job_pool; } if (!depfile.empty()) { vars["depfile"] = depfile; } this->WriteBuild(*this->BuildFileStream, build); } if (this->ComputingUnknownDependencies) { // we need to track every dependency that comes in, since we are trying // to find dependencies that are side effects of build commands for (std::string const& dep : explicitDeps) { this->CombinedCustomCommandExplicitDependencies.insert(dep); } } } void cmGlobalNinjaGenerator::AddMacOSXContentRule() { cmNinjaRule rule("COPY_OSX_CONTENT"); rule.Command = CMakeCmd() + " -E copy $in $out"; rule.Description = "Copying OS X Content $out"; rule.Comment = "Rule for copying OS X bundle content file."; this->AddRule(rule); } void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input, std::string output) { this->AddMacOSXContentRule(); { cmNinjaBuild build("COPY_OSX_CONTENT"); build.Outputs.push_back(std::move(output)); build.ExplicitDeps.push_back(std::move(input)); this->WriteBuild(*this->BuildFileStream, build); } } void cmGlobalNinjaGenerator::WriteRule(std::ostream& os, cmNinjaRule const& rule) { // -- Parameter checks // Make sure the rule has a name. if (rule.Name.empty()) { cmSystemTools::Error("No name given for WriteRule! called with comment: " + rule.Comment); return; } // Make sure a command is given. if (rule.Command.empty()) { cmSystemTools::Error( "No command given for WriteRule! called with comment: " + rule.Comment); return; } // Make sure response file content is given if (!rule.RspFile.empty() && rule.RspContent.empty()) { cmSystemTools::Error("rspfile but no rspfile_content given for WriteRule! " "called with comment: " + rule.Comment); return; } // -- Write rule // Write rule intro cmGlobalNinjaGenerator::WriteComment(os, rule.Comment); os << "rule " << rule.Name << '\n'; // Write rule key/value pairs auto writeKV = [&os](const char* key, std::string const& value) { if (!value.empty()) { cmGlobalNinjaGenerator::Indent(os, 1); os << key << " = " << value << '\n'; } }; writeKV("depfile", rule.DepFile); writeKV("deps", rule.DepType); writeKV("command", rule.Command); writeKV("description", rule.Description); if (!rule.RspFile.empty()) { writeKV("rspfile", rule.RspFile); writeKV("rspfile_content", rule.RspContent); } writeKV("restat", rule.Restat); if (rule.Generator) { writeKV("generator", "1"); } // Finish rule os << '\n'; } void cmGlobalNinjaGenerator::WriteVariable(std::ostream& os, const std::string& name, const std::string& value, const std::string& comment, int indent) { // Make sure we have a name. if (name.empty()) { cmSystemTools::Error("No name given for WriteVariable! called " "with comment: " + comment); return; } // Do not add a variable if the value is empty. std::string val = cmSystemTools::TrimWhitespace(value); if (val.empty()) { return; } cmGlobalNinjaGenerator::WriteComment(os, comment); cmGlobalNinjaGenerator::Indent(os, indent); os << name << " = " << val << "\n"; } void cmGlobalNinjaGenerator::WriteInclude(std::ostream& os, const std::string& filename, const std::string& comment) { cmGlobalNinjaGenerator::WriteComment(os, comment); os << "include " << filename << "\n"; } void cmGlobalNinjaGenerator::WriteDefault(std::ostream& os, const cmNinjaDeps& targets, const std::string& comment) { cmGlobalNinjaGenerator::WriteComment(os, comment); os << "default"; for (std::string const& target : targets) { os << " " << target; } os << "\n"; } cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm) : cmGlobalCommonGenerator(cm) { #ifdef _WIN32 cm->GetState()->SetWindowsShell(true); #endif // // Ninja is not ported to non-Unix OS yet. // this->ForceUnixPaths = true; this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake"; } // Virtual public methods. cmLocalGenerator* cmGlobalNinjaGenerator::CreateLocalGenerator(cmMakefile* mf) { return new cmLocalNinjaGenerator(this, mf); } codecvt::Encoding cmGlobalNinjaGenerator::GetMakefileEncoding() const { #ifdef _WIN32 // Ninja on Windows does not support non-ANSI characters. // https://github.com/ninja-build/ninja/issues/1195 return codecvt::ANSI; #else // No encoding conversion needed on other platforms. return codecvt::None; #endif } void cmGlobalNinjaGenerator::GetDocumentation(cmDocumentationEntry& entry) { entry.Name = cmGlobalNinjaGenerator::GetActualName(); entry.Brief = "Generates build.ninja files."; } // Implemented in all cmGlobaleGenerator sub-classes. // Used in: // Source/cmLocalGenerator.cxx // Source/cmake.cxx void cmGlobalNinjaGenerator::Generate() { // Check minimum Ninja version. if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion.c_str(), RequiredNinjaVersion().c_str())) { std::ostringstream msg; msg << "The detected version of Ninja (" << this->NinjaVersion; msg << ") is less than the version of Ninja required by CMake ("; msg << cmGlobalNinjaGenerator::RequiredNinjaVersion() << ")."; this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, msg.str()); return; } if (!this->OpenBuildFileStream()) { return; } if (!this->OpenRulesFileStream()) { return; } this->TargetDependsClosures.clear(); this->InitOutputPathPrefix(); this->TargetAll = this->NinjaOutputPath("all"); this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt"); this->PolicyCMP0058 = this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus( cmPolicies::CMP0058); this->ComputingUnknownDependencies = (this->PolicyCMP0058 == cmPolicies::OLD || this->PolicyCMP0058 == cmPolicies::WARN); this->cmGlobalGenerator::Generate(); this->WriteAssumedSourceDependencies(); this->WriteTargetAliases(*this->BuildFileStream); this->WriteFolderTargets(*this->BuildFileStream); this->WriteUnknownExplicitDependencies(*this->BuildFileStream); this->WriteBuiltinTargets(*this->BuildFileStream); if (cmSystemTools::GetErrorOccuredFlag()) { this->RulesFileStream->setstate(std::ios::failbit); this->BuildFileStream->setstate(std::ios::failbit); } this->CloseCompileCommandsStream(); this->CloseRulesFileStream(); this->CloseBuildFileStream(); } bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf) { if (!this->cmGlobalGenerator::FindMakeProgram(mf)) { return false; } if (const char* ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) { this->NinjaCommand = ninjaCommand; std::vector command; command.push_back(this->NinjaCommand); command.emplace_back("--version"); std::string version; std::string error; if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr, nullptr, cmSystemTools::OUTPUT_NONE)) { mf->IssueMessage(MessageType::FATAL_ERROR, "Running\n '" + cmJoin(command, "' '") + "'\n" "failed with:\n " + error); cmSystemTools::SetFatalErrorOccured(); return false; } this->NinjaVersion = cmSystemTools::TrimWhitespace(version); this->CheckNinjaFeatures(); } return true; } void cmGlobalNinjaGenerator::CheckNinjaFeatures() { this->NinjaSupportsConsolePool = !cmSystemTools::VersionCompare( cmSystemTools::OP_LESS, this->NinjaVersion.c_str(), RequiredNinjaVersionForConsolePool().c_str()); this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare( cmSystemTools::OP_LESS, this->NinjaVersion.c_str(), cmGlobalNinjaGenerator::RequiredNinjaVersionForImplicitOuts().c_str()); this->NinjaSupportsManifestRestat = !cmSystemTools::VersionCompare( cmSystemTools::OP_LESS, this->NinjaVersion.c_str(), RequiredNinjaVersionForManifestRestat().c_str()); this->NinjaSupportsMultilineDepfile = !cmSystemTools::VersionCompare( cmSystemTools::OP_LESS, this->NinjaVersion.c_str(), RequiredNinjaVersionForMultilineDepfile().c_str()); { // Our ninja branch adds ".dyndep-#" to its version number, // where '#' is a feature-specific version number. Extract it. static std::string const k_DYNDEP_ = ".dyndep-"; std::string::size_type pos = this->NinjaVersion.find(k_DYNDEP_); if (pos != std::string::npos) { const char* fv = &this->NinjaVersion[pos + k_DYNDEP_.size()]; cmSystemTools::StringToULong(fv, &this->NinjaSupportsDyndeps); } } } bool cmGlobalNinjaGenerator::CheckLanguages( std::vector const& languages, cmMakefile* mf) const { if (std::find(languages.begin(), languages.end(), "Fortran") != languages.end()) { return this->CheckFortran(mf); } return true; } bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const { if (this->NinjaSupportsDyndeps == 1) { return true; } std::ostringstream e; if (this->NinjaSupportsDyndeps == 0) { /* clang-format off */ e << "The Ninja generator does not support Fortran using Ninja version\n" " " + this->NinjaVersion + "\n" "due to lack of required features. " "Kitware has implemented the required features but as of this version " "of CMake they have not been integrated to upstream ninja. " "Pending integration, Kitware maintains a branch at:\n" " https://github.com/Kitware/ninja/tree/features-for-fortran#readme\n" "with the required features. " "One may build ninja from that branch to get support for Fortran." ; /* clang-format on */ } else { /* clang-format off */ e << "The Ninja generator in this version of CMake does not support Fortran " "using Ninja version\n" " " + this->NinjaVersion + "\n" "because its 'dyndep' feature version is " << this->NinjaSupportsDyndeps << ". " "This version of CMake is aware only of 'dyndep' feature version 1." ; /* clang-format on */ } mf->IssueMessage(MessageType::FATAL_ERROR, e.str()); cmSystemTools::SetFatalErrorOccured(); return false; } void cmGlobalNinjaGenerator::EnableLanguage( std::vector const& langs, cmMakefile* mf, bool optional) { this->cmGlobalGenerator::EnableLanguage(langs, mf, optional); for (std::string const& l : langs) { if (l == "NONE") { continue; } this->ResolveLanguageCompiler(l, mf, optional); } #ifdef _WIN32 const bool clangGnuMode = ((mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "Clang") && (mf->GetSafeDefinition("CMAKE_C_COMPILER_FRONTEND_VARIANT") == "GNU")) || ((mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "Clang") && (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_FRONTEND_VARIANT") == "GNU")); if (clangGnuMode || ((mf->GetSafeDefinition("CMAKE_C_SIMULATE_ID") != "MSVC") && (mf->GetSafeDefinition("CMAKE_CXX_SIMULATE_ID") != "MSVC") && (mf->IsOn("CMAKE_COMPILER_IS_MINGW") || (mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "GNU") || (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "GNU") || (mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "Clang") || (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "Clang") || (mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "QCC") || (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "QCC")))) { this->UsingGCCOnWindows = true; } #endif } // Implemented by: // cmGlobalUnixMakefileGenerator3 // cmGlobalGhsMultiGenerator // cmGlobalVisualStudio10Generator // cmGlobalVisualStudio7Generator // cmGlobalXCodeGenerator // Called by: // cmGlobalGenerator::Build() std::vector cmGlobalNinjaGenerator::GenerateBuildCommand( const std::string& makeProgram, const std::string& /*projectName*/, const std::string& /*projectDir*/, std::vector const& targetNames, const std::string& /*config*/, bool /*fast*/, int jobs, bool verbose, std::vector const& makeOptions) { GeneratedMakeCommand makeCommand; makeCommand.Add(this->SelectMakeProgram(makeProgram)); if (verbose) { makeCommand.Add("-v"); } if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) && (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) { makeCommand.Add("-j", std::to_string(jobs)); } makeCommand.Add(makeOptions.begin(), makeOptions.end()); for (const auto& tname : targetNames) { if (!tname.empty()) { makeCommand.Add(tname); } } return { std::move(makeCommand) }; } // Non-virtual public methods. void cmGlobalNinjaGenerator::AddRule(cmNinjaRule const& rule) { // Do not add the same rule twice. if (!this->Rules.insert(rule.Name).second) { return; } // Store command length this->RuleCmdLength[rule.Name] = static_cast(rule.Command.size()); // Write rule cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, rule); } bool cmGlobalNinjaGenerator::HasRule(const std::string& name) { return (this->Rules.find(name) != this->Rules.end()); } // Private virtual overrides std::string cmGlobalNinjaGenerator::GetEditCacheCommand() const { // Ninja by design does not run interactive tools in the terminal, // so our only choice is cmake-gui. return cmSystemTools::GetCMakeGUICommand(); } void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory( cmGeneratorTarget* gt) const { // Compute full path to object file directory for this target. std::string dir; dir += gt->LocalGenerator->GetCurrentBinaryDirectory(); dir += "/"; dir += gt->LocalGenerator->GetTargetDirectory(gt); dir += "/"; gt->ObjectDirectory = dir; } // Private methods bool cmGlobalNinjaGenerator::OpenBuildFileStream() { // Compute Ninja's build file path. std::string buildFilePath = this->GetCMakeInstance()->GetHomeOutputDirectory(); buildFilePath += "/"; buildFilePath += cmGlobalNinjaGenerator::NINJA_BUILD_FILE; // Get a stream where to generate things. if (!this->BuildFileStream) { this->BuildFileStream = cm::make_unique( buildFilePath, false, this->GetMakefileEncoding()); if (!(*this->BuildFileStream)) { // An error message is generated by the constructor if it cannot // open the file. return false; } } // Write the do not edit header. this->WriteDisclaimer(*this->BuildFileStream); // Write a comment about this file. *this->BuildFileStream << "# This file contains all the build statements describing the\n" << "# compilation DAG.\n\n"; return true; } void cmGlobalNinjaGenerator::CloseBuildFileStream() { if (this->BuildFileStream) { this->BuildFileStream.reset(); } else { cmSystemTools::Error("Build file stream was not open."); } } bool cmGlobalNinjaGenerator::OpenRulesFileStream() { // Compute Ninja's build file path. std::string rulesFilePath = this->GetCMakeInstance()->GetHomeOutputDirectory(); rulesFilePath += "/"; rulesFilePath += cmGlobalNinjaGenerator::NINJA_RULES_FILE; // Get a stream where to generate things. if (!this->RulesFileStream) { this->RulesFileStream = cm::make_unique( rulesFilePath, false, this->GetMakefileEncoding()); if (!(*this->RulesFileStream)) { // An error message is generated by the constructor if it cannot // open the file. return false; } } // Write the do not edit header. this->WriteDisclaimer(*this->RulesFileStream); // Write comment about this file. /* clang-format off */ *this->RulesFileStream << "# This file contains all the rules used to get the outputs files\n" << "# built from the input files.\n" << "# It is included in the main '" << NINJA_BUILD_FILE << "'.\n\n" ; /* clang-format on */ return true; } void cmGlobalNinjaGenerator::CloseRulesFileStream() { if (this->RulesFileStream) { this->RulesFileStream.reset(); } else { cmSystemTools::Error("Rules file stream was not open."); } } static void EnsureTrailingSlash(std::string& path) { if (path.empty()) { return; } std::string::value_type last = path.back(); #ifdef _WIN32 if (last != '\\') { path += '\\'; } #else if (last != '/') { path += '/'; } #endif } std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath( const std::string& path) const { auto const f = ConvertToNinjaPathCache.find(path); if (f != ConvertToNinjaPathCache.end()) { return f->second; } cmLocalNinjaGenerator* ng = static_cast(this->LocalGenerators[0]); std::string const& bin_dir = ng->GetState()->GetBinaryDirectory(); std::string convPath = ng->MaybeConvertToRelativePath(bin_dir, path); convPath = this->NinjaOutputPath(convPath); #ifdef _WIN32 std::replace(convPath.begin(), convPath.end(), '/', '\\'); #endif return ConvertToNinjaPathCache.emplace(path, std::move(convPath)) .first->second; } void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName) { this->AdditionalCleanFiles.emplace(std::move(fileName)); } void cmGlobalNinjaGenerator::AddCXXCompileCommand( const std::string& commandLine, const std::string& sourceFile) { // Compute Ninja's build file path. std::string buildFileDir = this->GetCMakeInstance()->GetHomeOutputDirectory(); if (!this->CompileCommandsStream) { std::string buildFilePath = buildFileDir + "/compile_commands.json"; if (this->ComputingUnknownDependencies) { this->CombinedBuildOutputs.insert( this->NinjaOutputPath("compile_commands.json")); } // Get a stream where to generate things. this->CompileCommandsStream = cm::make_unique(buildFilePath); *this->CompileCommandsStream << "["; } else { *this->CompileCommandsStream << "," << std::endl; } std::string sourceFileName = sourceFile; if (!cmSystemTools::FileIsFullPath(sourceFileName)) { sourceFileName = cmSystemTools::CollapseFullPath( sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory()); } /* clang-format off */ *this->CompileCommandsStream << "\n{\n" << R"( "directory": ")" << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n" << R"( "command": ")" << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n" << R"( "file": ")" << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\"\n" << "}"; /* clang-format on */ } void cmGlobalNinjaGenerator::CloseCompileCommandsStream() { if (this->CompileCommandsStream) { *this->CompileCommandsStream << "\n]"; this->CompileCommandsStream.reset(); } } void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os) { os << "# CMAKE generated file: DO NOT EDIT!\n" << "# Generated by \"" << this->GetName() << "\"" << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "." << cmVersion::GetMinorVersion() << "\n\n"; } void cmGlobalNinjaGenerator::AddDependencyToAll(cmGeneratorTarget* target) { this->AppendTargetOutputs(target, this->AllDependencies); } void cmGlobalNinjaGenerator::AddDependencyToAll(const std::string& input) { this->AllDependencies.push_back(input); } void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies() { for (auto const& asd : this->AssumedSourceDependencies) { cmNinjaDeps orderOnlyDeps; std::copy(asd.second.begin(), asd.second.end(), std::back_inserter(orderOnlyDeps)); WriteCustomCommandBuild(/*command=*/"", /*description=*/"", "Assume dependencies for generated source file.", /*depfile*/ "", /*job_pool*/ "", /*uses_terminal*/ false, /*restat*/ true, cmNinjaDeps(1, asd.first), cmNinjaDeps(), orderOnlyDeps); } } std::string OrderDependsTargetForTarget(cmGeneratorTarget const* target) { return "cmake_object_order_depends_target_" + target->GetName(); } void cmGlobalNinjaGenerator::AppendTargetOutputs( cmGeneratorTarget const* target, cmNinjaDeps& outputs, cmNinjaTargetDepends depends) { std::string configName = target->Target->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE"); // for frameworks, we want the real name, not smple name // frameworks always appear versioned, and the build.ninja // will always attempt to manage symbolic links instead // of letting cmOSXBundleGenerator do it. bool realname = target->IsFrameworkOnApple(); switch (target->GetType()) { case cmStateEnums::SHARED_LIBRARY: case cmStateEnums::STATIC_LIBRARY: case cmStateEnums::MODULE_LIBRARY: { if (depends == DependOnTargetOrdering) { outputs.push_back(OrderDependsTargetForTarget(target)); break; } } // FALLTHROUGH case cmStateEnums::EXECUTABLE: { outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath( configName, cmStateEnums::RuntimeBinaryArtifact, realname))); break; } case cmStateEnums::OBJECT_LIBRARY: { if (depends == DependOnTargetOrdering) { outputs.push_back(OrderDependsTargetForTarget(target)); break; } } // FALLTHROUGH case cmStateEnums::GLOBAL_TARGET: case cmStateEnums::UTILITY: { std::string path = target->GetLocalGenerator()->GetCurrentBinaryDirectory() + std::string("/") + target->GetName(); outputs.push_back(this->ConvertToNinjaPath(path)); break; } default: return; } } void cmGlobalNinjaGenerator::AppendTargetDepends( cmGeneratorTarget const* target, cmNinjaDeps& outputs, cmNinjaTargetDepends depends) { if (target->GetType() == cmStateEnums::GLOBAL_TARGET) { // These depend only on other CMake-provided targets, e.g. "all". for (BT const& util : target->GetUtilities()) { std::string d = target->GetLocalGenerator()->GetCurrentBinaryDirectory() + "/" + util.Value; outputs.push_back(this->ConvertToNinjaPath(d)); } } else { cmNinjaDeps outs; for (cmTargetDepend const& targetDep : this->GetTargetDirectDepends(target)) { if (targetDep->GetType() == cmStateEnums::INTERFACE_LIBRARY) { continue; } this->AppendTargetOutputs(targetDep, outs, depends); } std::sort(outs.begin(), outs.end()); cmAppend(outputs, outs); } } void cmGlobalNinjaGenerator::AppendTargetDependsClosure( cmGeneratorTarget const* target, cmNinjaDeps& outputs) { cmNinjaOuts outs; this->AppendTargetDependsClosure(target, outs, true); cmAppend(outputs, outs); } void cmGlobalNinjaGenerator::AppendTargetDependsClosure( cmGeneratorTarget const* target, cmNinjaOuts& outputs, bool omit_self) { // try to locate the target in the cache auto find = this->TargetDependsClosures.lower_bound(target); if (find == this->TargetDependsClosures.end() || find->first != target) { // We now calculate the closure outputs by inspecting the dependent // targets recursively. // For that we have to distinguish between a local result set that is only // relevant for filling the cache entries properly isolated and a global // result set that is relevant for the result of the top level call to // AppendTargetDependsClosure. cmNinjaOuts this_outs; // this will be the new cache entry for (auto const& dep_target : this->GetTargetDirectDepends(target)) { if (dep_target->GetType() == cmStateEnums::INTERFACE_LIBRARY) { continue; } // Collect the dependent targets for _this_ target this->AppendTargetDependsClosure(dep_target, this_outs, false); } find = this->TargetDependsClosures.emplace_hint(find, target, std::move(this_outs)); } // now fill the outputs of the final result from the newly generated cache // entry outputs.insert(find->second.begin(), find->second.end()); // finally generate the outputs of the target itself, if applicable cmNinjaDeps outs; if (!omit_self) { this->AppendTargetOutputs(target, outs); } outputs.insert(outs.begin(), outs.end()); } void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias, cmGeneratorTarget* target) { std::string buildAlias = this->NinjaOutputPath(alias); cmNinjaDeps outputs; this->AppendTargetOutputs(target, outputs); // Mark the target's outputs as ambiguous to ensure that no other target uses // the output as an alias. for (std::string const& output : outputs) { TargetAliases[output] = nullptr; } // Insert the alias into the map. If the alias was already present in the // map and referred to another target, mark it as ambiguous. std::pair newAlias = TargetAliases.insert(std::make_pair(buildAlias, target)); if (newAlias.second && newAlias.first->second != target) { newAlias.first->second = nullptr; } } void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os) { cmGlobalNinjaGenerator::WriteDivider(os); os << "# Target aliases.\n\n"; cmNinjaBuild build("phony"); build.Outputs.emplace_back(""); for (auto const& ta : TargetAliases) { // Don't write ambiguous aliases. if (!ta.second) { continue; } // Don't write alias if there is a already a custom command with // matching output if (this->HasCustomCommandOutput(ta.first)) { continue; } // Outputs build.Outputs[0] = ta.first; // Explicit depdendencies build.ExplicitDeps.clear(); this->AppendTargetOutputs(ta.second, build.ExplicitDeps); // Write this->WriteBuild(os, build); } } void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os) { cmGlobalNinjaGenerator::WriteDivider(os); os << "# Folder targets.\n\n"; std::string const& rootBinaryDir = this->LocalGenerators[0]->GetBinaryDirectory(); std::map targetsPerFolder; for (cmLocalGenerator const* lg : this->LocalGenerators) { std::string const& currentBinaryFolder( lg->GetStateSnapshot().GetDirectory().GetCurrentBinary()); // Do not generate a rule for the root binary dir. if (currentBinaryFolder == rootBinaryDir) { continue; } // The directory-level rule should depend on the target-level rules // for all targets in the directory. cmNinjaDeps& folderTargets = targetsPerFolder[currentBinaryFolder]; for (auto gt : lg->GetGeneratorTargets()) { cmStateEnums::TargetType const type = gt->GetType(); if ((type == cmStateEnums::EXECUTABLE || type == cmStateEnums::STATIC_LIBRARY || type == cmStateEnums::SHARED_LIBRARY || type == cmStateEnums::MODULE_LIBRARY || type == cmStateEnums::OBJECT_LIBRARY || type == cmStateEnums::UTILITY) && !gt->GetPropertyAsBool("EXCLUDE_FROM_ALL")) { folderTargets.push_back(gt->GetName()); } } // The directory-level rule should depend on the directory-level // rules of the subdirectories. for (cmStateSnapshot const& state : lg->GetStateSnapshot().GetChildren()) { std::string const& currentBinaryDir = state.GetDirectory().GetCurrentBinary(); folderTargets.push_back( this->ConvertToNinjaPath(currentBinaryDir + "/all")); } } if (!targetsPerFolder.empty()) { cmNinjaBuild build("phony"); build.Outputs.emplace_back(""); for (auto& it : targetsPerFolder) { cmGlobalNinjaGenerator::WriteDivider(os); std::string const& currentBinaryDir = it.first; // Setup target build.Comment = "Folder: " + currentBinaryDir; build.Outputs[0] = this->ConvertToNinjaPath(currentBinaryDir + "/all"); build.ExplicitDeps = std::move(it.second); // Write target this->WriteBuild(os, build); } } } void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os) { if (!this->ComputingUnknownDependencies) { return; } // We need to collect the set of known build outputs. // Start with those generated by WriteBuild calls. // No other method needs this so we can take ownership // of the set locally and throw it out when we are done. std::set knownDependencies; knownDependencies.swap(this->CombinedBuildOutputs); // now write out the unknown explicit dependencies. // union the configured files, evaluations files and the // CombinedBuildOutputs, // and then difference with CombinedExplicitDependencies to find the explicit // dependencies that we have no rule for cmGlobalNinjaGenerator::WriteDivider(os); /* clang-format off */ os << "# Unknown Build Time Dependencies.\n" << "# Tell Ninja that they may appear as side effects of build rules\n" << "# otherwise ordered by order-only dependencies.\n\n"; /* clang-format on */ // get the list of files that cmake itself has generated as a // product of configuration. for (cmLocalGenerator* lg : this->LocalGenerators) { // get the vector of files created by this makefile and convert them // to ninja paths, which are all relative in respect to the build directory for (std::string const& file : lg->GetMakefile()->GetOutputFiles()) { knownDependencies.insert(this->ConvertToNinjaPath(file)); } if (!this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) { // get list files which are implicit dependencies as well and will be // phony for rebuild manifest for (std::string const& j : lg->GetMakefile()->GetListFiles()) { knownDependencies.insert(this->ConvertToNinjaPath(j)); } } for (cmGeneratorExpressionEvaluationFile* li : lg->GetMakefile()->GetEvaluationFiles()) { // get all the files created by generator expressions and convert them // to ninja paths for (std::string const& evaluationFile : li->GetFiles()) { knownDependencies.insert(this->ConvertToNinjaPath(evaluationFile)); } } } knownDependencies.insert(this->CMakeCacheFile); for (auto const& ta : this->TargetAliases) { knownDependencies.insert(this->ConvertToNinjaPath(ta.first)); } // remove all source files we know will exist. for (auto const& i : this->AssumedSourceDependencies) { knownDependencies.insert(this->ConvertToNinjaPath(i.first)); } // now we difference with CombinedCustomCommandExplicitDependencies to find // the list of items we know nothing about. // We have encoded all the paths in CombinedCustomCommandExplicitDependencies // and knownDependencies so no matter if unix or windows paths they // should all match now. std::vector unknownExplicitDepends; this->CombinedCustomCommandExplicitDependencies.erase(this->TargetAll); std::set_difference(this->CombinedCustomCommandExplicitDependencies.begin(), this->CombinedCustomCommandExplicitDependencies.end(), knownDependencies.begin(), knownDependencies.end(), std::back_inserter(unknownExplicitDepends)); std::vector warnExplicitDepends; if (!unknownExplicitDepends.empty()) { cmake* cmk = this->GetCMakeInstance(); std::string const& buildRoot = cmk->GetHomeOutputDirectory(); bool const inSource = (buildRoot == cmk->GetHomeDirectory()); bool const warn = (!inSource && (this->PolicyCMP0058 == cmPolicies::WARN)); cmNinjaBuild build("phony"); build.Outputs.emplace_back(""); for (std::string const& ued : unknownExplicitDepends) { // verify the file is in the build directory std::string const absDepPath = cmSystemTools::CollapseFullPath(ued, buildRoot); if (cmSystemTools::IsSubDirectory(absDepPath, buildRoot)) { // Generate phony build statement build.Outputs[0] = ued; this->WriteBuild(os, build); // Add to warning on demand if (warn && warnExplicitDepends.size() < 10) { warnExplicitDepends.push_back(ued); } } } } if (!warnExplicitDepends.empty()) { std::ostringstream w; /* clang-format off */ w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0058) << "\n" "This project specifies custom command DEPENDS on files " "in the build tree that are not specified as the OUTPUT or " "BYPRODUCTS of any add_custom_command or add_custom_target:\n" " " << cmJoin(warnExplicitDepends, "\n ") << "\n" "For compatibility with versions of CMake that did not have " "the BYPRODUCTS option, CMake is generating phony rules for " "such files to convince 'ninja' to build." "\n" "Project authors should add the missing BYPRODUCTS or OUTPUT " "options to the custom commands that produce these files." ; /* clang-format on */ this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING, w.str()); } } void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os) { // Write headers. cmGlobalNinjaGenerator::WriteDivider(os); os << "# Built-in targets\n\n"; this->WriteTargetAll(os); this->WriteTargetRebuildManifest(os); this->WriteTargetClean(os); this->WriteTargetHelp(os); } void cmGlobalNinjaGenerator::WriteTargetAll(std::ostream& os) { cmNinjaBuild build("phony"); build.Comment = "The main all target."; build.Outputs.push_back(this->TargetAll); build.ExplicitDeps = this->AllDependencies; this->WriteBuild(os, build); if (!this->HasOutputPathPrefix()) { cmGlobalNinjaGenerator::WriteDefault(os, build.Outputs, "Make the all target the default."); } } void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os) { if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) { return; } cmLocalGenerator* lg = this->LocalGenerators[0]; { cmNinjaRule rule("RERUN_CMAKE"); rule.Command = CMakeCmd(); rule.Command += " -S"; rule.Command += lg->ConvertToOutputFormat(lg->GetSourceDirectory(), cmOutputConverter::SHELL); rule.Command += " -B"; rule.Command += lg->ConvertToOutputFormat(lg->GetBinaryDirectory(), cmOutputConverter::SHELL); rule.Description = "Re-running CMake..."; rule.Comment = "Rule for re-running cmake."; rule.Generator = true; WriteRule(*this->RulesFileStream, rule); } cmNinjaBuild reBuild("RERUN_CMAKE"); reBuild.Comment = "Re-run CMake if any of its inputs changed."; reBuild.Outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE)); for (cmLocalGenerator* localGen : this->LocalGenerators) { for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) { reBuild.ImplicitDeps.push_back(this->ConvertToNinjaPath(fi)); } } reBuild.ImplicitDeps.push_back(this->CMakeCacheFile); // Use 'console' pool to get non buffered output of the CMake re-run call // Available since Ninja 1.5 if (SupportsConsolePool()) { reBuild.Variables["pool"] = "console"; } cmake* cm = this->GetCMakeInstance(); if (this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) { { cmNinjaRule rule("VERIFY_GLOBS"); rule.Command = CMakeCmd(); rule.Command += " -P "; rule.Command += lg->ConvertToOutputFormat(cm->GetGlobVerifyScript(), cmOutputConverter::SHELL); rule.Description = "Re-checking globbed directories..."; rule.Comment = "Rule for re-checking globbed directories."; rule.Generator = true; this->WriteRule(*this->RulesFileStream, rule); } cmNinjaBuild phonyBuild("phony"); phonyBuild.Comment = "Phony target to force glob verification run."; phonyBuild.Outputs.push_back(cm->GetGlobVerifyScript() + "_force"); this->WriteBuild(os, phonyBuild); reBuild.Variables["restat"] = "1"; std::string const verifyScriptFile = this->NinjaOutputPath(cm->GetGlobVerifyScript()); std::string const verifyStampFile = this->NinjaOutputPath(cm->GetGlobVerifyStamp()); { cmNinjaBuild vgBuild("VERIFY_GLOBS"); vgBuild.Comment = "Re-run CMake to check if globbed directories changed."; vgBuild.Outputs.push_back(verifyStampFile); vgBuild.ImplicitDeps = phonyBuild.Outputs; vgBuild.Variables = reBuild.Variables; this->WriteBuild(os, vgBuild); } reBuild.Variables.erase("restat"); reBuild.ImplicitDeps.push_back(verifyScriptFile); reBuild.ExplicitDeps.push_back(verifyStampFile); } else if (!this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) { std::ostringstream msg; msg << "The detected version of Ninja:\n" << " " << this->NinjaVersion << "\n" << "is less than the version of Ninja required by CMake for adding " "restat dependencies to the build.ninja manifest regeneration " "target:\n" << " " << cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat() << "\n"; msg << "Any pre-check scripts, such as those generated for file(GLOB " "CONFIGURE_DEPENDS), will not be run by Ninja."; this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING, msg.str()); } std::sort(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()); reBuild.ImplicitDeps.erase( std::unique(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()), reBuild.ImplicitDeps.end()); this->WriteBuild(os, reBuild); { cmNinjaBuild build("phony"); build.Comment = "A missing CMake input file is not an error."; std::set_difference(std::make_move_iterator(reBuild.ImplicitDeps.begin()), std::make_move_iterator(reBuild.ImplicitDeps.end()), CustomCommandOutputs.begin(), CustomCommandOutputs.end(), std::back_inserter(build.Outputs)); this->WriteBuild(os, build); } } std::string cmGlobalNinjaGenerator::CMakeCmd() const { cmLocalGenerator* lgen = this->LocalGenerators.at(0); return lgen->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL); } std::string cmGlobalNinjaGenerator::NinjaCmd() const { cmLocalGenerator* lgen = this->LocalGenerators[0]; if (lgen != nullptr) { return lgen->ConvertToOutputFormat(this->NinjaCommand, cmOutputConverter::SHELL); } return "ninja"; } bool cmGlobalNinjaGenerator::SupportsConsolePool() const { return this->NinjaSupportsConsolePool; } bool cmGlobalNinjaGenerator::SupportsImplicitOuts() const { return this->NinjaSupportsImplicitOuts; } bool cmGlobalNinjaGenerator::SupportsManifestRestat() const { return this->NinjaSupportsManifestRestat; } bool cmGlobalNinjaGenerator::SupportsMultilineDepfile() const { return this->NinjaSupportsMultilineDepfile; } bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os) { cmLocalGenerator* lgr = this->LocalGenerators.at(0); std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake"; std::string cleanScriptAbs = lgr->GetBinaryDirectory(); cleanScriptAbs += '/'; cleanScriptAbs += cleanScriptRel; // Check if there are additional files to clean if (this->AdditionalCleanFiles.empty()) { // Remove cmake clean script file if it exists cmSystemTools::RemoveFile(cleanScriptAbs); return false; } // Write cmake clean script file { cmGeneratedFileStream fout(cleanScriptAbs); if (!fout) { return false; } fout << "# Additional clean files\n\n"; fout << "file(REMOVE_RECURSE\n"; for (std::string const& acf : this->AdditionalCleanFiles) { fout << " " << cmOutputConverter::EscapeForCMake(ConvertToNinjaPath(acf)) << '\n'; } fout << ")\n"; } // Register clean script file lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs); // Write rule { cmNinjaRule rule("CLEAN_ADDITIONAL"); rule.Command = CMakeCmd(); rule.Command += " -P "; rule.Command += lgr->ConvertToOutputFormat( this->NinjaOutputPath(cleanScriptRel), cmOutputConverter::SHELL); rule.Description = "Cleaning additional files..."; rule.Comment = "Rule for cleaning additional files."; WriteRule(*this->RulesFileStream, rule); } // Write build { cmNinjaBuild build("CLEAN_ADDITIONAL"); build.Comment = "Clean additional files."; build.Outputs.push_back( this->NinjaOutputPath(this->GetAdditionalCleanTargetName())); WriteBuild(os, build); } // Return success return true; } void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os) { // -- Additional clean target bool additionalFiles = WriteTargetCleanAdditional(os); // -- Default clean target // Write rule { cmNinjaRule rule("CLEAN"); rule.Command = NinjaCmd() + " -t clean"; rule.Description = "Cleaning all built files..."; rule.Comment = "Rule for cleaning all built files."; WriteRule(*this->RulesFileStream, rule); } // Write build { cmNinjaBuild build("CLEAN"); build.Comment = "Clean all the built files."; build.Outputs.push_back(this->NinjaOutputPath(this->GetCleanTargetName())); if (additionalFiles) { build.ExplicitDeps.push_back( this->NinjaOutputPath(this->GetAdditionalCleanTargetName())); } WriteBuild(os, build); } } void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os) { { cmNinjaRule rule("HELP"); rule.Command = NinjaCmd() + " -t targets"; rule.Description = "All primary targets available:"; rule.Comment = "Rule for printing all primary targets available."; WriteRule(*this->RulesFileStream, rule); } { cmNinjaBuild build("HELP"); build.Comment = "Print all primary targets available."; build.Outputs.push_back(this->NinjaOutputPath("help")); WriteBuild(os, build); } } void cmGlobalNinjaGenerator::InitOutputPathPrefix() { this->OutputPathPrefix = this->LocalGenerators[0]->GetMakefile()->GetSafeDefinition( "CMAKE_NINJA_OUTPUT_PATH_PREFIX"); EnsureTrailingSlash(this->OutputPathPrefix); } std::string cmGlobalNinjaGenerator::NinjaOutputPath( std::string const& path) const { if (!this->HasOutputPathPrefix() || cmSystemTools::FileIsFullPath(path)) { return path; } return this->OutputPathPrefix + path; } void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix( std::string& path) { if (path.empty()) { return; } EnsureTrailingSlash(path); cmStripSuffixIfExists(path, this->OutputPathPrefix); } /* We use the following approach to support Fortran. Each target already has a .dir/ directory used to hold intermediate files for CMake. For each target, a FortranDependInfo.json file is generated by CMake with information about include directories, module directories, and the locations the per-target directories for target dependencies. Compilation of source files within a target is split into the following steps: 1. Preprocess all sources, scan preprocessed output for module dependencies. This step is done with independent build statements for each source, and can therefore be done in parallel. rule Fortran_PREPROCESS depfile = $DEP_FILE command = gfortran -cpp $DEFINES $INCLUDES $FLAGS -E $in -o $out && cmake -E cmake_ninja_depends \ --tdi=FortranDependInfo.json --pp=$out --dep=$DEP_FILE \ --obj=$OBJ_FILE --ddi=$DYNDEP_INTERMEDIATE_FILE \ --lang=Fortran build src.f90-pp.f90 | src.f90.o.ddi: Fortran_PREPROCESS src.f90 OBJ_FILE = src.f90.o DEP_FILE = src.f90.o.d DYNDEP_INTERMEDIATE_FILE = src.f90.o.ddi The ``cmake -E cmake_ninja_depends`` tool reads the preprocessed output and generates the ninja depfile for preprocessor dependencies. It also generates a "ddi" file (in a format private to CMake) that lists the object file that compilation will produce along with the module names it provides and/or requires. The "ddi" file is an implicit output because it should not appear in "$out" but is generated by the rule. 2. Consolidate the per-source module dependencies saved in the "ddi" files from all sources to produce a ninja "dyndep" file, ``Fortran.dd``. rule Fortran_DYNDEP command = cmake -E cmake_ninja_dyndep \ --tdi=FortranDependInfo.json --lang=Fortran --dd=$out $in build Fortran.dd: Fortran_DYNDEP src1.f90.o.ddi src2.f90.o.ddi The ``cmake -E cmake_ninja_dyndep`` tool reads the "ddi" files from all sources in the target and the ``FortranModules.json`` files from targets on which the target depends. It computes dependency edges on compilations that require modules to those that provide the modules. This information is placed in the ``Fortran.dd`` file for ninja to load later. It also writes the expected location of modules provided by this target into ``FortranModules.json`` for use by dependent targets. 3. Compile all sources after loading dynamically discovered dependencies of the compilation build statements from their ``dyndep`` bindings. rule Fortran_COMPILE command = gfortran $INCLUDES $FLAGS -c $in -o $out build src1.f90.o: Fortran_COMPILE src1.f90-pp.f90 || Fortran.dd dyndep = Fortran.dd The "dyndep" binding tells ninja to load dynamically discovered dependency information from ``Fortran.dd``. This adds information such as: build src1.f90.o | mod1.mod: dyndep restat = 1 This tells ninja that ``mod1.mod`` is an implicit output of compiling the object file ``src1.f90.o``. The ``restat`` binding tells it that the timestamp of the output may not always change. Additionally: build src2.f90.o: dyndep | mod1.mod This tells ninja that ``mod1.mod`` is a dependency of compiling the object file ``src2.f90.o``. This ensures that ``src1.f90.o`` and ``mod1.mod`` will always be up to date before ``src2.f90.o`` is built (because the latter consumes the module). */ struct cmSourceInfo { // Set of provided and required modules. std::set Provides; std::set Requires; // Set of files included in the translation unit. std::set Includes; }; static std::unique_ptr cmcmd_cmake_ninja_depends_fortran( std::string const& arg_tdi, std::string const& arg_pp); int cmcmd_cmake_ninja_depends(std::vector::const_iterator argBeg, std::vector::const_iterator argEnd) { std::string arg_tdi; std::string arg_pp; std::string arg_dep; std::string arg_obj; std::string arg_ddi; std::string arg_lang; for (std::string const& arg : cmMakeRange(argBeg, argEnd)) { if (cmHasLiteralPrefix(arg, "--tdi=")) { arg_tdi = arg.substr(6); } else if (cmHasLiteralPrefix(arg, "--pp=")) { arg_pp = arg.substr(5); } else if (cmHasLiteralPrefix(arg, "--dep=")) { arg_dep = arg.substr(6); } else if (cmHasLiteralPrefix(arg, "--obj=")) { arg_obj = arg.substr(6); } else if (cmHasLiteralPrefix(arg, "--ddi=")) { arg_ddi = arg.substr(6); } else if (cmHasLiteralPrefix(arg, "--lang=")) { arg_lang = arg.substr(7); } else { cmSystemTools::Error("-E cmake_ninja_depends unknown argument: " + arg); return 1; } } if (arg_tdi.empty()) { cmSystemTools::Error("-E cmake_ninja_depends requires value for --tdi="); return 1; } if (arg_pp.empty()) { cmSystemTools::Error("-E cmake_ninja_depends requires value for --pp="); return 1; } if (arg_dep.empty()) { cmSystemTools::Error("-E cmake_ninja_depends requires value for --dep="); return 1; } if (arg_obj.empty()) { cmSystemTools::Error("-E cmake_ninja_depends requires value for --obj="); return 1; } if (arg_ddi.empty()) { cmSystemTools::Error("-E cmake_ninja_depends requires value for --ddi="); return 1; } if (arg_lang.empty()) { cmSystemTools::Error("-E cmake_ninja_depends requires value for --lang="); return 1; } std::unique_ptr info; if (arg_lang == "Fortran") { info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_pp); } else { cmSystemTools::Error("-E cmake_ninja_depends does not understand the " + arg_lang + " language"); return 1; } if (!info) { // The error message is already expected to have been output. return 1; } { cmGeneratedFileStream depfile(arg_dep); depfile << cmSystemTools::ConvertToUnixOutputPath(arg_pp) << ":"; for (std::string const& include : info->Includes) { depfile << " \\\n " << cmSystemTools::ConvertToUnixOutputPath(include); } depfile << "\n"; } Json::Value ddi(Json::objectValue); ddi["object"] = arg_obj; Json::Value& ddi_provides = ddi["provides"] = Json::arrayValue; for (std::string const& provide : info->Provides) { ddi_provides.append(provide); } Json::Value& ddi_requires = ddi["requires"] = Json::arrayValue; for (std::string const& r : info->Requires) { // Require modules not provided in the same source. if (!info->Provides.count(r)) { ddi_requires.append(r); } } cmGeneratedFileStream ddif(arg_ddi); ddif << ddi; if (!ddif) { cmSystemTools::Error("-E cmake_ninja_depends failed to write " + arg_ddi); return 1; } return 0; } std::unique_ptr cmcmd_cmake_ninja_depends_fortran( std::string const& arg_tdi, std::string const& arg_pp) { cmFortranCompiler fc; std::vector includes; { Json::Value tdio; Json::Value const& tdi = tdio; { cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary); Json::Reader reader; if (!reader.parse(tdif, tdio, false)) { cmSystemTools::Error("-E cmake_ninja_depends failed to parse " + arg_tdi + reader.getFormattedErrorMessages()); return nullptr; } } Json::Value const& tdi_include_dirs = tdi["include-dirs"]; if (tdi_include_dirs.isArray()) { for (auto const& tdi_include_dir : tdi_include_dirs) { includes.push_back(tdi_include_dir.asString()); } } Json::Value const& tdi_compiler_id = tdi["compiler-id"]; fc.Id = tdi_compiler_id.asString(); Json::Value const& tdi_submodule_sep = tdi["submodule-sep"]; fc.SModSep = tdi_submodule_sep.asString(); Json::Value const& tdi_submodule_ext = tdi["submodule-ext"]; fc.SModExt = tdi_submodule_ext.asString(); } cmFortranSourceInfo finfo; std::set defines; cmFortranParser parser(fc, includes, defines, finfo); if (!cmFortranParser_FilePush(&parser, arg_pp.c_str())) { cmSystemTools::Error("-E cmake_ninja_depends failed to open " + arg_pp); return nullptr; } if (cmFortran_yyparse(parser.Scanner) != 0) { // Failed to parse the file. return nullptr; } auto info = cm::make_unique(); info->Provides = finfo.Provides; info->Requires = finfo.Requires; info->Includes = finfo.Includes; return info; } struct cmDyndepObjectInfo { std::string Object; std::vector Provides; std::vector Requires; }; bool cmGlobalNinjaGenerator::WriteDyndepFile( std::string const& dir_top_src, std::string const& dir_top_bld, std::string const& dir_cur_src, std::string const& dir_cur_bld, std::string const& arg_dd, std::vector const& arg_ddis, std::string const& module_dir, std::vector const& linked_target_dirs, std::string const& arg_lang) { // Setup path conversions. { cmStateSnapshot snapshot = this->GetCMakeInstance()->GetCurrentSnapshot(); snapshot.GetDirectory().SetCurrentSource(dir_cur_src); snapshot.GetDirectory().SetCurrentBinary(dir_cur_bld); snapshot.GetDirectory().SetRelativePathTopSource(dir_top_src.c_str()); snapshot.GetDirectory().SetRelativePathTopBinary(dir_top_bld.c_str()); auto mfd = cm::make_unique(this, snapshot); std::unique_ptr lgd( static_cast( this->CreateLocalGenerator(mfd.get()))); this->Makefiles.push_back(mfd.release()); this->LocalGenerators.push_back(lgd.release()); } std::vector objects; for (std::string const& arg_ddi : arg_ddis) { // Load the ddi file and compute the module file paths it provides. Json::Value ddio; Json::Value const& ddi = ddio; cmsys::ifstream ddif(arg_ddi.c_str(), std::ios::in | std::ios::binary); Json::Reader reader; if (!reader.parse(ddif, ddio, false)) { cmSystemTools::Error("-E cmake_ninja_dyndep failed to parse " + arg_ddi + reader.getFormattedErrorMessages()); return false; } cmDyndepObjectInfo info; info.Object = ddi["object"].asString(); Json::Value const& ddi_provides = ddi["provides"]; if (ddi_provides.isArray()) { for (auto const& ddi_provide : ddi_provides) { info.Provides.push_back(ddi_provide.asString()); } } Json::Value const& ddi_requires = ddi["requires"]; if (ddi_requires.isArray()) { for (auto const& ddi_require : ddi_requires) { info.Requires.push_back(ddi_require.asString()); } } objects.push_back(std::move(info)); } // Map from module name to module file path, if known. std::map mod_files; // Populate the module map with those provided by linked targets first. for (std::string const& linked_target_dir : linked_target_dirs) { std::string const ltmn = linked_target_dir + "/" + arg_lang + "Modules.json"; Json::Value ltm; cmsys::ifstream ltmf(ltmn.c_str(), std::ios::in | std::ios::binary); Json::Reader reader; if (ltmf && !reader.parse(ltmf, ltm, false)) { cmSystemTools::Error("-E cmake_ninja_dyndep failed to parse " + linked_target_dir + reader.getFormattedErrorMessages()); return false; } if (ltm.isObject()) { for (Json::Value::iterator i = ltm.begin(); i != ltm.end(); ++i) { mod_files[i.key().asString()] = i->asString(); } } } // Extend the module map with those provided by this target. // We do this after loading the modules provided by linked targets // in case we have one of the same name that must be preferred. Json::Value tm = Json::objectValue; for (cmDyndepObjectInfo const& object : objects) { for (std::string const& p : object.Provides) { std::string const mod = module_dir + p; mod_files[p] = mod; tm[p] = mod; } } cmGeneratedFileStream ddf(arg_dd); ddf << "ninja_dyndep_version = 1.0\n"; { cmNinjaBuild build("dyndep"); build.Outputs.emplace_back(""); for (cmDyndepObjectInfo const& object : objects) { build.Outputs[0] = object.Object; build.ImplicitOuts.clear(); for (std::string const& p : object.Provides) { build.ImplicitOuts.push_back(this->ConvertToNinjaPath(mod_files[p])); } build.ImplicitDeps.clear(); for (std::string const& r : object.Requires) { auto mit = mod_files.find(r); if (mit != mod_files.end()) { build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second)); } } build.Variables.clear(); if (!object.Provides.empty()) { build.Variables.emplace("restat", "1"); } this->WriteBuild(ddf, build); } } // Store the map of modules provided by this target in a file for // use by dependents that reference this target in linked-target-dirs. std::string const target_mods_file = cmSystemTools::GetFilenamePath(arg_dd) + "/" + arg_lang + "Modules.json"; cmGeneratedFileStream tmf(target_mods_file); tmf << tm; return true; } int cmcmd_cmake_ninja_dyndep(std::vector::const_iterator argBeg, std::vector::const_iterator argEnd) { std::vector arg_full = cmSystemTools::HandleResponseFile(argBeg, argEnd); std::string arg_dd; std::string arg_lang; std::string arg_tdi; std::vector arg_ddis; for (std::string const& arg : arg_full) { if (cmHasLiteralPrefix(arg, "--tdi=")) { arg_tdi = arg.substr(6); } else if (cmHasLiteralPrefix(arg, "--lang=")) { arg_lang = arg.substr(7); } else if (cmHasLiteralPrefix(arg, "--dd=")) { arg_dd = arg.substr(5); } else if (!cmHasLiteralPrefix(arg, "--") && cmHasLiteralSuffix(arg, ".ddi")) { arg_ddis.push_back(arg); } else { cmSystemTools::Error("-E cmake_ninja_dyndep unknown argument: " + arg); return 1; } } if (arg_tdi.empty()) { cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --tdi="); return 1; } if (arg_lang.empty()) { cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --lang="); return 1; } if (arg_dd.empty()) { cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --dd="); return 1; } Json::Value tdio; Json::Value const& tdi = tdio; { cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary); Json::Reader reader; if (!reader.parse(tdif, tdio, false)) { cmSystemTools::Error("-E cmake_ninja_dyndep failed to parse " + arg_tdi + reader.getFormattedErrorMessages()); return 1; } } std::string const dir_cur_bld = tdi["dir-cur-bld"].asString(); std::string const dir_cur_src = tdi["dir-cur-src"].asString(); std::string const dir_top_bld = tdi["dir-top-bld"].asString(); std::string const dir_top_src = tdi["dir-top-src"].asString(); std::string module_dir = tdi["module-dir"].asString(); if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) { module_dir += "/"; } std::vector linked_target_dirs; Json::Value const& tdi_linked_target_dirs = tdi["linked-target-dirs"]; if (tdi_linked_target_dirs.isArray()) { for (auto const& tdi_linked_target_dir : tdi_linked_target_dirs) { linked_target_dirs.push_back(tdi_linked_target_dir.asString()); } } cmake cm(cmake::RoleInternal, cmState::Unknown); cm.SetHomeDirectory(dir_top_src); cm.SetHomeOutputDirectory(dir_top_bld); std::unique_ptr ggd( static_cast(cm.CreateGlobalGenerator("Ninja"))); if (!ggd || !ggd->WriteDyndepFile(dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld, arg_dd, arg_ddis, module_dir, linked_target_dirs, arg_lang)) { return 1; } return 0; }