1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmGlobalNinjaGenerator.h"
12 #include <cmext/algorithm>
13 #include <cmext/memory>
15 #include "cmsys/FStream.hxx"
17 #include "cm_jsoncpp_reader.h"
18 #include "cm_jsoncpp_value.h"
19 #include "cm_jsoncpp_writer.h"
21 #include "cmAlgorithms.h"
22 #include "cmDocumentationEntry.h"
23 #include "cmFortranParser.h"
24 #include "cmGeneratedFileStream.h"
25 #include "cmGeneratorExpressionEvaluationFile.h"
26 #include "cmGeneratorTarget.h"
27 #include "cmGlobalGenerator.h"
28 #include "cmLinkLineComputer.h"
29 #include "cmListFileCache.h"
30 #include "cmLocalGenerator.h"
31 #include "cmLocalNinjaGenerator.h"
32 #include "cmMakefile.h"
33 #include "cmMessageType.h"
34 #include "cmNinjaLinkLineComputer.h"
35 #include "cmOutputConverter.h"
38 #include "cmStateDirectory.h"
39 #include "cmStateSnapshot.h"
40 #include "cmStateTypes.h"
41 #include "cmStringAlgorithms.h"
42 #include "cmSystemTools.h"
44 #include "cmTargetDepend.h"
45 #include "cmVersion.h"
48 const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja";
49 const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE = "rules.ninja";
50 const char* cmGlobalNinjaGenerator::INDENT = " ";
52 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd .";
54 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":";
57 void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
59 for (int i = 0; i < count; ++i) {
60 os << cmGlobalNinjaGenerator::INDENT;
64 void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os)
66 os << "# ======================================"
67 "=======================================\n";
70 void cmGlobalNinjaGenerator::WriteComment(std::ostream& os,
71 const std::string& comment)
73 if (comment.empty()) {
77 std::string::size_type lpos = 0;
78 std::string::size_type rpos;
79 os << "\n#############################################\n";
80 while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
81 os << "# " << comment.substr(lpos, rpos - lpos) << "\n";
84 os << "# " << comment.substr(lpos) << "\n\n";
87 std::unique_ptr<cmLinkLineComputer>
88 cmGlobalNinjaGenerator::CreateLinkLineComputer(
89 cmOutputConverter* outputConverter,
90 cmStateDirectory const& /* stateDir */) const
92 return std::unique_ptr<cmLinkLineComputer>(
93 cm::make_unique<cmNinjaLinkLineComputer>(
95 this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this));
98 std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name)
100 // Ninja rule names must match "[a-zA-Z0-9_.-]+". Use ".xx" to encode
101 // "." and all invalid characters as hexadecimal.
103 for (char i : name) {
104 if (isalnum(i) || i == '_' || i == '-') {
108 sprintf(buf, ".%02x", static_cast<unsigned int>(i));
115 std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
117 std::string result = lit;
118 cmSystemTools::ReplaceString(result, "$", "$$");
119 cmSystemTools::ReplaceString(result, "\n", "$\n");
120 if (this->IsMultiConfig()) {
121 cmSystemTools::ReplaceString(result,
122 cmStrCat('$', this->GetCMakeCFGIntDir()),
123 this->GetCMakeCFGIntDir());
128 std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
130 std::string result = path;
132 if (this->IsGCCOnWindows())
133 std::replace(result.begin(), result.end(), '\\', '/');
135 std::replace(result.begin(), result.end(), '/', '\\');
137 result = EncodeLiteral(result);
138 cmSystemTools::ReplaceString(result, " ", "$ ");
139 cmSystemTools::ReplaceString(result, ":", "$:");
143 void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
144 cmNinjaBuild const& build,
146 bool* usedResponseFile)
148 // Make sure there is a rule.
149 if (build.Rule.empty()) {
150 cmSystemTools::Error("No rule for WriteBuild! called with comment: " +
155 // Make sure there is at least one output file.
156 if (build.Outputs.empty()) {
157 cmSystemTools::Error(
158 "No output files for WriteBuild! called with comment: " + build.Comment);
162 cmGlobalNinjaGenerator::WriteComment(os, build.Comment);
164 // Write output files.
165 std::string buildStr("build");
167 // Write explicit outputs
168 for (std::string const& output : build.Outputs) {
169 buildStr += " " + EncodePath(output);
170 if (this->ComputingUnknownDependencies) {
171 this->CombinedBuildOutputs.insert(output);
174 // Write implicit outputs
175 if (!build.ImplicitOuts.empty()) {
177 for (std::string const& implicitOut : build.ImplicitOuts) {
178 buildStr += " " + EncodePath(implicitOut);
185 buildStr += build.Rule;
188 std::string arguments;
190 // TODO: Better formatting for when there are multiple input/output files.
192 // Write explicit dependencies.
193 for (std::string const& explicitDep : build.ExplicitDeps) {
194 arguments += " " + EncodePath(explicitDep);
197 // Write implicit dependencies.
198 if (!build.ImplicitDeps.empty()) {
200 for (std::string const& implicitDep : build.ImplicitDeps) {
201 arguments += " " + EncodePath(implicitDep);
205 // Write order-only dependencies.
206 if (!build.OrderOnlyDeps.empty()) {
208 for (std::string const& orderOnlyDep : build.OrderOnlyDeps) {
209 arguments += " " + EncodePath(orderOnlyDep);
216 // Write the variables bound to this build statement.
217 std::string assignments;
219 std::ostringstream variable_assignments;
220 for (auto const& variable : build.Variables) {
221 cmGlobalNinjaGenerator::WriteVariable(
222 variable_assignments, variable.first, variable.second, "", 1);
225 // check if a response file rule should be used
226 assignments = variable_assignments.str();
227 bool useResponseFile = false;
228 if (cmdLineLimit < 0 ||
230 (arguments.size() + buildStr.size() + assignments.size() + 1000) >
231 static_cast<size_t>(cmdLineLimit))) {
232 variable_assignments.str(std::string());
233 cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
234 build.RspFile, "", 1);
235 assignments += variable_assignments.str();
236 useResponseFile = true;
238 if (usedResponseFile) {
239 *usedResponseFile = useResponseFile;
243 os << buildStr << arguments << assignments << "\n";
246 void cmGlobalNinjaGenerator::AddCustomCommandRule()
248 cmNinjaRule rule("CUSTOM_COMMAND");
249 rule.Command = "$COMMAND";
250 rule.Description = "$DESC";
251 rule.Comment = "Rule for running custom commands.";
255 void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
256 const std::string& command, const std::string& description,
257 const std::string& comment, const std::string& depfile,
258 const std::string& job_pool, bool uses_terminal, bool restat,
259 const cmNinjaDeps& outputs, const std::string& config,
260 const cmNinjaDeps& explicitDeps, const cmNinjaDeps& orderOnlyDeps)
262 this->AddCustomCommandRule();
265 cmNinjaBuild build("CUSTOM_COMMAND");
266 build.Comment = comment;
267 build.Outputs = outputs;
268 build.ExplicitDeps = explicitDeps;
269 build.OrderOnlyDeps = orderOnlyDeps;
271 cmNinjaVars& vars = build.Variables;
273 std::string cmd = command; // NOLINT(*)
276 // TODO Shouldn't an empty command be handled by ninja?
279 vars["COMMAND"] = std::move(cmd);
281 vars["DESC"] = EncodeLiteral(description);
283 vars["restat"] = "1";
285 if (uses_terminal && SupportsConsolePool()) {
286 vars["pool"] = "console";
287 } else if (!job_pool.empty()) {
288 vars["pool"] = job_pool;
290 if (!depfile.empty()) {
291 vars["depfile"] = depfile;
293 if (config.empty()) {
294 this->WriteBuild(*this->GetCommonFileStream(), build);
296 this->WriteBuild(*this->GetImplFileStream(config), build);
300 if (this->ComputingUnknownDependencies) {
301 // we need to track every dependency that comes in, since we are trying
302 // to find dependencies that are side effects of build commands
303 for (std::string const& dep : explicitDeps) {
304 this->CombinedCustomCommandExplicitDependencies.insert(dep);
309 void cmGlobalNinjaGenerator::AddMacOSXContentRule()
311 cmNinjaRule rule("COPY_OSX_CONTENT");
312 rule.Command = CMakeCmd() + " -E copy $in $out";
313 rule.Description = "Copying OS X Content $out";
314 rule.Comment = "Rule for copying OS X bundle content file.";
318 void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input,
320 const std::string& config)
322 this->AddMacOSXContentRule();
324 cmNinjaBuild build("COPY_OSX_CONTENT");
325 build.Outputs.push_back(std::move(output));
326 build.ExplicitDeps.push_back(std::move(input));
327 this->WriteBuild(*this->GetImplFileStream(config), build);
331 void cmGlobalNinjaGenerator::WriteRule(std::ostream& os,
332 cmNinjaRule const& rule)
334 // -- Parameter checks
335 // Make sure the rule has a name.
336 if (rule.Name.empty()) {
337 cmSystemTools::Error("No name given for WriteRule! called with comment: " +
342 // Make sure a command is given.
343 if (rule.Command.empty()) {
344 cmSystemTools::Error(
345 "No command given for WriteRule! called with comment: " + rule.Comment);
349 // Make sure response file content is given
350 if (!rule.RspFile.empty() && rule.RspContent.empty()) {
351 cmSystemTools::Error("rspfile but no rspfile_content given for WriteRule! "
352 "called with comment: " +
359 cmGlobalNinjaGenerator::WriteComment(os, rule.Comment);
360 os << "rule " << rule.Name << '\n';
362 // Write rule key/value pairs
363 auto writeKV = [&os](const char* key, std::string const& value) {
364 if (!value.empty()) {
365 cmGlobalNinjaGenerator::Indent(os, 1);
366 os << key << " = " << value << '\n';
370 writeKV("depfile", rule.DepFile);
371 writeKV("deps", rule.DepType);
372 writeKV("command", rule.Command);
373 writeKV("description", rule.Description);
374 if (!rule.RspFile.empty()) {
375 writeKV("rspfile", rule.RspFile);
376 writeKV("rspfile_content", rule.RspContent);
378 writeKV("restat", rule.Restat);
379 if (rule.Generator) {
380 writeKV("generator", "1");
387 void cmGlobalNinjaGenerator::WriteVariable(std::ostream& os,
388 const std::string& name,
389 const std::string& value,
390 const std::string& comment,
393 // Make sure we have a name.
395 cmSystemTools::Error("No name given for WriteVariable! called "
401 // Do not add a variable if the value is empty.
402 std::string val = cmTrimWhitespace(value);
407 cmGlobalNinjaGenerator::WriteComment(os, comment);
408 cmGlobalNinjaGenerator::Indent(os, indent);
409 os << name << " = " << val << "\n";
412 void cmGlobalNinjaGenerator::WriteInclude(std::ostream& os,
413 const std::string& filename,
414 const std::string& comment)
416 cmGlobalNinjaGenerator::WriteComment(os, comment);
417 os << "include " << filename << "\n";
420 void cmGlobalNinjaGenerator::WriteDefault(std::ostream& os,
421 const cmNinjaDeps& targets,
422 const std::string& comment)
424 cmGlobalNinjaGenerator::WriteComment(os, comment);
426 for (std::string const& target : targets) {
432 cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
433 : cmGlobalCommonGenerator(cm)
436 cm->GetState()->SetWindowsShell(true);
438 // // Ninja is not ported to non-Unix OS yet.
439 // this->ForceUnixPaths = true;
440 this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake";
443 // Virtual public methods.
445 std::unique_ptr<cmLocalGenerator> cmGlobalNinjaGenerator::CreateLocalGenerator(
448 return std::unique_ptr<cmLocalGenerator>(
449 cm::make_unique<cmLocalNinjaGenerator>(this, mf));
452 codecvt::Encoding cmGlobalNinjaGenerator::GetMakefileEncoding() const
455 // Ninja on Windows does not support non-ANSI characters.
456 // https://github.com/ninja-build/ninja/issues/1195
457 return codecvt::ANSI;
459 // No encoding conversion needed on other platforms.
460 return codecvt::None;
464 void cmGlobalNinjaGenerator::GetDocumentation(cmDocumentationEntry& entry)
466 entry.Name = cmGlobalNinjaGenerator::GetActualName();
467 entry.Brief = "Generates build.ninja files.";
470 // Implemented in all cmGlobaleGenerator sub-classes.
472 // Source/cmLocalGenerator.cxx
474 void cmGlobalNinjaGenerator::Generate()
476 // Check minimum Ninja version.
477 if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS,
478 this->NinjaVersion.c_str(),
479 RequiredNinjaVersion().c_str())) {
480 std::ostringstream msg;
481 msg << "The detected version of Ninja (" << this->NinjaVersion;
482 msg << ") is less than the version of Ninja required by CMake (";
483 msg << cmGlobalNinjaGenerator::RequiredNinjaVersion() << ").";
484 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
488 if (!this->InspectConfigTypeVariables()) {
491 if (!this->OpenBuildFileStreams()) {
494 if (!this->OpenRulesFileStream()) {
498 for (auto& it : this->Configs) {
499 it.second.TargetDependsClosures.clear();
502 this->InitOutputPathPrefix();
503 this->TargetAll = this->NinjaOutputPath("all");
504 this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
506 this->PolicyCMP0058 =
507 this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
508 cmPolicies::CMP0058);
509 this->ComputingUnknownDependencies =
510 (this->PolicyCMP0058 == cmPolicies::OLD ||
511 this->PolicyCMP0058 == cmPolicies::WARN);
513 this->cmGlobalGenerator::Generate();
515 this->WriteAssumedSourceDependencies();
516 this->WriteTargetAliases(*this->GetCommonFileStream());
517 this->WriteFolderTargets(*this->GetCommonFileStream());
518 this->WriteUnknownExplicitDependencies(*this->GetCommonFileStream());
519 this->WriteBuiltinTargets(*this->GetCommonFileStream());
521 if (cmSystemTools::GetErrorOccuredFlag()) {
522 this->RulesFileStream->setstate(std::ios::failbit);
523 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
524 this->GetImplFileStream(config)->setstate(std::ios::failbit);
525 this->GetConfigFileStream(config)->setstate(std::ios::failbit);
527 this->GetCommonFileStream()->setstate(std::ios::failbit);
530 this->CloseCompileCommandsStream();
531 this->CloseRulesFileStream();
532 this->CloseBuildFileStreams();
535 // The ninja tools will not be able to update metadata on Windows
536 // when we are re-generating inside an existing 'ninja' invocation
537 // because the outer tool has the files open for write.
538 if (!this->GetCMakeInstance()->GetRegenerateDuringBuild())
541 this->CleanMetaData();
545 void cmGlobalNinjaGenerator::CleanMetaData()
547 auto run_ninja_tool = [this](std::vector<char const*> const& args) {
548 std::vector<std::string> command;
549 command.push_back(this->NinjaCommand);
550 command.emplace_back("-C");
551 command.emplace_back(this->GetCMakeInstance()->GetHomeOutputDirectory());
552 command.emplace_back("-t");
553 for (auto const& arg : args) {
554 command.emplace_back(arg);
557 if (!cmSystemTools::RunSingleCommand(command, nullptr, &error, nullptr,
559 cmSystemTools::OUTPUT_NONE)) {
560 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
562 cmJoin(command, "' '") +
566 cmSystemTools::SetFatalErrorOccured();
570 // Can the tools below expect 'build.ninja' to be loadable?
571 bool const expectBuildManifest =
572 !this->IsMultiConfig() && this->OutputPathPrefix.empty();
574 // Skip some ninja tools if they need 'build.ninja' but it is missing.
575 bool const missingBuildManifest = expectBuildManifest &&
576 (this->NinjaSupportsCleanDeadTool ||
577 this->NinjaSupportsUnconditionalRecompactTool) &&
578 !cmSystemTools::FileExists("build.ninja");
580 // The `cleandead` tool needs to know about all outputs in the build we just
581 // wrote out. Ninja-Multi doesn't have a single `build.ninja` we can use that
582 // is the union of all generated configurations, so we can't run it reliably
584 if (this->NinjaSupportsCleanDeadTool && expectBuildManifest &&
585 !missingBuildManifest) {
586 run_ninja_tool({ "cleandead" });
588 // The `recompact` tool loads the manifest. As above, we don't have a single
589 // `build.ninja` to load for this in Ninja-Multi. This may be relaxed in the
590 // future pending further investigation into how Ninja works upstream
592 if (this->NinjaSupportsUnconditionalRecompactTool && expectBuildManifest &&
593 !missingBuildManifest) {
594 run_ninja_tool({ "recompact" });
596 if (this->NinjaSupportsRestatTool && this->OutputPathPrefix.empty()) {
597 // XXX(ninja): We only list `build.ninja` entry files here because CMake
598 // *always* rewrites these files on a reconfigure. If CMake ever gets
599 // smarter about this, all CMake-time created/edited files listed as
600 // outputs for the reconfigure build statement will need to be listed here.
602 this->AddRebuildManifestOutputs(outputs);
603 std::vector<const char*> args;
604 args.reserve(outputs.size() + 1);
605 args.push_back("restat");
606 for (auto const& output : outputs) {
607 args.push_back(output.c_str());
609 run_ninja_tool(args);
613 bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
615 if (!this->cmGlobalGenerator::FindMakeProgram(mf)) {
618 if (const char* ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
619 this->NinjaCommand = ninjaCommand;
620 std::vector<std::string> command;
621 command.push_back(this->NinjaCommand);
622 command.emplace_back("--version");
625 if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr,
627 cmSystemTools::OUTPUT_NONE)) {
628 mf->IssueMessage(MessageType::FATAL_ERROR,
629 "Running\n '" + cmJoin(command, "' '") +
633 cmSystemTools::SetFatalErrorOccured();
636 this->NinjaVersion = cmTrimWhitespace(version);
637 this->CheckNinjaFeatures();
642 void cmGlobalNinjaGenerator::CheckNinjaFeatures()
644 this->NinjaSupportsConsolePool = !cmSystemTools::VersionCompare(
645 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
646 RequiredNinjaVersionForConsolePool().c_str());
647 this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare(
648 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
649 cmGlobalNinjaGenerator::RequiredNinjaVersionForImplicitOuts().c_str());
650 this->NinjaSupportsManifestRestat = !cmSystemTools::VersionCompare(
651 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
652 RequiredNinjaVersionForManifestRestat().c_str());
653 this->NinjaSupportsMultilineDepfile = !cmSystemTools::VersionCompare(
654 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
655 RequiredNinjaVersionForMultilineDepfile().c_str());
656 this->NinjaSupportsDyndeps = !cmSystemTools::VersionCompare(
657 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
658 RequiredNinjaVersionForDyndeps().c_str());
659 if (!this->NinjaSupportsDyndeps) {
660 // The ninja version number is not new enough to have upstream support.
661 // Our ninja branch adds ".dyndep-#" to its version number,
662 // where '#' is a feature-specific version number. Extract it.
663 static std::string const k_DYNDEP_ = ".dyndep-";
664 std::string::size_type pos = this->NinjaVersion.find(k_DYNDEP_);
665 if (pos != std::string::npos) {
666 const char* fv = &this->NinjaVersion[pos + k_DYNDEP_.size()];
667 unsigned long dyndep = 0;
668 cmStrToULong(fv, &dyndep);
670 this->NinjaSupportsDyndeps = true;
674 this->NinjaSupportsCleanDeadTool = !cmSystemTools::VersionCompare(
675 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
676 RequiredNinjaVersionForCleanDeadTool().c_str());
677 this->NinjaSupportsUnconditionalRecompactTool =
678 !cmSystemTools::VersionCompare(
679 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
680 RequiredNinjaVersionForUnconditionalRecompactTool().c_str());
681 this->NinjaSupportsRestatTool = !cmSystemTools::VersionCompare(
682 cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
683 RequiredNinjaVersionForRestatTool().c_str());
686 bool cmGlobalNinjaGenerator::CheckLanguages(
687 std::vector<std::string> const& languages, cmMakefile* mf) const
689 if (cmContains(languages, "Fortran")) {
690 return this->CheckFortran(mf);
692 if (cmContains(languages, "Swift")) {
693 const std::string architectures =
694 mf->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
695 if (architectures.find_first_of(';') != std::string::npos) {
696 mf->IssueMessage(MessageType::FATAL_ERROR,
697 "multiple values for CMAKE_OSX_ARCHITECTURES not "
698 "supported with Swift");
699 cmSystemTools::SetFatalErrorOccured();
706 bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
708 if (this->NinjaSupportsDyndeps) {
712 std::ostringstream e;
713 /* clang-format off */
715 "The Ninja generator does not support Fortran using Ninja version\n"
716 " " + this->NinjaVersion + "\n"
717 "due to lack of required features. "
718 "Kitware has implemented the required features and they have been "
719 "merged to upstream ninja for inclusion in Ninja 1.10 and higher. "
720 "As of this version of CMake, Ninja 1.10 has not been released. "
721 "Meanwhile, Kitware maintains a branch of Ninja at:\n"
722 " https://github.com/Kitware/ninja/tree/features-for-fortran#readme\n"
723 "with the required features. "
724 "One may build ninja from that branch to get support for Fortran."
726 /* clang-format on */
727 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
728 cmSystemTools::SetFatalErrorOccured();
732 void cmGlobalNinjaGenerator::EnableLanguage(
733 std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
735 if (this->IsMultiConfig()) {
736 if (!mf->GetDefinition("CMAKE_CONFIGURATION_TYPES")) {
737 mf->AddCacheDefinition(
738 "CMAKE_CONFIGURATION_TYPES", "Debug;Release;RelWithDebInfo",
739 "Semicolon separated list of supported configuration types, only "
740 "supports Debug, Release, MinSizeRel, and RelWithDebInfo, anything "
741 "else will be ignored",
742 cmStateEnums::STRING);
746 this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
747 for (std::string const& l : langs) {
751 this->ResolveLanguageCompiler(l, mf, optional);
754 const bool clangGnuMode =
755 ((mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "Clang") &&
756 (mf->GetSafeDefinition("CMAKE_C_COMPILER_FRONTEND_VARIANT") == "GNU")) ||
757 ((mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "Clang") &&
758 (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_FRONTEND_VARIANT") == "GNU"));
761 ((mf->GetSafeDefinition("CMAKE_C_SIMULATE_ID") != "MSVC") &&
762 (mf->GetSafeDefinition("CMAKE_CXX_SIMULATE_ID") != "MSVC") &&
763 (mf->IsOn("CMAKE_COMPILER_IS_MINGW") ||
764 (mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "GNU") ||
765 (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "GNU") ||
766 (mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "Clang") ||
767 (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "Clang") ||
768 (mf->GetSafeDefinition("CMAKE_C_COMPILER_ID") == "QCC") ||
769 (mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == "QCC")))) {
770 this->UsingGCCOnWindows = true;
776 // cmGlobalUnixMakefileGenerator3
777 // cmGlobalGhsMultiGenerator
778 // cmGlobalVisualStudio10Generator
779 // cmGlobalVisualStudio7Generator
780 // cmGlobalXCodeGenerator
782 // cmGlobalGenerator::Build()
783 std::vector<cmGlobalGenerator::GeneratedMakeCommand>
784 cmGlobalNinjaGenerator::GenerateBuildCommand(
785 const std::string& makeProgram, const std::string& /*projectName*/,
786 const std::string& /*projectDir*/,
787 std::vector<std::string> const& targetNames, const std::string& config,
788 bool /*fast*/, int jobs, bool verbose,
789 std::vector<std::string> const& makeOptions)
791 GeneratedMakeCommand makeCommand;
792 makeCommand.Add(this->SelectMakeProgram(makeProgram));
795 makeCommand.Add("-v");
798 if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) &&
799 (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) {
800 makeCommand.Add("-j", std::to_string(jobs));
803 this->AppendNinjaFileArgument(makeCommand, config);
805 makeCommand.Add(makeOptions.begin(), makeOptions.end());
806 for (const auto& tname : targetNames) {
807 if (!tname.empty()) {
808 makeCommand.Add(tname);
811 return { std::move(makeCommand) };
814 // Non-virtual public methods.
816 void cmGlobalNinjaGenerator::AddRule(cmNinjaRule const& rule)
818 // Do not add the same rule twice.
819 if (!this->Rules.insert(rule.Name).second) {
822 // Store command length
823 this->RuleCmdLength[rule.Name] = static_cast<int>(rule.Command.size());
825 cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, rule);
828 bool cmGlobalNinjaGenerator::HasRule(const std::string& name)
830 return (this->Rules.find(name) != this->Rules.end());
833 // Private virtual overrides
835 std::string cmGlobalNinjaGenerator::GetEditCacheCommand() const
837 // Ninja by design does not run interactive tools in the terminal,
838 // so our only choice is cmake-gui.
839 return cmSystemTools::GetCMakeGUICommand();
842 void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
843 cmGeneratorTarget* gt) const
845 // Compute full path to object file directory for this target.
846 std::string dir = cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(),
847 '/', gt->LocalGenerator->GetTargetDirectory(gt),
848 '/', this->GetCMakeCFGIntDir(), '/');
849 gt->ObjectDirectory = dir;
854 bool cmGlobalNinjaGenerator::OpenBuildFileStreams()
856 if (!this->OpenFileStream(this->BuildFileStream,
857 cmGlobalNinjaGenerator::NINJA_BUILD_FILE)) {
861 // Write a comment about this file.
862 *this->BuildFileStream
863 << "# This file contains all the build statements describing the\n"
864 << "# compilation DAG.\n\n";
869 bool cmGlobalNinjaGenerator::OpenFileStream(
870 std::unique_ptr<cmGeneratedFileStream>& stream, const std::string& name)
872 // Get a stream where to generate things.
874 // Compute Ninja's build file path.
876 cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/', name);
877 stream = cm::make_unique<cmGeneratedFileStream>(
878 path, false, this->GetMakefileEncoding());
880 // An error message is generated by the constructor if it cannot
885 // Write the do not edit header.
886 this->WriteDisclaimer(*stream);
892 cm::optional<std::set<std::string>> cmGlobalNinjaGenerator::ListSubsetWithAll(
893 const std::set<std::string>& all, const std::set<std::string>& defaults,
894 const std::vector<std::string>& items)
896 std::set<std::string> result;
898 for (auto const& item : items) {
900 if (items.size() == 1) {
905 } else if (all.count(item)) {
912 return cm::make_optional(result);
915 void cmGlobalNinjaGenerator::CloseBuildFileStreams()
917 if (this->BuildFileStream) {
918 this->BuildFileStream.reset();
920 cmSystemTools::Error("Build file stream was not open.");
924 bool cmGlobalNinjaGenerator::OpenRulesFileStream()
926 if (!this->OpenFileStream(this->RulesFileStream,
927 cmGlobalNinjaGenerator::NINJA_RULES_FILE)) {
931 // Write comment about this file.
932 /* clang-format off */
933 *this->RulesFileStream
934 << "# This file contains all the rules used to get the outputs files\n"
935 << "# built from the input files.\n"
936 << "# It is included in the main '" << NINJA_BUILD_FILE << "'.\n\n"
938 /* clang-format on */
942 void cmGlobalNinjaGenerator::CloseRulesFileStream()
944 if (this->RulesFileStream) {
945 this->RulesFileStream.reset();
947 cmSystemTools::Error("Rules file stream was not open.");
951 static void EnsureTrailingSlash(std::string& path)
956 std::string::value_type last = path.back();
968 std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath(
969 const std::string& path) const
971 auto const f = ConvertToNinjaPathCache.find(path);
972 if (f != ConvertToNinjaPathCache.end()) {
977 cm::static_reference_cast<cmLocalNinjaGenerator>(this->LocalGenerators[0]);
978 std::string const& bin_dir = ng.GetState()->GetBinaryDirectory();
979 std::string convPath = ng.MaybeConvertToRelativePath(bin_dir, path);
980 convPath = this->NinjaOutputPath(convPath);
982 std::replace(convPath.begin(), convPath.end(), '/', '\\');
984 return ConvertToNinjaPathCache.emplace(path, std::move(convPath))
988 void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName,
989 const std::string& config)
991 this->Configs[config].AdditionalCleanFiles.emplace(std::move(fileName));
994 void cmGlobalNinjaGenerator::AddCXXCompileCommand(
995 const std::string& commandLine, const std::string& sourceFile)
997 // Compute Ninja's build file path.
998 std::string buildFileDir =
999 this->GetCMakeInstance()->GetHomeOutputDirectory();
1000 if (!this->CompileCommandsStream) {
1001 std::string buildFilePath = buildFileDir + "/compile_commands.json";
1002 if (this->ComputingUnknownDependencies) {
1003 this->CombinedBuildOutputs.insert(
1004 this->NinjaOutputPath("compile_commands.json"));
1007 // Get a stream where to generate things.
1008 this->CompileCommandsStream =
1009 cm::make_unique<cmGeneratedFileStream>(buildFilePath);
1010 *this->CompileCommandsStream << "[";
1012 *this->CompileCommandsStream << "," << std::endl;
1015 std::string sourceFileName = sourceFile;
1016 if (!cmSystemTools::FileIsFullPath(sourceFileName)) {
1017 sourceFileName = cmSystemTools::CollapseFullPath(
1018 sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory());
1021 /* clang-format off */
1022 *this->CompileCommandsStream << "\n{\n"
1023 << R"( "directory": ")"
1024 << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n"
1025 << R"( "command": ")"
1026 << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n"
1028 << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\"\n"
1030 /* clang-format on */
1033 void cmGlobalNinjaGenerator::CloseCompileCommandsStream()
1035 if (this->CompileCommandsStream) {
1036 *this->CompileCommandsStream << "\n]";
1037 this->CompileCommandsStream.reset();
1041 void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os)
1043 os << "# CMAKE generated file: DO NOT EDIT!\n"
1044 << "# Generated by \"" << this->GetName() << "\""
1045 << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
1046 << cmVersion::GetMinorVersion() << "\n\n";
1049 void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies()
1051 for (auto const& asd : this->AssumedSourceDependencies) {
1052 cmNinjaDeps orderOnlyDeps;
1053 std::copy(asd.second.begin(), asd.second.end(),
1054 std::back_inserter(orderOnlyDeps));
1055 WriteCustomCommandBuild(/*command=*/"", /*description=*/"",
1056 "Assume dependencies for generated source file.",
1057 /*depfile*/ "", /*job_pool*/ "",
1058 /*uses_terminal*/ false,
1059 /*restat*/ true, cmNinjaDeps(1, asd.first), "",
1060 cmNinjaDeps(), orderOnlyDeps);
1064 std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
1065 cmGeneratorTarget const* target, const std::string& config)
1067 return "cmake_object_order_depends_target_" + target->GetName() + "_" +
1071 void cmGlobalNinjaGenerator::AppendTargetOutputs(
1072 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1073 const std::string& config, cmNinjaTargetDepends depends)
1075 // for frameworks, we want the real name, not smple name
1076 // frameworks always appear versioned, and the build.ninja
1077 // will always attempt to manage symbolic links instead
1078 // of letting cmOSXBundleGenerator do it.
1079 bool realname = target->IsFrameworkOnApple();
1081 switch (target->GetType()) {
1082 case cmStateEnums::SHARED_LIBRARY:
1083 case cmStateEnums::STATIC_LIBRARY:
1084 case cmStateEnums::MODULE_LIBRARY: {
1085 if (depends == DependOnTargetOrdering) {
1086 outputs.push_back(OrderDependsTargetForTarget(target, config));
1091 case cmStateEnums::EXECUTABLE: {
1092 outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
1093 config, cmStateEnums::RuntimeBinaryArtifact, realname)));
1096 case cmStateEnums::OBJECT_LIBRARY: {
1097 if (depends == DependOnTargetOrdering) {
1098 outputs.push_back(OrderDependsTargetForTarget(target, config));
1103 case cmStateEnums::GLOBAL_TARGET:
1104 case cmStateEnums::UTILITY: {
1106 target->GetLocalGenerator()->GetCurrentBinaryDirectory() +
1107 std::string("/") + target->GetName();
1108 std::string output = this->ConvertToNinjaPath(path);
1109 if (target->Target->IsPerConfig()) {
1110 output = this->BuildAlias(output, config);
1112 outputs.push_back(output);
1121 void cmGlobalNinjaGenerator::AppendTargetDepends(
1122 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1123 const std::string& config, const std::string& fileConfig,
1124 cmNinjaTargetDepends depends)
1126 if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
1127 // These depend only on other CMake-provided targets, e.g. "all".
1128 for (BT<std::pair<std::string, bool>> const& util :
1129 target->GetUtilities()) {
1131 target->GetLocalGenerator()->GetCurrentBinaryDirectory() + "/" +
1133 outputs.push_back(this->BuildAlias(this->ConvertToNinjaPath(d), config));
1137 for (cmTargetDepend const& targetDep :
1138 this->GetTargetDirectDepends(target)) {
1139 if (targetDep->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
1142 if (targetDep.IsCross()) {
1143 this->AppendTargetOutputs(targetDep, outs, fileConfig, depends);
1145 this->AppendTargetOutputs(targetDep, outs, config, depends);
1148 std::sort(outs.begin(), outs.end());
1149 cm::append(outputs, outs);
1153 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1154 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1155 const std::string& config)
1158 this->AppendTargetDependsClosure(target, outs, config, true);
1159 cm::append(outputs, outs);
1162 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1163 cmGeneratorTarget const* target, cmNinjaOuts& outputs,
1164 const std::string& config, bool omit_self)
1167 // try to locate the target in the cache
1168 auto find = this->Configs[config].TargetDependsClosures.lower_bound(target);
1170 if (find == this->Configs[config].TargetDependsClosures.end() ||
1171 find->first != target) {
1172 // We now calculate the closure outputs by inspecting the dependent
1173 // targets recursively.
1174 // For that we have to distinguish between a local result set that is only
1175 // relevant for filling the cache entries properly isolated and a global
1176 // result set that is relevant for the result of the top level call to
1177 // AppendTargetDependsClosure.
1178 cmNinjaOuts this_outs; // this will be the new cache entry
1180 for (auto const& dep_target : this->GetTargetDirectDepends(target)) {
1181 if (dep_target->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
1182 (this->EnableCrossConfigBuild() && !dep_target.IsCross())) {
1186 // Collect the dependent targets for _this_ target
1187 this->AppendTargetDependsClosure(dep_target, this_outs, config, false);
1189 find = this->Configs[config].TargetDependsClosures.emplace_hint(
1190 find, target, std::move(this_outs));
1193 // now fill the outputs of the final result from the newly generated cache
1195 outputs.insert(find->second.begin(), find->second.end());
1197 // finally generate the outputs of the target itself, if applicable
1200 this->AppendTargetOutputs(target, outs, config);
1202 outputs.insert(outs.begin(), outs.end());
1205 void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias,
1206 cmGeneratorTarget* target,
1207 const std::string& config)
1209 std::string outputPath = this->NinjaOutputPath(alias);
1210 std::string buildAlias = this->BuildAlias(outputPath, config);
1211 cmNinjaDeps outputs;
1212 this->AppendTargetOutputs(target, outputs, config);
1213 // Mark the target's outputs as ambiguous to ensure that no other target
1214 // uses the output as an alias.
1215 for (std::string const& output : outputs) {
1216 this->TargetAliases[output].GeneratorTarget = nullptr;
1217 this->DefaultTargetAliases[output].GeneratorTarget = nullptr;
1218 for (const std::string& config2 :
1219 this->Makefiles.front()->GetGeneratorConfigs()) {
1220 this->Configs[config2].TargetAliases[output].GeneratorTarget = nullptr;
1224 // Insert the alias into the map. If the alias was already present in the
1225 // map and referred to another target, mark it as ambiguous.
1227 ta.GeneratorTarget = target;
1230 auto newAliasGlobal =
1231 this->TargetAliases.insert(std::make_pair(buildAlias, ta));
1232 if (newAliasGlobal.second &&
1233 newAliasGlobal.first->second.GeneratorTarget != target) {
1234 newAliasGlobal.first->second.GeneratorTarget = nullptr;
1237 auto newAliasConfig =
1238 this->Configs[config].TargetAliases.insert(std::make_pair(outputPath, ta));
1239 if (newAliasConfig.second &&
1240 newAliasConfig.first->second.GeneratorTarget != target) {
1241 newAliasConfig.first->second.GeneratorTarget = nullptr;
1243 if (this->DefaultConfigs.count(config)) {
1244 auto newAliasDefaultGlobal =
1245 this->DefaultTargetAliases.insert(std::make_pair(outputPath, ta));
1246 if (newAliasDefaultGlobal.second &&
1247 newAliasDefaultGlobal.first->second.GeneratorTarget != target) {
1248 newAliasDefaultGlobal.first->second.GeneratorTarget = nullptr;
1253 void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
1255 cmGlobalNinjaGenerator::WriteDivider(os);
1256 os << "# Target aliases.\n\n";
1258 cmNinjaBuild build("phony");
1259 build.Outputs.emplace_back();
1260 for (auto const& ta : this->TargetAliases) {
1261 // Don't write ambiguous aliases.
1262 if (!ta.second.GeneratorTarget) {
1266 // Don't write alias if there is a already a custom command with
1268 if (this->HasCustomCommandOutput(ta.first)) {
1272 build.Outputs.front() = ta.first;
1273 build.ExplicitDeps.clear();
1274 if (ta.second.Config == "all") {
1275 for (auto const& config : this->CrossConfigs) {
1276 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1277 build.ExplicitDeps, config);
1280 this->AppendTargetOutputs(ta.second.GeneratorTarget, build.ExplicitDeps,
1283 this->WriteBuild(this->EnableCrossConfigBuild() &&
1284 (ta.second.Config == "all" ||
1285 this->CrossConfigs.count(ta.second.Config))
1287 : *this->GetImplFileStream(ta.second.Config),
1291 if (this->IsMultiConfig()) {
1292 for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs()) {
1293 for (auto const& ta : this->Configs[config].TargetAliases) {
1294 // Don't write ambiguous aliases.
1295 if (!ta.second.GeneratorTarget) {
1299 // Don't write alias if there is a already a custom command with
1301 if (this->HasCustomCommandOutput(ta.first)) {
1305 build.Outputs.front() = ta.first;
1306 build.ExplicitDeps.clear();
1307 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1308 build.ExplicitDeps, config);
1309 this->WriteBuild(*this->GetConfigFileStream(config), build);
1313 if (!this->DefaultConfigs.empty()) {
1314 for (auto const& ta : this->DefaultTargetAliases) {
1315 // Don't write ambiguous aliases.
1316 if (!ta.second.GeneratorTarget) {
1320 // Don't write alias if there is a already a custom command with
1322 if (this->HasCustomCommandOutput(ta.first)) {
1326 build.Outputs.front() = ta.first;
1327 build.ExplicitDeps.clear();
1328 for (auto const& config : this->DefaultConfigs) {
1329 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1330 build.ExplicitDeps, config);
1332 this->WriteBuild(*this->GetDefaultFileStream(), build);
1338 void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
1340 cmGlobalNinjaGenerator::WriteDivider(os);
1341 os << "# Folder targets.\n\n";
1343 std::map<std::string, DirectoryTarget> dirTargets =
1344 this->ComputeDirectoryTargets();
1346 for (auto const& it : dirTargets) {
1347 cmNinjaBuild build("phony");
1348 cmGlobalNinjaGenerator::WriteDivider(os);
1349 std::string const& currentBinaryDir = it.first;
1350 DirectoryTarget const& dt = it.second;
1351 std::vector<std::string> configs;
1352 dt.LG->GetMakefile()->GetConfigurations(configs, true);
1353 if (configs.empty()) {
1354 configs.emplace_back();
1358 cmNinjaDeps configDeps;
1359 build.Comment = "Folder: " + currentBinaryDir;
1360 build.Outputs.emplace_back();
1361 for (auto const& config : configs) {
1362 build.ExplicitDeps.clear();
1363 build.Outputs.front() = this->BuildAlias(
1364 this->ConvertToNinjaPath(currentBinaryDir + "/all"), config);
1365 configDeps.emplace_back(build.Outputs.front());
1366 for (DirectoryTarget::Target const& t : dt.Targets) {
1367 if (!t.ExcludeFromAll) {
1368 this->AppendTargetOutputs(t.GT, build.ExplicitDeps, config);
1371 for (DirectoryTarget::Dir const& d : dt.Children) {
1372 if (!d.ExcludeFromAll) {
1373 build.ExplicitDeps.emplace_back(this->BuildAlias(
1374 this->ConvertToNinjaPath(d.Path + "/all"), config));
1378 this->WriteBuild(this->EnableCrossConfigBuild() &&
1379 this->CrossConfigs.count(config)
1381 : *this->GetImplFileStream(config),
1385 // Add shortcut target
1386 if (this->IsMultiConfig()) {
1387 for (auto const& config : configs) {
1388 build.ExplicitDeps = { this->BuildAlias(
1389 this->ConvertToNinjaPath(currentBinaryDir + "/all"), config) };
1390 build.Outputs.front() =
1391 this->ConvertToNinjaPath(currentBinaryDir + "/all");
1392 this->WriteBuild(*this->GetConfigFileStream(config), build);
1395 if (!this->DefaultFileConfig.empty()) {
1396 build.ExplicitDeps.clear();
1397 for (auto const& config : this->DefaultConfigs) {
1398 build.ExplicitDeps.push_back(this->BuildAlias(
1399 this->ConvertToNinjaPath(currentBinaryDir + "/all"), config));
1401 build.Outputs.front() =
1402 this->ConvertToNinjaPath(currentBinaryDir + "/all");
1403 this->WriteBuild(*this->GetDefaultFileStream(), build);
1407 // Add target for all configs
1408 if (this->EnableCrossConfigBuild()) {
1409 build.ExplicitDeps.clear();
1410 for (auto const& config : this->CrossConfigs) {
1411 build.ExplicitDeps.push_back(this->BuildAlias(
1412 this->ConvertToNinjaPath(currentBinaryDir + "/all"), config));
1414 build.Outputs.front() = this->BuildAlias(
1415 this->ConvertToNinjaPath(currentBinaryDir + "/all"), "all");
1416 this->WriteBuild(os, build);
1421 void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os)
1423 if (!this->ComputingUnknownDependencies) {
1427 // We need to collect the set of known build outputs.
1428 // Start with those generated by WriteBuild calls.
1429 // No other method needs this so we can take ownership
1430 // of the set locally and throw it out when we are done.
1431 std::set<std::string> knownDependencies;
1432 knownDependencies.swap(this->CombinedBuildOutputs);
1434 // now write out the unknown explicit dependencies.
1436 // union the configured files, evaluations files and the
1437 // CombinedBuildOutputs,
1438 // and then difference with CombinedExplicitDependencies to find the explicit
1439 // dependencies that we have no rule for
1441 cmGlobalNinjaGenerator::WriteDivider(os);
1442 /* clang-format off */
1443 os << "# Unknown Build Time Dependencies.\n"
1444 << "# Tell Ninja that they may appear as side effects of build rules\n"
1445 << "# otherwise ordered by order-only dependencies.\n\n";
1446 /* clang-format on */
1448 // get the list of files that cmake itself has generated as a
1449 // product of configuration.
1451 for (const auto& lg : this->LocalGenerators) {
1452 // get the vector of files created by this makefile and convert them
1453 // to ninja paths, which are all relative in respect to the build directory
1454 for (std::string const& file : lg->GetMakefile()->GetOutputFiles()) {
1455 knownDependencies.insert(this->ConvertToNinjaPath(file));
1457 if (!this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1458 // get list files which are implicit dependencies as well and will be
1459 // phony for rebuild manifest
1460 for (std::string const& j : lg->GetMakefile()->GetListFiles()) {
1461 knownDependencies.insert(this->ConvertToNinjaPath(j));
1464 for (const auto& li : lg->GetMakefile()->GetEvaluationFiles()) {
1465 // get all the files created by generator expressions and convert them
1467 for (std::string const& evaluationFile : li->GetFiles()) {
1468 knownDependencies.insert(this->ConvertToNinjaPath(evaluationFile));
1472 knownDependencies.insert(this->CMakeCacheFile);
1474 for (auto const& ta : this->TargetAliases) {
1475 knownDependencies.insert(this->ConvertToNinjaPath(ta.first));
1478 // remove all source files we know will exist.
1479 for (auto const& i : this->AssumedSourceDependencies) {
1480 knownDependencies.insert(this->ConvertToNinjaPath(i.first));
1483 // now we difference with CombinedCustomCommandExplicitDependencies to find
1484 // the list of items we know nothing about.
1485 // We have encoded all the paths in CombinedCustomCommandExplicitDependencies
1486 // and knownDependencies so no matter if unix or windows paths they
1487 // should all match now.
1489 std::vector<std::string> unknownExplicitDepends;
1490 this->CombinedCustomCommandExplicitDependencies.erase(this->TargetAll);
1492 std::set_difference(this->CombinedCustomCommandExplicitDependencies.begin(),
1493 this->CombinedCustomCommandExplicitDependencies.end(),
1494 knownDependencies.begin(), knownDependencies.end(),
1495 std::back_inserter(unknownExplicitDepends));
1497 std::vector<std::string> warnExplicitDepends;
1498 if (!unknownExplicitDepends.empty()) {
1499 cmake* cmk = this->GetCMakeInstance();
1500 std::string const& buildRoot = cmk->GetHomeOutputDirectory();
1501 bool const inSource = (buildRoot == cmk->GetHomeDirectory());
1502 bool const warn = (!inSource && (this->PolicyCMP0058 == cmPolicies::WARN));
1503 cmNinjaBuild build("phony");
1504 build.Outputs.emplace_back("");
1505 for (std::string const& ued : unknownExplicitDepends) {
1506 // verify the file is in the build directory
1507 std::string const absDepPath =
1508 cmSystemTools::CollapseFullPath(ued, buildRoot);
1509 if (cmSystemTools::IsSubDirectory(absDepPath, buildRoot)) {
1510 // Generate phony build statement
1511 build.Outputs[0] = ued;
1512 this->WriteBuild(os, build);
1513 // Add to warning on demand
1514 if (warn && warnExplicitDepends.size() < 10) {
1515 warnExplicitDepends.push_back(ued);
1521 if (!warnExplicitDepends.empty()) {
1522 std::ostringstream w;
1523 /* clang-format off */
1524 w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0058) << "\n"
1525 "This project specifies custom command DEPENDS on files "
1526 "in the build tree that are not specified as the OUTPUT or "
1527 "BYPRODUCTS of any add_custom_command or add_custom_target:\n"
1528 " " << cmJoin(warnExplicitDepends, "\n ") <<
1530 "For compatibility with versions of CMake that did not have "
1531 "the BYPRODUCTS option, CMake is generating phony rules for "
1532 "such files to convince 'ninja' to build."
1534 "Project authors should add the missing BYPRODUCTS or OUTPUT "
1535 "options to the custom commands that produce these files."
1537 /* clang-format on */
1538 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1543 void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os)
1546 cmGlobalNinjaGenerator::WriteDivider(os);
1547 os << "# Built-in targets\n\n";
1549 this->WriteTargetRebuildManifest(os);
1550 this->WriteTargetClean(os);
1551 this->WriteTargetHelp(os);
1553 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
1554 this->WriteTargetDefault(*this->GetConfigFileStream(config));
1557 if (!this->DefaultFileConfig.empty()) {
1558 this->WriteTargetDefault(*this->GetDefaultFileStream());
1562 void cmGlobalNinjaGenerator::WriteTargetDefault(std::ostream& os)
1564 if (!this->HasOutputPathPrefix()) {
1566 all.push_back(this->TargetAll);
1567 cmGlobalNinjaGenerator::WriteDefault(os, all,
1568 "Make the all target the default.");
1572 void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
1574 if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1577 const auto& lg = this->LocalGenerators[0];
1580 cmNinjaRule rule("RERUN_CMAKE");
1582 cmStrCat(CMakeCmd(), " --regenerate-during-build -S",
1583 lg->ConvertToOutputFormat(lg->GetSourceDirectory(),
1584 cmOutputConverter::SHELL),
1586 lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
1587 cmOutputConverter::SHELL));
1588 rule.Description = "Re-running CMake...";
1589 rule.Comment = "Rule for re-running cmake.";
1590 rule.Generator = true;
1591 WriteRule(*this->RulesFileStream, rule);
1594 cmNinjaBuild reBuild("RERUN_CMAKE");
1595 reBuild.Comment = "Re-run CMake if any of its inputs changed.";
1596 this->AddRebuildManifestOutputs(reBuild.Outputs);
1598 for (const auto& localGen : this->LocalGenerators) {
1599 for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) {
1600 reBuild.ImplicitDeps.push_back(this->ConvertToNinjaPath(fi));
1603 reBuild.ImplicitDeps.push_back(this->CMakeCacheFile);
1605 // Use 'console' pool to get non buffered output of the CMake re-run call
1606 // Available since Ninja 1.5
1607 if (SupportsConsolePool()) {
1608 reBuild.Variables["pool"] = "console";
1611 cmake* cm = this->GetCMakeInstance();
1612 if (this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) {
1614 cmNinjaRule rule("VERIFY_GLOBS");
1616 cmStrCat(CMakeCmd(), " -P ",
1617 lg->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
1618 cmOutputConverter::SHELL));
1619 rule.Description = "Re-checking globbed directories...";
1620 rule.Comment = "Rule for re-checking globbed directories.";
1621 rule.Generator = true;
1622 this->WriteRule(*this->RulesFileStream, rule);
1625 cmNinjaBuild phonyBuild("phony");
1626 phonyBuild.Comment = "Phony target to force glob verification run.";
1627 phonyBuild.Outputs.push_back(cm->GetGlobVerifyScript() + "_force");
1628 this->WriteBuild(os, phonyBuild);
1630 reBuild.Variables["restat"] = "1";
1631 std::string const verifyScriptFile =
1632 this->NinjaOutputPath(cm->GetGlobVerifyScript());
1633 std::string const verifyStampFile =
1634 this->NinjaOutputPath(cm->GetGlobVerifyStamp());
1636 cmNinjaBuild vgBuild("VERIFY_GLOBS");
1638 "Re-run CMake to check if globbed directories changed.";
1639 vgBuild.Outputs.push_back(verifyStampFile);
1640 vgBuild.ImplicitDeps = phonyBuild.Outputs;
1641 vgBuild.Variables = reBuild.Variables;
1642 this->WriteBuild(os, vgBuild);
1644 reBuild.Variables.erase("restat");
1645 reBuild.ImplicitDeps.push_back(verifyScriptFile);
1646 reBuild.ExplicitDeps.push_back(verifyStampFile);
1647 } else if (!this->SupportsManifestRestat() &&
1648 cm->DoWriteGlobVerifyTarget()) {
1649 std::ostringstream msg;
1650 msg << "The detected version of Ninja:\n"
1651 << " " << this->NinjaVersion << "\n"
1652 << "is less than the version of Ninja required by CMake for adding "
1653 "restat dependencies to the build.ninja manifest regeneration "
1656 << cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat()
1658 msg << "Any pre-check scripts, such as those generated for file(GLOB "
1659 "CONFIGURE_DEPENDS), will not be run by Ninja.";
1660 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1664 std::sort(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end());
1665 reBuild.ImplicitDeps.erase(
1666 std::unique(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()),
1667 reBuild.ImplicitDeps.end());
1669 this->WriteBuild(os, reBuild);
1672 cmNinjaBuild build("phony");
1673 build.Comment = "A missing CMake input file is not an error.";
1674 std::set_difference(std::make_move_iterator(reBuild.ImplicitDeps.begin()),
1675 std::make_move_iterator(reBuild.ImplicitDeps.end()),
1676 CustomCommandOutputs.begin(),
1677 CustomCommandOutputs.end(),
1678 std::back_inserter(build.Outputs));
1679 this->WriteBuild(os, build);
1683 std::string cmGlobalNinjaGenerator::CMakeCmd() const
1685 const auto& lgen = this->LocalGenerators.at(0);
1686 return lgen->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
1687 cmOutputConverter::SHELL);
1690 std::string cmGlobalNinjaGenerator::NinjaCmd() const
1692 const auto& lgen = this->LocalGenerators[0];
1693 if (lgen != nullptr) {
1694 return lgen->ConvertToOutputFormat(this->NinjaCommand,
1695 cmOutputConverter::SHELL);
1700 bool cmGlobalNinjaGenerator::SupportsConsolePool() const
1702 return this->NinjaSupportsConsolePool;
1705 bool cmGlobalNinjaGenerator::SupportsImplicitOuts() const
1707 return this->NinjaSupportsImplicitOuts;
1710 bool cmGlobalNinjaGenerator::SupportsManifestRestat() const
1712 return this->NinjaSupportsManifestRestat;
1715 bool cmGlobalNinjaGenerator::SupportsMultilineDepfile() const
1717 return this->NinjaSupportsMultilineDepfile;
1720 bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
1722 const auto& lgr = this->LocalGenerators.at(0);
1723 std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake";
1724 std::string cleanScriptAbs =
1725 cmStrCat(lgr->GetBinaryDirectory(), '/', cleanScriptRel);
1726 std::vector<std::string> configs;
1727 this->Makefiles[0]->GetConfigurations(configs, true);
1728 if (configs.empty()) {
1729 configs.emplace_back();
1732 // Check if there are additional files to clean
1734 for (auto const& config : configs) {
1735 auto const it = this->Configs.find(config);
1736 if (it != this->Configs.end() &&
1737 !it->second.AdditionalCleanFiles.empty()) {
1743 // Remove cmake clean script file if it exists
1744 cmSystemTools::RemoveFile(cleanScriptAbs);
1748 // Write cmake clean script file
1750 cmGeneratedFileStream fout(cleanScriptAbs);
1754 fout << "# Additional clean files\ncmake_minimum_required(VERSION 3.16)\n";
1755 for (auto const& config : configs) {
1756 auto const it = this->Configs.find(config);
1757 if (it != this->Configs.end() &&
1758 !it->second.AdditionalCleanFiles.empty()) {
1759 fout << "\nif(\"${CONFIG}\" STREQUAL \"\" OR \"${CONFIG}\" STREQUAL \""
1760 << config << "\")\n";
1761 fout << " file(REMOVE_RECURSE\n";
1762 for (std::string const& acf : it->second.AdditionalCleanFiles) {
1764 << cmOutputConverter::EscapeForCMake(ConvertToNinjaPath(acf))
1768 fout << "endif()\n";
1772 // Register clean script file
1773 lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs);
1777 cmNinjaRule rule("CLEAN_ADDITIONAL");
1778 rule.Command = cmStrCat(
1779 CMakeCmd(), " -DCONFIG=$CONFIG -P ",
1780 lgr->ConvertToOutputFormat(this->NinjaOutputPath(cleanScriptRel),
1781 cmOutputConverter::SHELL));
1782 rule.Description = "Cleaning additional files...";
1783 rule.Comment = "Rule for cleaning additional files.";
1784 WriteRule(*this->RulesFileStream, rule);
1789 cmNinjaBuild build("CLEAN_ADDITIONAL");
1790 build.Comment = "Clean additional files.";
1791 build.Outputs.emplace_back();
1792 for (auto const& config : configs) {
1793 build.Outputs.front() = this->BuildAlias(
1794 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()), config);
1795 build.Variables["CONFIG"] = config;
1796 WriteBuild(os, build);
1798 if (this->IsMultiConfig()) {
1799 build.Outputs.front() =
1800 this->NinjaOutputPath(this->GetAdditionalCleanTargetName());
1801 build.Variables["CONFIG"] = "";
1802 WriteBuild(os, build);
1809 void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
1811 // -- Additional clean target
1812 bool additionalFiles = WriteTargetCleanAdditional(os);
1814 // -- Default clean target
1817 cmNinjaRule rule("CLEAN");
1818 rule.Command = NinjaCmd() + " $FILE_ARG -t clean $TARGETS";
1819 rule.Description = "Cleaning all built files...";
1820 rule.Comment = "Rule for cleaning all built files.";
1821 WriteRule(*this->RulesFileStream, rule);
1824 auto const configs = this->Makefiles.front()->GetGeneratorConfigs();
1828 cmNinjaBuild build("CLEAN");
1829 build.Comment = "Clean all the built files.";
1830 build.Outputs.emplace_back();
1832 for (auto const& config : configs) {
1833 build.Outputs.front() = this->BuildAlias(
1834 this->NinjaOutputPath(this->GetCleanTargetName()), config);
1835 if (this->IsMultiConfig()) {
1836 build.Variables["TARGETS"] =
1837 cmStrCat(this->BuildAlias(GetByproductsForCleanTargetName(), config),
1838 " ", GetByproductsForCleanTargetName());
1840 build.ExplicitDeps.clear();
1841 if (additionalFiles) {
1842 build.ExplicitDeps.push_back(this->BuildAlias(
1843 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
1846 for (auto const& fileConfig : configs) {
1847 if (fileConfig != config && !this->EnableCrossConfigBuild()) {
1850 if (this->IsMultiConfig()) {
1851 build.Variables["FILE_ARG"] = cmStrCat(
1853 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
1855 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
1859 if (this->EnableCrossConfigBuild()) {
1860 build.Outputs.front() = this->BuildAlias(
1861 this->NinjaOutputPath(this->GetCleanTargetName()), "all");
1862 build.ExplicitDeps.clear();
1864 if (additionalFiles) {
1865 for (auto const& config : this->CrossConfigs) {
1866 build.ExplicitDeps.push_back(this->BuildAlias(
1867 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
1872 std::vector<std::string> byproducts;
1873 for (auto const& config : this->CrossConfigs) {
1874 byproducts.push_back(
1875 this->BuildAlias(GetByproductsForCleanTargetName(), config));
1877 build.Variables["TARGETS"] = cmJoin(byproducts, " ");
1879 for (auto const& fileConfig : configs) {
1880 build.Variables["FILE_ARG"] = cmStrCat(
1882 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
1883 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
1888 if (this->IsMultiConfig()) {
1889 cmNinjaBuild build("phony");
1890 build.Outputs.emplace_back(
1891 this->NinjaOutputPath(this->GetCleanTargetName()));
1892 build.ExplicitDeps.emplace_back();
1894 for (auto const& config : configs) {
1895 build.ExplicitDeps.front() = this->BuildAlias(
1896 this->NinjaOutputPath(this->GetCleanTargetName()), config);
1897 this->WriteBuild(*this->GetConfigFileStream(config), build);
1900 if (!this->DefaultConfigs.empty()) {
1901 build.ExplicitDeps.clear();
1902 for (auto const& config : this->DefaultConfigs) {
1903 build.ExplicitDeps.push_back(this->BuildAlias(
1904 this->NinjaOutputPath(this->GetCleanTargetName()), config));
1906 this->WriteBuild(*this->GetDefaultFileStream(), build);
1911 if (this->IsMultiConfig()) {
1912 cmNinjaBuild build("phony");
1913 build.Comment = "Clean byproducts.";
1914 build.Outputs.emplace_back(
1915 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()));
1916 build.ExplicitDeps = this->ByproductsForCleanTarget;
1917 WriteBuild(os, build);
1919 for (auto const& config : configs) {
1920 build.Outputs.front() = this->BuildAlias(
1921 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()), config);
1922 build.ExplicitDeps = this->Configs[config].ByproductsForCleanTarget;
1923 WriteBuild(os, build);
1928 void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os)
1931 cmNinjaRule rule("HELP");
1932 rule.Command = NinjaCmd() + " -t targets";
1933 rule.Description = "All primary targets available:";
1934 rule.Comment = "Rule for printing all primary targets available.";
1935 WriteRule(*this->RulesFileStream, rule);
1938 cmNinjaBuild build("HELP");
1939 build.Comment = "Print all primary targets available.";
1940 build.Outputs.push_back(this->NinjaOutputPath("help"));
1941 WriteBuild(os, build);
1945 void cmGlobalNinjaGenerator::InitOutputPathPrefix()
1947 this->OutputPathPrefix =
1948 this->LocalGenerators[0]->GetMakefile()->GetSafeDefinition(
1949 "CMAKE_NINJA_OUTPUT_PATH_PREFIX");
1950 EnsureTrailingSlash(this->OutputPathPrefix);
1953 std::string cmGlobalNinjaGenerator::NinjaOutputPath(
1954 std::string const& path) const
1956 if (!this->HasOutputPathPrefix() || cmSystemTools::FileIsFullPath(path)) {
1959 return this->OutputPathPrefix + path;
1962 void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix(
1968 EnsureTrailingSlash(path);
1969 cmStripSuffixIfExists(path, this->OutputPathPrefix);
1974 We use the following approach to support Fortran. Each target already
1975 has a <target>.dir/ directory used to hold intermediate files for CMake.
1976 For each target, a FortranDependInfo.json file is generated by CMake with
1977 information about include directories, module directories, and the locations
1978 the per-target directories for target dependencies.
1980 Compilation of source files within a target is split into the following steps:
1982 1. Preprocess all sources, scan preprocessed output for module dependencies.
1983 This step is done with independent build statements for each source,
1984 and can therefore be done in parallel.
1986 rule Fortran_PREPROCESS
1988 command = gfortran -cpp $DEFINES $INCLUDES $FLAGS -E $in -o $out &&
1989 cmake -E cmake_ninja_depends \
1990 --tdi=FortranDependInfo.json --pp=$out --dep=$DEP_FILE \
1991 --obj=$OBJ_FILE --ddi=$DYNDEP_INTERMEDIATE_FILE \
1994 build src.f90-pp.f90 | src.f90.o.ddi: Fortran_PREPROCESS src.f90
1995 OBJ_FILE = src.f90.o
1996 DEP_FILE = src.f90.o.d
1997 DYNDEP_INTERMEDIATE_FILE = src.f90.o.ddi
1999 The ``cmake -E cmake_ninja_depends`` tool reads the preprocessed output
2000 and generates the ninja depfile for preprocessor dependencies. It also
2001 generates a "ddi" file (in a format private to CMake) that lists the
2002 object file that compilation will produce along with the module names
2003 it provides and/or requires. The "ddi" file is an implicit output
2004 because it should not appear in "$out" but is generated by the rule.
2006 2. Consolidate the per-source module dependencies saved in the "ddi"
2007 files from all sources to produce a ninja "dyndep" file, ``Fortran.dd``.
2010 command = cmake -E cmake_ninja_dyndep \
2011 --tdi=FortranDependInfo.json --lang=Fortran --dd=$out $in
2013 build Fortran.dd: Fortran_DYNDEP src1.f90.o.ddi src2.f90.o.ddi
2015 The ``cmake -E cmake_ninja_dyndep`` tool reads the "ddi" files from all
2016 sources in the target and the ``FortranModules.json`` files from targets
2017 on which the target depends. It computes dependency edges on compilations
2018 that require modules to those that provide the modules. This information
2019 is placed in the ``Fortran.dd`` file for ninja to load later. It also
2020 writes the expected location of modules provided by this target into
2021 ``FortranModules.json`` for use by dependent targets.
2023 3. Compile all sources after loading dynamically discovered dependencies
2024 of the compilation build statements from their ``dyndep`` bindings.
2026 rule Fortran_COMPILE
2027 command = gfortran $INCLUDES $FLAGS -c $in -o $out
2029 build src1.f90.o: Fortran_COMPILE src1.f90-pp.f90 || Fortran.dd
2032 The "dyndep" binding tells ninja to load dynamically discovered
2033 dependency information from ``Fortran.dd``. This adds information
2036 build src1.f90.o | mod1.mod: dyndep
2039 This tells ninja that ``mod1.mod`` is an implicit output of compiling
2040 the object file ``src1.f90.o``. The ``restat`` binding tells it that
2041 the timestamp of the output may not always change. Additionally:
2043 build src2.f90.o: dyndep | mod1.mod
2045 This tells ninja that ``mod1.mod`` is a dependency of compiling the
2046 object file ``src2.f90.o``. This ensures that ``src1.f90.o`` and
2047 ``mod1.mod`` will always be up to date before ``src2.f90.o`` is built
2048 (because the latter consumes the module).
2053 // Set of provided and required modules.
2054 std::set<std::string> Provides;
2055 std::set<std::string> Requires;
2057 // Set of files included in the translation unit.
2058 std::set<std::string> Includes;
2061 static std::unique_ptr<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2062 std::string const& arg_tdi, std::string const& arg_pp);
2064 int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
2065 std::vector<std::string>::const_iterator argEnd)
2067 std::string arg_tdi;
2069 std::string arg_dep;
2070 std::string arg_obj;
2071 std::string arg_ddi;
2072 std::string arg_lang;
2073 for (std::string const& arg : cmMakeRange(argBeg, argEnd)) {
2074 if (cmHasLiteralPrefix(arg, "--tdi=")) {
2075 arg_tdi = arg.substr(6);
2076 } else if (cmHasLiteralPrefix(arg, "--pp=")) {
2077 arg_pp = arg.substr(5);
2078 } else if (cmHasLiteralPrefix(arg, "--dep=")) {
2079 arg_dep = arg.substr(6);
2080 } else if (cmHasLiteralPrefix(arg, "--obj=")) {
2081 arg_obj = arg.substr(6);
2082 } else if (cmHasLiteralPrefix(arg, "--ddi=")) {
2083 arg_ddi = arg.substr(6);
2084 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2085 arg_lang = arg.substr(7);
2087 cmSystemTools::Error("-E cmake_ninja_depends unknown argument: " + arg);
2091 if (arg_tdi.empty()) {
2092 cmSystemTools::Error("-E cmake_ninja_depends requires value for --tdi=");
2095 if (arg_pp.empty()) {
2096 cmSystemTools::Error("-E cmake_ninja_depends requires value for --pp=");
2099 if (arg_dep.empty()) {
2100 cmSystemTools::Error("-E cmake_ninja_depends requires value for --dep=");
2103 if (arg_obj.empty()) {
2104 cmSystemTools::Error("-E cmake_ninja_depends requires value for --obj=");
2107 if (arg_ddi.empty()) {
2108 cmSystemTools::Error("-E cmake_ninja_depends requires value for --ddi=");
2111 if (arg_lang.empty()) {
2112 cmSystemTools::Error("-E cmake_ninja_depends requires value for --lang=");
2116 std::unique_ptr<cmSourceInfo> info;
2117 if (arg_lang == "Fortran") {
2118 info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_pp);
2120 cmSystemTools::Error(
2121 cmStrCat("-E cmake_ninja_depends does not understand the ", arg_lang,
2127 // The error message is already expected to have been output.
2132 cmGeneratedFileStream depfile(arg_dep);
2133 depfile << cmSystemTools::ConvertToUnixOutputPath(arg_pp) << ":";
2134 for (std::string const& include : info->Includes) {
2135 depfile << " \\\n " << cmSystemTools::ConvertToUnixOutputPath(include);
2140 Json::Value ddi(Json::objectValue);
2141 ddi["object"] = arg_obj;
2143 Json::Value& ddi_provides = ddi["provides"] = Json::arrayValue;
2144 for (std::string const& provide : info->Provides) {
2145 ddi_provides.append(provide);
2147 Json::Value& ddi_requires = ddi["requires"] = Json::arrayValue;
2148 for (std::string const& r : info->Requires) {
2149 // Require modules not provided in the same source.
2150 if (!info->Provides.count(r)) {
2151 ddi_requires.append(r);
2155 cmGeneratedFileStream ddif(arg_ddi);
2158 cmSystemTools::Error("-E cmake_ninja_depends failed to write " + arg_ddi);
2164 std::unique_ptr<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2165 std::string const& arg_tdi, std::string const& arg_pp)
2167 cmFortranCompiler fc;
2168 std::vector<std::string> includes;
2171 Json::Value const& tdi = tdio;
2173 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2174 Json::Reader reader;
2175 if (!reader.parse(tdif, tdio, false)) {
2176 cmSystemTools::Error(
2177 cmStrCat("-E cmake_ninja_depends failed to parse ", arg_tdi,
2178 reader.getFormattedErrorMessages()));
2183 Json::Value const& tdi_include_dirs = tdi["include-dirs"];
2184 if (tdi_include_dirs.isArray()) {
2185 for (auto const& tdi_include_dir : tdi_include_dirs) {
2186 includes.push_back(tdi_include_dir.asString());
2190 Json::Value const& tdi_compiler_id = tdi["compiler-id"];
2191 fc.Id = tdi_compiler_id.asString();
2193 Json::Value const& tdi_submodule_sep = tdi["submodule-sep"];
2194 fc.SModSep = tdi_submodule_sep.asString();
2196 Json::Value const& tdi_submodule_ext = tdi["submodule-ext"];
2197 fc.SModExt = tdi_submodule_ext.asString();
2200 cmFortranSourceInfo finfo;
2201 std::set<std::string> defines;
2202 cmFortranParser parser(fc, includes, defines, finfo);
2203 if (!cmFortranParser_FilePush(&parser, arg_pp.c_str())) {
2204 cmSystemTools::Error("-E cmake_ninja_depends failed to open " + arg_pp);
2207 if (cmFortran_yyparse(parser.Scanner) != 0) {
2208 // Failed to parse the file.
2212 auto info = cm::make_unique<cmSourceInfo>();
2213 info->Provides = finfo.Provides;
2214 info->Requires = finfo.Requires;
2215 info->Includes = finfo.Includes;
2219 struct cmDyndepObjectInfo
2222 std::vector<std::string> Provides;
2223 std::vector<std::string> Requires;
2226 bool cmGlobalNinjaGenerator::WriteDyndepFile(
2227 std::string const& dir_top_src, std::string const& dir_top_bld,
2228 std::string const& dir_cur_src, std::string const& dir_cur_bld,
2229 std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
2230 std::string const& module_dir,
2231 std::vector<std::string> const& linked_target_dirs,
2232 std::string const& arg_lang)
2234 // Setup path conversions.
2236 cmStateSnapshot snapshot = this->GetCMakeInstance()->GetCurrentSnapshot();
2237 snapshot.GetDirectory().SetCurrentSource(dir_cur_src);
2238 snapshot.GetDirectory().SetCurrentBinary(dir_cur_bld);
2239 snapshot.GetDirectory().SetRelativePathTopSource(dir_top_src.c_str());
2240 snapshot.GetDirectory().SetRelativePathTopBinary(dir_top_bld.c_str());
2241 auto mfd = cm::make_unique<cmMakefile>(this, snapshot);
2242 auto lgd = this->CreateLocalGenerator(mfd.get());
2243 this->Makefiles.push_back(std::move(mfd));
2244 this->LocalGenerators.push_back(std::move(lgd));
2247 std::vector<cmDyndepObjectInfo> objects;
2248 for (std::string const& arg_ddi : arg_ddis) {
2249 // Load the ddi file and compute the module file paths it provides.
2251 Json::Value const& ddi = ddio;
2252 cmsys::ifstream ddif(arg_ddi.c_str(), std::ios::in | std::ios::binary);
2253 Json::Reader reader;
2254 if (!reader.parse(ddif, ddio, false)) {
2255 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2257 reader.getFormattedErrorMessages()));
2261 cmDyndepObjectInfo info;
2262 info.Object = ddi["object"].asString();
2263 Json::Value const& ddi_provides = ddi["provides"];
2264 if (ddi_provides.isArray()) {
2265 for (auto const& ddi_provide : ddi_provides) {
2266 info.Provides.push_back(ddi_provide.asString());
2269 Json::Value const& ddi_requires = ddi["requires"];
2270 if (ddi_requires.isArray()) {
2271 for (auto const& ddi_require : ddi_requires) {
2272 info.Requires.push_back(ddi_require.asString());
2275 objects.push_back(std::move(info));
2278 // Map from module name to module file path, if known.
2279 std::map<std::string, std::string> mod_files;
2281 // Populate the module map with those provided by linked targets first.
2282 for (std::string const& linked_target_dir : linked_target_dirs) {
2283 std::string const ltmn =
2284 cmStrCat(linked_target_dir, "/", arg_lang, "Modules.json");
2286 cmsys::ifstream ltmf(ltmn.c_str(), std::ios::in | std::ios::binary);
2287 Json::Reader reader;
2288 if (ltmf && !reader.parse(ltmf, ltm, false)) {
2289 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2291 reader.getFormattedErrorMessages()));
2294 if (ltm.isObject()) {
2295 for (Json::Value::iterator i = ltm.begin(); i != ltm.end(); ++i) {
2296 mod_files[i.key().asString()] = i->asString();
2301 // Extend the module map with those provided by this target.
2302 // We do this after loading the modules provided by linked targets
2303 // in case we have one of the same name that must be preferred.
2304 Json::Value tm = Json::objectValue;
2305 for (cmDyndepObjectInfo const& object : objects) {
2306 for (std::string const& p : object.Provides) {
2307 std::string const mod = module_dir + p;
2313 cmGeneratedFileStream ddf(arg_dd);
2314 ddf << "ninja_dyndep_version = 1.0\n";
2317 cmNinjaBuild build("dyndep");
2318 build.Outputs.emplace_back("");
2319 for (cmDyndepObjectInfo const& object : objects) {
2320 build.Outputs[0] = object.Object;
2321 build.ImplicitOuts.clear();
2322 for (std::string const& p : object.Provides) {
2323 build.ImplicitOuts.push_back(this->ConvertToNinjaPath(mod_files[p]));
2325 build.ImplicitDeps.clear();
2326 for (std::string const& r : object.Requires) {
2327 auto mit = mod_files.find(r);
2328 if (mit != mod_files.end()) {
2329 build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second));
2332 build.Variables.clear();
2333 if (!object.Provides.empty()) {
2334 build.Variables.emplace("restat", "1");
2337 this->WriteBuild(ddf, build);
2341 // Store the map of modules provided by this target in a file for
2342 // use by dependents that reference this target in linked-target-dirs.
2343 std::string const target_mods_file =
2344 cmSystemTools::GetFilenamePath(arg_dd) + "/" + arg_lang + "Modules.json";
2345 cmGeneratedFileStream tmf(target_mods_file);
2351 bool cmGlobalNinjaGenerator::EnableCrossConfigBuild() const
2353 return !this->CrossConfigs.empty();
2356 int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
2357 std::vector<std::string>::const_iterator argEnd)
2359 std::vector<std::string> arg_full =
2360 cmSystemTools::HandleResponseFile(argBeg, argEnd);
2363 std::string arg_lang;
2364 std::string arg_tdi;
2365 std::vector<std::string> arg_ddis;
2366 for (std::string const& arg : arg_full) {
2367 if (cmHasLiteralPrefix(arg, "--tdi=")) {
2368 arg_tdi = arg.substr(6);
2369 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2370 arg_lang = arg.substr(7);
2371 } else if (cmHasLiteralPrefix(arg, "--dd=")) {
2372 arg_dd = arg.substr(5);
2373 } else if (!cmHasLiteralPrefix(arg, "--") &&
2374 cmHasLiteralSuffix(arg, ".ddi")) {
2375 arg_ddis.push_back(arg);
2377 cmSystemTools::Error("-E cmake_ninja_dyndep unknown argument: " + arg);
2381 if (arg_tdi.empty()) {
2382 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --tdi=");
2385 if (arg_lang.empty()) {
2386 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --lang=");
2389 if (arg_dd.empty()) {
2390 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --dd=");
2395 Json::Value const& tdi = tdio;
2397 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2398 Json::Reader reader;
2399 if (!reader.parse(tdif, tdio, false)) {
2400 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2402 reader.getFormattedErrorMessages()));
2407 std::string const dir_cur_bld = tdi["dir-cur-bld"].asString();
2408 std::string const dir_cur_src = tdi["dir-cur-src"].asString();
2409 std::string const dir_top_bld = tdi["dir-top-bld"].asString();
2410 std::string const dir_top_src = tdi["dir-top-src"].asString();
2411 std::string module_dir = tdi["module-dir"].asString();
2412 if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
2415 std::vector<std::string> linked_target_dirs;
2416 Json::Value const& tdi_linked_target_dirs = tdi["linked-target-dirs"];
2417 if (tdi_linked_target_dirs.isArray()) {
2418 for (auto const& tdi_linked_target_dir : tdi_linked_target_dirs) {
2419 linked_target_dirs.push_back(tdi_linked_target_dir.asString());
2423 cmake cm(cmake::RoleInternal, cmState::Unknown);
2424 cm.SetHomeDirectory(dir_top_src);
2425 cm.SetHomeOutputDirectory(dir_top_bld);
2426 auto ggd = cm.CreateGlobalGenerator("Ninja");
2428 !cm::static_reference_cast<cmGlobalNinjaGenerator>(ggd).WriteDyndepFile(
2429 dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld, arg_dd, arg_ddis,
2430 module_dir, linked_target_dirs, arg_lang)) {
2436 void cmGlobalNinjaGenerator::AppendDirectoryForConfig(
2437 const std::string& prefix, const std::string& config,
2438 const std::string& suffix, std::string& dir)
2440 if (!config.empty() && this->IsMultiConfig()) {
2447 std::set<std::string> cmGlobalNinjaGenerator::GetCrossConfigs(
2448 const std::string& fileConfig) const
2450 auto result = this->CrossConfigs;
2451 result.insert(fileConfig);
2455 const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE =
2456 "CMakeFiles/common.ninja";
2457 const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja";
2459 cmGlobalNinjaMultiGenerator::cmGlobalNinjaMultiGenerator(cmake* cm)
2460 : cmGlobalNinjaGenerator(cm)
2462 cm->GetState()->SetIsGeneratorMultiConfig(true);
2463 cm->GetState()->SetNinjaMulti(true);
2466 void cmGlobalNinjaMultiGenerator::GetDocumentation(cmDocumentationEntry& entry)
2468 entry.Name = cmGlobalNinjaMultiGenerator::GetActualName();
2469 entry.Brief = "Generates build-<Config>.ninja files.";
2472 std::string cmGlobalNinjaMultiGenerator::ExpandCFGIntDir(
2473 const std::string& str, const std::string& config) const
2475 std::string result = str;
2476 cmSystemTools::ReplaceString(result, this->GetCMakeCFGIntDir(), config);
2480 bool cmGlobalNinjaMultiGenerator::OpenBuildFileStreams()
2482 if (!this->OpenFileStream(this->CommonFileStream,
2483 cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE)) {
2487 if (!this->DefaultFileConfig.empty()) {
2488 if (!this->OpenFileStream(this->DefaultFileStream, NINJA_BUILD_FILE)) {
2491 *this->DefaultFileStream
2492 << "# Build using rules for '" << this->DefaultFileConfig << "'.\n\n"
2493 << "include " << GetNinjaImplFilename(this->DefaultFileConfig) << "\n\n";
2496 // Write a comment about this file.
2497 *this->CommonFileStream
2498 << "# This file contains build statements common to all "
2499 "configurations.\n\n";
2501 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
2503 if (!this->OpenFileStream(this->ImplFileStreams[config],
2504 GetNinjaImplFilename(config))) {
2508 // Write a comment about this file.
2509 *this->ImplFileStreams[config]
2510 << "# This file contains build statements specific to the \"" << config
2511 << "\"\n# configuration.\n\n";
2513 // Open config file.
2514 if (!this->OpenFileStream(this->ConfigFileStreams[config],
2515 GetNinjaConfigFilename(config))) {
2519 // Write a comment about this file.
2520 *this->ConfigFileStreams[config]
2521 << "# This file contains aliases specific to the \"" << config
2522 << "\"\n# configuration.\n\n"
2523 << "include " << GetNinjaImplFilename(config) << "\n\n";
2529 void cmGlobalNinjaMultiGenerator::CloseBuildFileStreams()
2531 if (this->CommonFileStream) {
2532 this->CommonFileStream.reset();
2534 cmSystemTools::Error("Common file stream was not open.");
2537 if (this->DefaultFileStream) {
2538 this->DefaultFileStream.reset();
2539 } // No error if it wasn't open
2541 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs()) {
2542 if (this->ImplFileStreams[config]) {
2543 this->ImplFileStreams[config].reset();
2545 cmSystemTools::Error(
2546 cmStrCat("Impl file stream for \"", config, "\" was not open."));
2548 if (this->ConfigFileStreams[config]) {
2549 this->ConfigFileStreams[config].reset();
2551 cmSystemTools::Error(
2552 cmStrCat("Config file stream for \"", config, "\" was not open."));
2557 void cmGlobalNinjaMultiGenerator::AppendNinjaFileArgument(
2558 GeneratedMakeCommand& command, const std::string& config) const
2560 if (!config.empty()) {
2562 command.Add(GetNinjaConfigFilename(config));
2566 std::string cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(
2567 const std::string& config)
2569 return cmStrCat("CMakeFiles/impl-", config,
2570 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
2573 std::string cmGlobalNinjaMultiGenerator::GetNinjaConfigFilename(
2574 const std::string& config)
2576 return cmStrCat("build-", config,
2577 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
2580 void cmGlobalNinjaMultiGenerator::AddRebuildManifestOutputs(
2581 cmNinjaDeps& outputs) const
2583 for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs()) {
2584 outputs.push_back(this->NinjaOutputPath(GetNinjaImplFilename(config)));
2585 outputs.push_back(this->NinjaOutputPath(GetNinjaConfigFilename(config)));
2587 if (!this->DefaultFileConfig.empty()) {
2588 outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
2592 void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs(
2593 std::vector<std::string>& configs) const
2595 auto const oldSize = configs.size();
2596 this->Makefiles.front()->GetConfigurations(configs);
2597 if (configs.size() == oldSize) {
2598 configs.emplace_back();
2602 bool cmGlobalNinjaMultiGenerator::InspectConfigTypeVariables()
2604 this->GetCMakeInstance()->MarkCliAsUsed("CMAKE_DEFAULT_BUILD_TYPE");
2605 this->GetCMakeInstance()->MarkCliAsUsed("CMAKE_CROSS_CONFIGS");
2606 this->GetCMakeInstance()->MarkCliAsUsed("CMAKE_DEFAULT_CONFIGS");
2607 return this->ReadCacheEntriesForBuild(*this->Makefiles.front()->GetState());
2610 std::string cmGlobalNinjaMultiGenerator::GetDefaultBuildConfig() const
2615 bool cmGlobalNinjaMultiGenerator::ReadCacheEntriesForBuild(
2616 const cmState& state)
2618 std::vector<std::string> configsVec;
2619 cmExpandList(state.GetSafeCacheEntryValue("CMAKE_CONFIGURATION_TYPES"),
2621 if (configsVec.empty()) {
2622 configsVec.emplace_back();
2624 std::set<std::string> configs(configsVec.cbegin(), configsVec.cend());
2626 this->DefaultFileConfig =
2627 state.GetSafeCacheEntryValue("CMAKE_DEFAULT_BUILD_TYPE");
2628 if (this->DefaultFileConfig.empty()) {
2629 this->DefaultFileConfig = configsVec.front();
2631 if (!configs.count(this->DefaultFileConfig)) {
2632 std::ostringstream msg;
2633 msg << "The configuration specified by "
2634 << "CMAKE_DEFAULT_BUILD_TYPE (" << this->DefaultFileConfig
2635 << ") is not present in CMAKE_CONFIGURATION_TYPES";
2636 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2641 std::vector<std::string> crossConfigsVec;
2642 cmExpandList(state.GetSafeCacheEntryValue("CMAKE_CROSS_CONFIGS"),
2644 auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsVec);
2645 if (!crossConfigs) {
2646 std::ostringstream msg;
2647 msg << "CMAKE_CROSS_CONFIGS is not a subset of "
2648 << "CMAKE_CONFIGURATION_TYPES";
2649 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2653 this->CrossConfigs = *crossConfigs;
2655 auto defaultConfigsString =
2656 state.GetSafeCacheEntryValue("CMAKE_DEFAULT_CONFIGS");
2657 if (defaultConfigsString.empty()) {
2658 defaultConfigsString = this->DefaultFileConfig;
2660 if (!defaultConfigsString.empty() &&
2661 defaultConfigsString != this->DefaultFileConfig &&
2662 (this->DefaultFileConfig.empty() || this->CrossConfigs.empty())) {
2663 std::ostringstream msg;
2664 msg << "CMAKE_DEFAULT_CONFIGS cannot be used without "
2665 << "CMAKE_DEFAULT_BUILD_TYPE or CMAKE_CROSS_CONFIGS";
2666 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2671 std::vector<std::string> defaultConfigsVec;
2672 cmExpandList(defaultConfigsString, defaultConfigsVec);
2673 if (!this->DefaultFileConfig.empty()) {
2674 auto defaultConfigs =
2675 ListSubsetWithAll(this->GetCrossConfigs(this->DefaultFileConfig),
2676 this->CrossConfigs, defaultConfigsVec);
2677 if (!defaultConfigs) {
2678 std::ostringstream msg;
2679 msg << "CMAKE_DEFAULT_CONFIGS is not a subset of CMAKE_CROSS_CONFIGS";
2680 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2684 this->DefaultConfigs = *defaultConfigs;