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"
13 #include <cm/iterator>
15 #include <cm/optional>
16 #include <cm/string_view>
17 #include <cmext/algorithm>
18 #include <cmext/memory>
19 #include <cmext/string_view>
21 #include <cm3p/json/reader.h>
22 #include <cm3p/json/value.h>
23 #include <cm3p/json/writer.h>
25 #include "cmsys/FStream.hxx"
27 #include "cmCxxModuleMapper.h"
28 #include "cmDocumentationEntry.h"
29 #include "cmFileSet.h"
30 #include "cmFortranParser.h"
31 #include "cmGeneratedFileStream.h"
32 #include "cmGeneratorExpressionEvaluationFile.h"
33 #include "cmGeneratorTarget.h"
34 #include "cmGlobalGenerator.h"
35 #include "cmLinkLineComputer.h"
36 #include "cmListFileCache.h"
37 #include "cmLocalGenerator.h"
38 #include "cmLocalNinjaGenerator.h"
39 #include "cmMakefile.h"
40 #include "cmMessageType.h"
41 #include "cmNinjaLinkLineComputer.h"
42 #include "cmOutputConverter.h"
44 #include "cmScanDepFormat.h"
46 #include "cmStateDirectory.h"
47 #include "cmStateSnapshot.h"
48 #include "cmStateTypes.h"
49 #include "cmStringAlgorithms.h"
50 #include "cmSystemTools.h"
52 #include "cmTargetDepend.h"
54 #include "cmVersion.h"
57 const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja";
58 const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE =
59 "CMakeFiles/rules.ninja";
60 const char* cmGlobalNinjaGenerator::INDENT = " ";
62 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd .";
64 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":";
68 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
69 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
71 return lhs.Target == rhs.Target && lhs.Config == rhs.Config &&
72 lhs.GenexOutput == rhs.GenexOutput;
76 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
77 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
83 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
84 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
86 return lhs.Target < rhs.Target ||
87 (lhs.Target == rhs.Target &&
88 (lhs.Config < rhs.Config ||
89 (lhs.Config == rhs.Config && lhs.GenexOutput < rhs.GenexOutput)));
93 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
94 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
100 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
101 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
107 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
108 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
113 void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
115 for (int i = 0; i < count; ++i) {
116 os << cmGlobalNinjaGenerator::INDENT;
120 void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os)
122 os << "# ======================================"
123 "=======================================\n";
126 void cmGlobalNinjaGenerator::WriteComment(std::ostream& os,
127 const std::string& comment)
129 if (comment.empty()) {
133 std::string::size_type lpos = 0;
134 std::string::size_type rpos;
135 os << "\n#############################################\n";
136 while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
137 os << "# " << comment.substr(lpos, rpos - lpos) << "\n";
140 os << "# " << comment.substr(lpos) << "\n\n";
143 std::unique_ptr<cmLinkLineComputer>
144 cmGlobalNinjaGenerator::CreateLinkLineComputer(
145 cmOutputConverter* outputConverter,
146 cmStateDirectory const& /* stateDir */) const
148 return std::unique_ptr<cmLinkLineComputer>(
149 cm::make_unique<cmNinjaLinkLineComputer>(
151 this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this));
154 std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name)
156 // Ninja rule names must match "[a-zA-Z0-9_.-]+". Use ".xx" to encode
157 // "." and all invalid characters as hexadecimal.
159 for (char i : name) {
160 if (isalnum(i) || i == '_' || i == '-') {
164 snprintf(buf, sizeof(buf), ".%02x", static_cast<unsigned int>(i));
171 std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
173 std::string result = lit;
174 EncodeLiteralInplace(result);
178 void cmGlobalNinjaGenerator::EncodeLiteralInplace(std::string& lit)
180 cmSystemTools::ReplaceString(lit, "$", "$$");
181 cmSystemTools::ReplaceString(lit, "\n", "$\n");
182 if (this->IsMultiConfig()) {
183 cmSystemTools::ReplaceString(lit, cmStrCat('$', this->GetCMakeCFGIntDir()),
184 this->GetCMakeCFGIntDir());
188 std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
190 std::string result = path;
192 if (this->IsGCCOnWindows())
193 std::replace(result.begin(), result.end(), '\\', '/');
195 std::replace(result.begin(), result.end(), '/', '\\');
197 this->EncodeLiteralInplace(result);
198 cmSystemTools::ReplaceString(result, " ", "$ ");
199 cmSystemTools::ReplaceString(result, ":", "$:");
203 void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
204 cmNinjaBuild const& build,
206 bool* usedResponseFile)
208 // Make sure there is a rule.
209 if (build.Rule.empty()) {
210 cmSystemTools::Error(cmStrCat(
211 "No rule for WriteBuild! called with comment: ", build.Comment));
215 // Make sure there is at least one output file.
216 if (build.Outputs.empty()) {
217 cmSystemTools::Error(cmStrCat(
218 "No output files for WriteBuild! called with comment: ", build.Comment));
222 cmGlobalNinjaGenerator::WriteComment(os, build.Comment);
224 // Write output files.
225 std::string buildStr("build");
227 // Write explicit outputs
228 for (std::string const& output : build.Outputs) {
229 buildStr = cmStrCat(buildStr, ' ', this->EncodePath(output));
230 if (this->ComputingUnknownDependencies) {
231 this->CombinedBuildOutputs.insert(output);
234 // Write implicit outputs
235 if (!build.ImplicitOuts.empty()) {
236 // Assume Ninja is new enough to support implicit outputs.
237 // Callers should not populate this field otherwise.
238 buildStr = cmStrCat(buildStr, " |");
239 for (std::string const& implicitOut : build.ImplicitOuts) {
240 buildStr = cmStrCat(buildStr, ' ', this->EncodePath(implicitOut));
241 if (this->ComputingUnknownDependencies) {
242 this->CombinedBuildOutputs.insert(implicitOut);
247 // Repeat some outputs, but expressed as absolute paths.
248 // This helps Ninja handle absolute paths found in a depfile.
249 // FIXME: Unfortunately this causes Ninja to stat the file twice.
250 // We could avoid this if Ninja Issue 1251 were fixed.
251 if (!build.WorkDirOuts.empty()) {
252 if (this->SupportsImplicitOuts() && build.ImplicitOuts.empty()) {
253 // Make them implicit outputs if supported by this version of Ninja.
254 buildStr = cmStrCat(buildStr, " |");
256 for (std::string const& workdirOut : build.WorkDirOuts) {
257 buildStr = cmStrCat(buildStr, " ${cmake_ninja_workdir}",
258 this->EncodePath(workdirOut));
263 buildStr = cmStrCat(buildStr, ": ", build.Rule);
266 std::string arguments;
268 // TODO: Better formatting for when there are multiple input/output files.
270 // Write explicit dependencies.
271 for (std::string const& explicitDep : build.ExplicitDeps) {
272 arguments += cmStrCat(' ', this->EncodePath(explicitDep));
275 // Write implicit dependencies.
276 if (!build.ImplicitDeps.empty()) {
278 for (std::string const& implicitDep : build.ImplicitDeps) {
279 arguments += cmStrCat(' ', this->EncodePath(implicitDep));
283 // Write order-only dependencies.
284 if (!build.OrderOnlyDeps.empty()) {
286 for (std::string const& orderOnlyDep : build.OrderOnlyDeps) {
287 arguments += cmStrCat(' ', this->EncodePath(orderOnlyDep));
294 // Write the variables bound to this build statement.
295 std::string assignments;
297 std::ostringstream variable_assignments;
298 for (auto const& variable : build.Variables) {
299 cmGlobalNinjaGenerator::WriteVariable(
300 variable_assignments, variable.first, variable.second, "", 1);
303 // check if a response file rule should be used
304 assignments = variable_assignments.str();
305 bool useResponseFile = false;
306 if (cmdLineLimit < 0 ||
308 (arguments.size() + buildStr.size() + assignments.size() + 1000) >
309 static_cast<size_t>(cmdLineLimit))) {
310 variable_assignments.str(std::string());
311 cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
312 build.RspFile, "", 1);
313 assignments += variable_assignments.str();
314 useResponseFile = true;
316 if (usedResponseFile) {
317 *usedResponseFile = useResponseFile;
321 if (build.Variables.count("dyndep") > 0) {
322 // The ninja 'cleandead' operation does not account for outputs
323 // discovered by 'dyndep' bindings. Avoid removing them.
324 this->DisableCleandead = true;
327 os << buildStr << arguments << assignments << "\n";
330 void cmGlobalNinjaGenerator::AddCustomCommandRule()
332 cmNinjaRule rule("CUSTOM_COMMAND");
333 rule.Command = "$COMMAND";
334 rule.Description = "$DESC";
335 rule.Comment = "Rule for running custom commands.";
339 void cmGlobalNinjaGenerator::CCOutputs::Add(
340 std::vector<std::string> const& paths)
342 for (std::string const& path : paths) {
343 std::string out = this->GG->ConvertToNinjaPath(path);
344 if (!cmSystemTools::FileIsFullPath(out)) {
345 // This output is expressed as a relative path. Repeat it,
346 // but expressed as an absolute path for Ninja Issue 1251.
347 this->WorkDirOuts.emplace_back(out);
348 this->GG->SeenCustomCommandOutput(this->GG->ConvertToNinjaAbsPath(path));
350 this->GG->SeenCustomCommandOutput(out);
351 this->ExplicitOuts.emplace_back(std::move(out));
355 void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
356 std::string const& command, std::string const& description,
357 std::string const& comment, std::string const& depfile,
358 std::string const& job_pool, bool uses_terminal, bool restat,
359 std::string const& config, CCOutputs outputs, cmNinjaDeps explicitDeps,
360 cmNinjaDeps orderOnlyDeps)
362 this->AddCustomCommandRule();
364 if (this->ComputingUnknownDependencies) {
365 // we need to track every dependency that comes in, since we are trying
366 // to find dependencies that are side effects of build commands
367 for (std::string const& dep : explicitDeps) {
368 this->CombinedCustomCommandExplicitDependencies.insert(dep);
373 cmNinjaBuild build("CUSTOM_COMMAND");
374 build.Comment = comment;
375 build.Outputs = std::move(outputs.ExplicitOuts);
376 build.WorkDirOuts = std::move(outputs.WorkDirOuts);
377 build.ExplicitDeps = std::move(explicitDeps);
378 build.OrderOnlyDeps = std::move(orderOnlyDeps);
380 cmNinjaVars& vars = build.Variables;
382 std::string cmd = command; // NOLINT(*)
385 // TODO Shouldn't an empty command be handled by ninja?
388 vars["COMMAND"] = std::move(cmd);
390 vars["DESC"] = this->EncodeLiteral(description);
392 vars["restat"] = "1";
394 if (uses_terminal && this->SupportsDirectConsole()) {
395 vars["pool"] = "console";
396 } else if (!job_pool.empty()) {
397 vars["pool"] = job_pool;
399 if (!depfile.empty()) {
400 vars["depfile"] = depfile;
402 if (config.empty()) {
403 this->WriteBuild(*this->GetCommonFileStream(), build);
405 this->WriteBuild(*this->GetImplFileStream(config), build);
410 void cmGlobalNinjaGenerator::AddMacOSXContentRule()
412 cmNinjaRule rule("COPY_OSX_CONTENT");
413 rule.Command = cmStrCat(this->CMakeCmd(), " -E copy $in $out");
414 rule.Description = "Copying OS X Content $out";
415 rule.Comment = "Rule for copying OS X bundle content file.";
419 void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input,
421 const std::string& config)
423 this->AddMacOSXContentRule();
425 cmNinjaBuild build("COPY_OSX_CONTENT");
426 build.Outputs.push_back(std::move(output));
427 build.ExplicitDeps.push_back(std::move(input));
428 this->WriteBuild(*this->GetImplFileStream(config), build);
432 void cmGlobalNinjaGenerator::WriteRule(std::ostream& os,
433 cmNinjaRule const& rule)
435 // -- Parameter checks
436 // Make sure the rule has a name.
437 if (rule.Name.empty()) {
438 cmSystemTools::Error(cmStrCat(
439 "No name given for WriteRule! called with comment: ", rule.Comment));
443 // Make sure a command is given.
444 if (rule.Command.empty()) {
445 cmSystemTools::Error(cmStrCat(
446 "No command given for WriteRule! called with comment: ", rule.Comment));
450 // Make sure response file content is given
451 if (!rule.RspFile.empty() && rule.RspContent.empty()) {
452 cmSystemTools::Error(
453 cmStrCat("rspfile but no rspfile_content given for WriteRule! "
454 "called with comment: ",
461 cmGlobalNinjaGenerator::WriteComment(os, rule.Comment);
462 os << "rule " << rule.Name << '\n';
464 // Write rule key/value pairs
465 auto writeKV = [&os](const char* key, std::string const& value) {
466 if (!value.empty()) {
467 cmGlobalNinjaGenerator::Indent(os, 1);
468 os << key << " = " << value << '\n';
472 writeKV("depfile", rule.DepFile);
473 writeKV("deps", rule.DepType);
474 writeKV("command", rule.Command);
475 writeKV("description", rule.Description);
476 if (!rule.RspFile.empty()) {
477 writeKV("rspfile", rule.RspFile);
478 writeKV("rspfile_content", rule.RspContent);
480 writeKV("restat", rule.Restat);
481 if (rule.Generator) {
482 writeKV("generator", "1");
489 void cmGlobalNinjaGenerator::WriteVariable(std::ostream& os,
490 const std::string& name,
491 const std::string& value,
492 const std::string& comment,
495 // Make sure we have a name.
497 cmSystemTools::Error(cmStrCat("No name given for WriteVariable! called "
503 // Do not add a variable if the value is empty.
504 std::string val = cmTrimWhitespace(value);
509 cmGlobalNinjaGenerator::WriteComment(os, comment);
510 cmGlobalNinjaGenerator::Indent(os, indent);
511 os << name << " = " << val << "\n";
514 void cmGlobalNinjaGenerator::WriteInclude(std::ostream& os,
515 const std::string& filename,
516 const std::string& comment)
518 cmGlobalNinjaGenerator::WriteComment(os, comment);
519 os << "include " << filename << "\n";
522 void cmGlobalNinjaGenerator::WriteDefault(std::ostream& os,
523 const cmNinjaDeps& targets,
524 const std::string& comment)
526 cmGlobalNinjaGenerator::WriteComment(os, comment);
528 for (std::string const& target : targets) {
534 cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
535 : cmGlobalCommonGenerator(cm)
538 cm->GetState()->SetWindowsShell(true);
540 this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake";
543 // Virtual public methods.
545 std::unique_ptr<cmLocalGenerator> cmGlobalNinjaGenerator::CreateLocalGenerator(
548 return std::unique_ptr<cmLocalGenerator>(
549 cm::make_unique<cmLocalNinjaGenerator>(this, mf));
552 codecvt::Encoding cmGlobalNinjaGenerator::GetMakefileEncoding() const
554 return this->NinjaExpectedEncoding;
557 void cmGlobalNinjaGenerator::GetDocumentation(cmDocumentationEntry& entry)
559 entry.Name = cmGlobalNinjaGenerator::GetActualName();
560 entry.Brief = "Generates build.ninja files.";
563 // Implemented in all cmGlobaleGenerator sub-classes.
565 // Source/cmLocalGenerator.cxx
567 void cmGlobalNinjaGenerator::Generate()
569 // Check minimum Ninja version.
570 if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
571 RequiredNinjaVersion())) {
572 std::ostringstream msg;
573 msg << "The detected version of Ninja (" << this->NinjaVersion;
574 msg << ") is less than the version of Ninja required by CMake (";
575 msg << cmGlobalNinjaGenerator::RequiredNinjaVersion() << ").";
576 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
580 if (!this->OpenBuildFileStreams()) {
583 if (!this->OpenRulesFileStream()) {
587 for (auto& it : this->Configs) {
588 it.second.TargetDependsClosures.clear();
591 this->InitOutputPathPrefix();
592 this->TargetAll = this->NinjaOutputPath("all");
593 this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
594 this->DisableCleandead = false;
595 this->DiagnosedCxxModuleSupport = false;
597 this->PolicyCMP0058 =
598 this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
599 cmPolicies::CMP0058);
600 this->ComputingUnknownDependencies =
601 (this->PolicyCMP0058 == cmPolicies::OLD ||
602 this->PolicyCMP0058 == cmPolicies::WARN);
604 this->cmGlobalGenerator::Generate();
606 this->WriteAssumedSourceDependencies();
607 this->WriteTargetAliases(*this->GetCommonFileStream());
608 this->WriteFolderTargets(*this->GetCommonFileStream());
609 this->WriteUnknownExplicitDependencies(*this->GetCommonFileStream());
610 this->WriteBuiltinTargets(*this->GetCommonFileStream());
612 if (cmSystemTools::GetErrorOccurredFlag()) {
613 this->RulesFileStream->setstate(std::ios::failbit);
614 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
615 cmMakefile::IncludeEmptyConfig)) {
616 this->GetImplFileStream(config)->setstate(std::ios::failbit);
617 this->GetConfigFileStream(config)->setstate(std::ios::failbit);
619 this->GetCommonFileStream()->setstate(std::ios::failbit);
622 this->CloseCompileCommandsStream();
623 this->CloseRulesFileStream();
624 this->CloseBuildFileStreams();
627 // Older ninja tools will not be able to update metadata on Windows
628 // when we are re-generating inside an existing 'ninja' invocation
629 // because the outer tool has the files open for write.
630 if (this->NinjaSupportsMetadataOnRegeneration ||
631 !this->GetCMakeInstance()->GetRegenerateDuringBuild())
634 this->CleanMetaData();
638 void cmGlobalNinjaGenerator::CleanMetaData()
640 auto run_ninja_tool = [this](std::vector<char const*> const& args) {
641 std::vector<std::string> command;
642 command.push_back(this->NinjaCommand);
643 command.emplace_back("-C");
644 command.emplace_back(this->GetCMakeInstance()->GetHomeOutputDirectory());
645 command.emplace_back("-t");
646 for (auto const& arg : args) {
647 command.emplace_back(arg);
650 if (!cmSystemTools::RunSingleCommand(command, nullptr, &error, nullptr,
652 cmSystemTools::OUTPUT_NONE)) {
653 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
654 cmStrCat("Running\n '",
655 cmJoin(command, "' '"),
659 cmSystemTools::SetFatalErrorOccurred();
663 // Can the tools below expect 'build.ninja' to be loadable?
664 bool const expectBuildManifest =
665 !this->IsMultiConfig() && this->OutputPathPrefix.empty();
667 // Skip some ninja tools if they need 'build.ninja' but it is missing.
668 bool const missingBuildManifest = expectBuildManifest &&
669 this->NinjaSupportsUnconditionalRecompactTool &&
670 !cmSystemTools::FileExists("build.ninja");
672 // The `recompact` tool loads the manifest. As above, we don't have a single
673 // `build.ninja` to load for this in Ninja-Multi. This may be relaxed in the
674 // future pending further investigation into how Ninja works upstream
676 if (this->NinjaSupportsUnconditionalRecompactTool &&
677 !this->GetCMakeInstance()->GetRegenerateDuringBuild() &&
678 expectBuildManifest && !missingBuildManifest) {
679 run_ninja_tool({ "recompact" });
681 if (this->NinjaSupportsRestatTool && this->OutputPathPrefix.empty()) {
682 // XXX(ninja): We only list `build.ninja` entry files here because CMake
683 // *always* rewrites these files on a reconfigure. If CMake ever gets
684 // smarter about this, all CMake-time created/edited files listed as
685 // outputs for the reconfigure build statement will need to be listed here.
687 this->AddRebuildManifestOutputs(outputs);
688 std::vector<const char*> args;
689 args.reserve(outputs.size() + 1);
690 args.push_back("restat");
691 for (auto const& output : outputs) {
692 args.push_back(output.c_str());
694 run_ninja_tool(args);
698 bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
700 if (!this->cmGlobalGenerator::FindMakeProgram(mf)) {
703 if (cmValue ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
704 this->NinjaCommand = *ninjaCommand;
705 std::vector<std::string> command;
706 command.push_back(this->NinjaCommand);
707 command.emplace_back("--version");
710 if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr,
712 cmSystemTools::OUTPUT_NONE)) {
713 mf->IssueMessage(MessageType::FATAL_ERROR,
714 cmStrCat("Running\n '", cmJoin(command, "' '"),
718 cmSystemTools::SetFatalErrorOccurred();
721 this->NinjaVersion = cmTrimWhitespace(version);
722 this->CheckNinjaFeatures();
727 void cmGlobalNinjaGenerator::CheckNinjaFeatures()
729 this->NinjaSupportsConsolePool =
730 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
731 RequiredNinjaVersionForConsolePool());
732 this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare(
733 cmSystemTools::OP_LESS, this->NinjaVersion,
734 cmGlobalNinjaGenerator::RequiredNinjaVersionForImplicitOuts());
735 this->NinjaSupportsManifestRestat =
736 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
737 RequiredNinjaVersionForManifestRestat());
738 this->NinjaSupportsMultilineDepfile =
739 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
740 RequiredNinjaVersionForMultilineDepfile());
741 this->NinjaSupportsDyndeps =
742 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
743 RequiredNinjaVersionForDyndeps());
744 if (!this->NinjaSupportsDyndeps) {
745 // The ninja version number is not new enough to have upstream support.
746 // Our ninja branch adds ".dyndep-#" to its version number,
747 // where '#' is a feature-specific version number. Extract it.
748 static std::string const k_DYNDEP_ = ".dyndep-";
749 std::string::size_type pos = this->NinjaVersion.find(k_DYNDEP_);
750 if (pos != std::string::npos) {
751 const char* fv = &this->NinjaVersion[pos + k_DYNDEP_.size()];
752 unsigned long dyndep = 0;
753 cmStrToULong(fv, &dyndep);
755 this->NinjaSupportsDyndeps = true;
759 this->NinjaSupportsUnconditionalRecompactTool =
760 !cmSystemTools::VersionCompare(
761 cmSystemTools::OP_LESS, this->NinjaVersion,
762 RequiredNinjaVersionForUnconditionalRecompactTool());
763 this->NinjaSupportsRestatTool =
764 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
765 RequiredNinjaVersionForRestatTool());
766 this->NinjaSupportsMultipleOutputs =
767 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
768 RequiredNinjaVersionForMultipleOutputs());
769 this->NinjaSupportsMetadataOnRegeneration = !cmSystemTools::VersionCompare(
770 cmSystemTools::OP_LESS, this->NinjaVersion,
771 RequiredNinjaVersionForMetadataOnRegeneration());
773 this->NinjaSupportsCodePage =
774 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
775 RequiredNinjaVersionForCodePage());
776 if (this->NinjaSupportsCodePage) {
777 this->CheckNinjaCodePage();
779 this->NinjaExpectedEncoding = codecvt::ANSI;
784 void cmGlobalNinjaGenerator::CheckNinjaCodePage()
786 std::vector<std::string> command{ this->NinjaCommand, "-t", "wincodepage" };
790 if (!cmSystemTools::RunSingleCommand(command, &output, &error, &result,
791 nullptr, cmSystemTools::OUTPUT_NONE)) {
792 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
793 cmStrCat("Running\n '",
794 cmJoin(command, "' '"),
798 cmSystemTools::SetFatalErrorOccurred();
799 } else if (result == 0) {
800 std::istringstream outputStream(output);
803 while (cmSystemTools::GetLineFromStream(outputStream, line)) {
804 if (cmHasLiteralPrefix(line, "Build file encoding: ")) {
805 cm::string_view lineView(line);
806 cm::string_view encoding =
807 lineView.substr(cmStrLen("Build file encoding: "));
808 if (encoding == "UTF-8") {
809 // Ninja expects UTF-8. We use that internally. No conversion needed.
810 this->NinjaExpectedEncoding = codecvt::None;
812 this->NinjaExpectedEncoding = codecvt::ANSI;
819 this->GetCMakeInstance()->IssueMessage(
820 MessageType::WARNING,
821 "Could not determine Ninja's code page, defaulting to UTF-8");
822 this->NinjaExpectedEncoding = codecvt::None;
825 this->NinjaExpectedEncoding = codecvt::ANSI;
829 bool cmGlobalNinjaGenerator::CheckLanguages(
830 std::vector<std::string> const& languages, cmMakefile* mf) const
832 if (cm::contains(languages, "Fortran")) {
833 return this->CheckFortran(mf);
835 if (cm::contains(languages, "ISPC")) {
836 return this->CheckISPC(mf);
838 if (cm::contains(languages, "Swift")) {
839 const std::string architectures =
840 mf->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
841 if (architectures.find_first_of(';') != std::string::npos) {
842 mf->IssueMessage(MessageType::FATAL_ERROR,
843 "multiple values for CMAKE_OSX_ARCHITECTURES not "
844 "supported with Swift");
845 cmSystemTools::SetFatalErrorOccurred();
852 bool cmGlobalNinjaGenerator::CheckCxxModuleSupport()
854 bool const diagnose = !this->DiagnosedCxxModuleSupport &&
855 !this->CMakeInstance->GetIsInTryCompile();
857 this->DiagnosedCxxModuleSupport = true;
858 this->GetCMakeInstance()->IssueMessage(
859 MessageType::AUTHOR_WARNING,
860 "C++20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP "
861 "is experimental. It is meant only for compiler developers to try.");
863 if (this->NinjaSupportsDyndeps) {
867 std::ostringstream e;
868 /* clang-format off */
870 "The Ninja generator does not support C++20 modules "
871 "using Ninja version \n"
872 " " << this->NinjaVersion << "\n"
873 "due to lack of required features. "
874 "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
876 /* clang-format on */
877 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str());
878 cmSystemTools::SetFatalErrorOccurred();
883 bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
885 if (this->NinjaSupportsDyndeps) {
889 std::ostringstream e;
890 /* clang-format off */
892 "The Ninja generator does not support Fortran using Ninja version\n"
893 " " << this->NinjaVersion << "\n"
894 "due to lack of required features. "
895 "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
897 /* clang-format on */
898 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
899 cmSystemTools::SetFatalErrorOccurred();
903 bool cmGlobalNinjaGenerator::CheckISPC(cmMakefile* mf) const
905 if (this->NinjaSupportsMultipleOutputs) {
909 std::ostringstream e;
910 /* clang-format off */
912 "The Ninja generator does not support ISPC using Ninja version\n"
913 " " << this->NinjaVersion << "\n"
914 "due to lack of required features. "
915 "Ninja " << RequiredNinjaVersionForMultipleOutputs() <<
916 " or higher is required."
918 /* clang-format on */
919 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
920 cmSystemTools::SetFatalErrorOccurred();
924 void cmGlobalNinjaGenerator::EnableLanguage(
925 std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
927 if (this->IsMultiConfig()) {
928 mf->InitCMAKE_CONFIGURATION_TYPES("Debug;Release;RelWithDebInfo");
931 this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
932 for (std::string const& l : langs) {
936 this->ResolveLanguageCompiler(l, mf, optional);
938 std::string const& compilerId =
939 mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_COMPILER_ID"));
940 std::string const& simulateId =
941 mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_SIMULATE_ID"));
942 std::string const& compilerFrontendVariant = mf->GetSafeDefinition(
943 cmStrCat("CMAKE_", l, "_COMPILER_FRONTEND_VARIANT"));
944 if ((compilerId == "Clang" && compilerFrontendVariant == "GNU") ||
945 (simulateId != "MSVC" &&
946 (compilerId == "GNU" || compilerId == "QCC" ||
947 cmHasLiteralSuffix(compilerId, "Clang")))) {
948 this->UsingGCCOnWindows = true;
955 // cmGlobalUnixMakefileGenerator3
956 // cmGlobalGhsMultiGenerator
957 // cmGlobalVisualStudio10Generator
958 // cmGlobalVisualStudio7Generator
959 // cmGlobalXCodeGenerator
961 // cmGlobalGenerator::Build()
962 std::vector<cmGlobalGenerator::GeneratedMakeCommand>
963 cmGlobalNinjaGenerator::GenerateBuildCommand(
964 const std::string& makeProgram, const std::string& /*projectName*/,
965 const std::string& /*projectDir*/,
966 std::vector<std::string> const& targetNames, const std::string& config,
967 int jobs, bool verbose, const cmBuildOptions& /*buildOptions*/,
968 std::vector<std::string> const& makeOptions)
970 GeneratedMakeCommand makeCommand;
971 makeCommand.Add(this->SelectMakeProgram(makeProgram));
974 makeCommand.Add("-v");
977 if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) &&
978 (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) {
979 makeCommand.Add("-j", std::to_string(jobs));
982 this->AppendNinjaFileArgument(makeCommand, config);
984 makeCommand.Add(makeOptions.begin(), makeOptions.end());
985 for (const auto& tname : targetNames) {
986 if (!tname.empty()) {
987 makeCommand.Add(tname);
990 return { std::move(makeCommand) };
993 // Non-virtual public methods.
995 void cmGlobalNinjaGenerator::AddRule(cmNinjaRule const& rule)
997 // Do not add the same rule twice.
998 if (!this->Rules.insert(rule.Name).second) {
1001 // Store command length
1002 this->RuleCmdLength[rule.Name] = static_cast<int>(rule.Command.size());
1004 cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, rule);
1007 bool cmGlobalNinjaGenerator::HasRule(const std::string& name)
1009 return (this->Rules.find(name) != this->Rules.end());
1012 // Private virtual overrides
1014 void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
1015 cmGeneratorTarget* gt) const
1017 // Compute full path to object file directory for this target.
1018 std::string dir = cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(),
1019 '/', gt->LocalGenerator->GetTargetDirectory(gt),
1020 '/', this->GetCMakeCFGIntDir(), '/');
1021 gt->ObjectDirectory = dir;
1026 bool cmGlobalNinjaGenerator::OpenBuildFileStreams()
1028 if (!this->OpenFileStream(this->BuildFileStream,
1029 cmGlobalNinjaGenerator::NINJA_BUILD_FILE)) {
1033 // Write a comment about this file.
1034 *this->BuildFileStream
1035 << "# This file contains all the build statements describing the\n"
1036 << "# compilation DAG.\n\n";
1041 bool cmGlobalNinjaGenerator::OpenFileStream(
1042 std::unique_ptr<cmGeneratedFileStream>& stream, const std::string& name)
1044 // Get a stream where to generate things.
1046 // Compute Ninja's build file path.
1048 cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/', name);
1049 stream = cm::make_unique<cmGeneratedFileStream>(
1050 path, false, this->GetMakefileEncoding());
1052 // An error message is generated by the constructor if it cannot
1057 // Write the do not edit header.
1058 this->WriteDisclaimer(*stream);
1064 cm::optional<std::set<std::string>> cmGlobalNinjaGenerator::ListSubsetWithAll(
1065 const std::set<std::string>& all, const std::set<std::string>& defaults,
1066 const std::vector<std::string>& items)
1068 std::set<std::string> result;
1070 for (auto const& item : items) {
1071 if (item == "all") {
1072 if (items.size() == 1) {
1077 } else if (all.count(item)) {
1078 result.insert(item);
1084 return cm::make_optional(result);
1087 void cmGlobalNinjaGenerator::CloseBuildFileStreams()
1089 if (this->BuildFileStream) {
1090 this->BuildFileStream.reset();
1092 cmSystemTools::Error("Build file stream was not open.");
1096 bool cmGlobalNinjaGenerator::OpenRulesFileStream()
1098 if (!this->OpenFileStream(this->RulesFileStream,
1099 cmGlobalNinjaGenerator::NINJA_RULES_FILE)) {
1103 // Write comment about this file.
1104 /* clang-format off */
1105 *this->RulesFileStream
1106 << "# This file contains all the rules used to get the outputs files\n"
1107 << "# built from the input files.\n"
1108 << "# It is included in the main '" << NINJA_BUILD_FILE << "'.\n\n"
1110 /* clang-format on */
1114 void cmGlobalNinjaGenerator::CloseRulesFileStream()
1116 if (this->RulesFileStream) {
1117 this->RulesFileStream.reset();
1119 cmSystemTools::Error("Rules file stream was not open.");
1123 static void EnsureTrailingSlash(std::string& path)
1128 std::string::value_type last = path.back();
1140 std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath(
1141 const std::string& path) const
1143 auto const f = this->ConvertToNinjaPathCache.find(path);
1144 if (f != this->ConvertToNinjaPathCache.end()) {
1148 std::string convPath =
1149 this->LocalGenerators[0]->MaybeRelativeToTopBinDir(path);
1150 convPath = this->NinjaOutputPath(convPath);
1152 std::replace(convPath.begin(), convPath.end(), '/', '\\');
1154 return this->ConvertToNinjaPathCache.emplace(path, std::move(convPath))
1158 std::string cmGlobalNinjaGenerator::ConvertToNinjaAbsPath(
1159 std::string path) const
1162 std::replace(path.begin(), path.end(), '/', '\\');
1167 void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName,
1168 const std::string& config)
1170 this->Configs[config].AdditionalCleanFiles.emplace(std::move(fileName));
1173 void cmGlobalNinjaGenerator::AddCXXCompileCommand(
1174 const std::string& commandLine, const std::string& sourceFile)
1176 // Compute Ninja's build file path.
1177 std::string buildFileDir =
1178 this->GetCMakeInstance()->GetHomeOutputDirectory();
1179 if (!this->CompileCommandsStream) {
1180 std::string buildFilePath =
1181 cmStrCat(buildFileDir, "/compile_commands.json");
1182 if (this->ComputingUnknownDependencies) {
1183 this->CombinedBuildOutputs.insert(
1184 this->NinjaOutputPath("compile_commands.json"));
1187 // Get a stream where to generate things.
1188 this->CompileCommandsStream =
1189 cm::make_unique<cmGeneratedFileStream>(buildFilePath);
1190 *this->CompileCommandsStream << "[\n";
1192 *this->CompileCommandsStream << ",\n";
1195 std::string sourceFileName = sourceFile;
1196 if (!cmSystemTools::FileIsFullPath(sourceFileName)) {
1197 sourceFileName = cmSystemTools::CollapseFullPath(
1198 sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory());
1201 /* clang-format off */
1202 *this->CompileCommandsStream << "{\n"
1203 << R"( "directory": ")"
1204 << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n"
1205 << R"( "command": ")"
1206 << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n"
1208 << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\"\n"
1210 /* clang-format on */
1213 void cmGlobalNinjaGenerator::CloseCompileCommandsStream()
1215 if (this->CompileCommandsStream) {
1216 *this->CompileCommandsStream << "\n]";
1217 this->CompileCommandsStream.reset();
1221 void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os) const
1223 os << "# CMAKE generated file: DO NOT EDIT!\n"
1224 << "# Generated by \"" << this->GetName() << "\""
1225 << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
1226 << cmVersion::GetMinorVersion() << "\n\n";
1229 void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies()
1231 for (auto const& asd : this->AssumedSourceDependencies) {
1232 CCOutputs outputs(this);
1233 outputs.ExplicitOuts.emplace_back(asd.first);
1234 cmNinjaDeps orderOnlyDeps;
1235 std::copy(asd.second.begin(), asd.second.end(),
1236 std::back_inserter(orderOnlyDeps));
1237 this->WriteCustomCommandBuild(
1238 /*command=*/"", /*description=*/"",
1239 "Assume dependencies for generated source file.",
1240 /*depfile*/ "", /*job_pool*/ "",
1241 /*uses_terminal*/ false,
1242 /*restat*/ true, std::string(), outputs, cmNinjaDeps(),
1243 std::move(orderOnlyDeps));
1247 std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
1248 cmGeneratorTarget const* target, const std::string& /*config*/) const
1250 return cmStrCat("cmake_object_order_depends_target_", target->GetName());
1253 void cmGlobalNinjaGenerator::AppendTargetOutputs(
1254 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1255 const std::string& config, cmNinjaTargetDepends depends) const
1257 // for frameworks, we want the real name, not smple name
1258 // frameworks always appear versioned, and the build.ninja
1259 // will always attempt to manage symbolic links instead
1260 // of letting cmOSXBundleGenerator do it.
1261 bool realname = target->IsFrameworkOnApple();
1263 switch (target->GetType()) {
1264 case cmStateEnums::SHARED_LIBRARY:
1265 case cmStateEnums::STATIC_LIBRARY:
1266 case cmStateEnums::MODULE_LIBRARY: {
1267 if (depends == DependOnTargetOrdering) {
1268 outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1273 case cmStateEnums::EXECUTABLE: {
1274 outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
1275 config, cmStateEnums::RuntimeBinaryArtifact, realname)));
1278 case cmStateEnums::OBJECT_LIBRARY: {
1279 if (depends == DependOnTargetOrdering) {
1280 outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1285 case cmStateEnums::GLOBAL_TARGET:
1286 case cmStateEnums::INTERFACE_LIBRARY:
1287 case cmStateEnums::UTILITY: {
1289 cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1291 std::string output = this->ConvertToNinjaPath(path);
1292 if (target->Target->IsPerConfig()) {
1293 output = this->BuildAlias(output, config);
1295 outputs.push_back(output);
1299 case cmStateEnums::UNKNOWN_LIBRARY:
1304 void cmGlobalNinjaGenerator::AppendTargetDepends(
1305 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1306 const std::string& config, const std::string& fileConfig,
1307 cmNinjaTargetDepends depends)
1309 if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
1310 // These depend only on other CMake-provided targets, e.g. "all".
1311 for (BT<std::pair<std::string, bool>> const& util :
1312 target->GetUtilities()) {
1314 cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1316 outputs.push_back(this->BuildAlias(this->ConvertToNinjaPath(d), config));
1321 auto computeISPCOuputs = [](cmGlobalNinjaGenerator* gg,
1322 cmGeneratorTarget const* depTarget,
1323 cmNinjaDeps& outputDeps,
1324 const std::string& targetConfig) {
1325 if (depTarget->CanCompileSources()) {
1326 auto headers = depTarget->GetGeneratedISPCHeaders(targetConfig);
1327 if (!headers.empty()) {
1328 std::transform(headers.begin(), headers.end(), headers.begin(),
1329 gg->MapToNinjaPath());
1330 outputDeps.insert(outputDeps.end(), headers.begin(), headers.end());
1332 auto objs = depTarget->GetGeneratedISPCObjects(targetConfig);
1333 if (!objs.empty()) {
1334 std::transform(objs.begin(), objs.end(), objs.begin(),
1335 gg->MapToNinjaPath());
1336 outputDeps.insert(outputDeps.end(), objs.begin(), objs.end());
1341 for (cmTargetDepend const& targetDep :
1342 this->GetTargetDirectDepends(target)) {
1343 if (!targetDep->IsInBuildSystem()) {
1346 if (targetDep.IsCross()) {
1347 this->AppendTargetOutputs(targetDep, outs, fileConfig, depends);
1348 computeISPCOuputs(this, targetDep, outs, fileConfig);
1350 this->AppendTargetOutputs(targetDep, outs, config, depends);
1351 computeISPCOuputs(this, targetDep, outs, config);
1354 std::sort(outs.begin(), outs.end());
1355 cm::append(outputs, outs);
1359 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1360 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1361 const std::string& config, const std::string& fileConfig, bool genexOutput)
1364 this->AppendTargetDependsClosure(target, outs, config, fileConfig,
1366 cm::append(outputs, outs);
1369 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1370 cmGeneratorTarget const* target, cmNinjaOuts& outputs,
1371 const std::string& config, const std::string& fileConfig, bool genexOutput,
1375 // try to locate the target in the cache
1376 ByConfig::TargetDependsClosureKey key{
1381 auto find = this->Configs[fileConfig].TargetDependsClosures.lower_bound(key);
1383 if (find == this->Configs[fileConfig].TargetDependsClosures.end() ||
1384 find->first != key) {
1385 // We now calculate the closure outputs by inspecting the dependent
1386 // targets recursively.
1387 // For that we have to distinguish between a local result set that is only
1388 // relevant for filling the cache entries properly isolated and a global
1389 // result set that is relevant for the result of the top level call to
1390 // AppendTargetDependsClosure.
1391 cmNinjaOuts this_outs; // this will be the new cache entry
1393 for (auto const& dep_target : this->GetTargetDirectDepends(target)) {
1394 if (!dep_target->IsInBuildSystem()) {
1398 if (!this->IsSingleConfigUtility(target) &&
1399 !this->IsSingleConfigUtility(dep_target) &&
1400 this->EnableCrossConfigBuild() && !dep_target.IsCross() &&
1405 if (dep_target.IsCross()) {
1406 this->AppendTargetDependsClosure(dep_target, this_outs, fileConfig,
1407 fileConfig, genexOutput, false);
1409 this->AppendTargetDependsClosure(dep_target, this_outs, config,
1410 fileConfig, genexOutput, false);
1413 find = this->Configs[fileConfig].TargetDependsClosures.emplace_hint(
1414 find, key, std::move(this_outs));
1417 // now fill the outputs of the final result from the newly generated cache
1419 outputs.insert(find->second.begin(), find->second.end());
1421 // finally generate the outputs of the target itself, if applicable
1424 this->AppendTargetOutputs(target, outs, config, DependOnTargetArtifact);
1426 outputs.insert(outs.begin(), outs.end());
1429 void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias,
1430 cmGeneratorTarget* target,
1431 const std::string& config)
1433 std::string outputPath = this->NinjaOutputPath(alias);
1434 std::string buildAlias = this->BuildAlias(outputPath, config);
1435 cmNinjaDeps outputs;
1436 if (config != "all") {
1437 this->AppendTargetOutputs(target, outputs, config, DependOnTargetArtifact);
1439 // Mark the target's outputs as ambiguous to ensure that no other target
1440 // uses the output as an alias.
1441 for (std::string const& output : outputs) {
1442 this->TargetAliases[output].GeneratorTarget = nullptr;
1443 this->DefaultTargetAliases[output].GeneratorTarget = nullptr;
1444 for (const std::string& config2 :
1445 this->Makefiles.front()->GetGeneratorConfigs(
1446 cmMakefile::IncludeEmptyConfig)) {
1447 this->Configs[config2].TargetAliases[output].GeneratorTarget = nullptr;
1451 // Insert the alias into the map. If the alias was already present in the
1452 // map and referred to another target, mark it as ambiguous.
1454 ta.GeneratorTarget = target;
1457 auto newAliasGlobal =
1458 this->TargetAliases.insert(std::make_pair(buildAlias, ta));
1459 if (newAliasGlobal.second &&
1460 newAliasGlobal.first->second.GeneratorTarget != target) {
1461 newAliasGlobal.first->second.GeneratorTarget = nullptr;
1464 auto newAliasConfig =
1465 this->Configs[config].TargetAliases.insert(std::make_pair(outputPath, ta));
1466 if (newAliasConfig.second &&
1467 newAliasConfig.first->second.GeneratorTarget != target) {
1468 newAliasConfig.first->second.GeneratorTarget = nullptr;
1470 if (this->DefaultConfigs.count(config)) {
1471 auto newAliasDefaultGlobal =
1472 this->DefaultTargetAliases.insert(std::make_pair(outputPath, ta));
1473 if (newAliasDefaultGlobal.second &&
1474 newAliasDefaultGlobal.first->second.GeneratorTarget != target) {
1475 newAliasDefaultGlobal.first->second.GeneratorTarget = nullptr;
1480 void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
1482 cmGlobalNinjaGenerator::WriteDivider(os);
1483 os << "# Target aliases.\n\n";
1485 cmNinjaBuild build("phony");
1486 build.Outputs.emplace_back();
1487 for (auto const& ta : this->TargetAliases) {
1488 // Don't write ambiguous aliases.
1489 if (!ta.second.GeneratorTarget) {
1493 // Don't write alias if there is a already a custom command with
1495 if (this->HasCustomCommandOutput(ta.first)) {
1499 build.Outputs.front() = ta.first;
1500 build.ExplicitDeps.clear();
1501 if (ta.second.Config == "all") {
1502 for (auto const& config : this->CrossConfigs) {
1503 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1504 build.ExplicitDeps, config,
1505 DependOnTargetArtifact);
1508 this->AppendTargetOutputs(ta.second.GeneratorTarget, build.ExplicitDeps,
1509 ta.second.Config, DependOnTargetArtifact);
1511 this->WriteBuild(this->EnableCrossConfigBuild() &&
1512 (ta.second.Config == "all" ||
1513 this->CrossConfigs.count(ta.second.Config))
1515 : *this->GetImplFileStream(ta.second.Config),
1519 if (this->IsMultiConfig()) {
1520 for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs(
1521 cmMakefile::IncludeEmptyConfig)) {
1522 for (auto const& ta : this->Configs[config].TargetAliases) {
1523 // Don't write ambiguous aliases.
1524 if (!ta.second.GeneratorTarget) {
1528 // Don't write alias if there is a already a custom command with
1530 if (this->HasCustomCommandOutput(ta.first)) {
1534 build.Outputs.front() = ta.first;
1535 build.ExplicitDeps.clear();
1536 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1537 build.ExplicitDeps, config,
1538 DependOnTargetArtifact);
1539 this->WriteBuild(*this->GetConfigFileStream(config), build);
1543 if (!this->DefaultConfigs.empty()) {
1544 for (auto const& ta : this->DefaultTargetAliases) {
1545 // Don't write ambiguous aliases.
1546 if (!ta.second.GeneratorTarget) {
1550 // Don't write alias if there is a already a custom command with
1552 if (this->HasCustomCommandOutput(ta.first)) {
1556 build.Outputs.front() = ta.first;
1557 build.ExplicitDeps.clear();
1558 for (auto const& config : this->DefaultConfigs) {
1559 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1560 build.ExplicitDeps, config,
1561 DependOnTargetArtifact);
1563 this->WriteBuild(*this->GetDefaultFileStream(), build);
1569 void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
1571 cmGlobalNinjaGenerator::WriteDivider(os);
1572 os << "# Folder targets.\n\n";
1574 std::map<std::string, DirectoryTarget> dirTargets =
1575 this->ComputeDirectoryTargets();
1577 for (auto const& it : dirTargets) {
1578 cmNinjaBuild build("phony");
1579 cmGlobalNinjaGenerator::WriteDivider(os);
1580 std::string const& currentBinaryDir = it.first;
1581 DirectoryTarget const& dt = it.second;
1582 std::vector<std::string> configs =
1583 dt.LG->GetMakefile()->GetGeneratorConfigs(
1584 cmMakefile::IncludeEmptyConfig);
1587 cmNinjaDeps configDeps;
1588 build.Comment = cmStrCat("Folder: ", currentBinaryDir);
1589 build.Outputs.emplace_back();
1590 std::string const buildDirAllTarget =
1591 this->ConvertToNinjaPath(cmStrCat(currentBinaryDir, "/all"));
1592 for (auto const& config : configs) {
1593 build.ExplicitDeps.clear();
1594 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
1595 configDeps.emplace_back(build.Outputs.front());
1596 for (DirectoryTarget::Target const& t : dt.Targets) {
1597 if (!this->IsExcludedFromAllInConfig(t, config)) {
1598 this->AppendTargetOutputs(t.GT, build.ExplicitDeps, config,
1599 DependOnTargetArtifact);
1602 for (DirectoryTarget::Dir const& d : dt.Children) {
1603 if (!d.ExcludeFromAll) {
1604 build.ExplicitDeps.emplace_back(this->BuildAlias(
1605 this->ConvertToNinjaPath(cmStrCat(d.Path, "/all")), config));
1609 this->WriteBuild(this->EnableCrossConfigBuild() &&
1610 this->CrossConfigs.count(config)
1612 : *this->GetImplFileStream(config),
1616 // Add shortcut target
1617 if (this->IsMultiConfig()) {
1618 for (auto const& config : configs) {
1619 build.ExplicitDeps = { this->BuildAlias(buildDirAllTarget, config) };
1620 build.Outputs.front() = buildDirAllTarget;
1621 this->WriteBuild(*this->GetConfigFileStream(config), build);
1624 if (!this->DefaultFileConfig.empty()) {
1625 build.ExplicitDeps.clear();
1626 for (auto const& config : this->DefaultConfigs) {
1627 build.ExplicitDeps.push_back(
1628 this->BuildAlias(buildDirAllTarget, config));
1630 build.Outputs.front() = buildDirAllTarget;
1631 this->WriteBuild(*this->GetDefaultFileStream(), build);
1635 // Add target for all configs
1636 if (this->EnableCrossConfigBuild()) {
1637 build.ExplicitDeps.clear();
1638 for (auto const& config : this->CrossConfigs) {
1639 build.ExplicitDeps.push_back(
1640 this->BuildAlias(buildDirAllTarget, config));
1642 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, "all");
1643 this->WriteBuild(os, build);
1648 void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os)
1650 if (!this->ComputingUnknownDependencies) {
1654 // We need to collect the set of known build outputs.
1655 // Start with those generated by WriteBuild calls.
1656 // No other method needs this so we can take ownership
1657 // of the set locally and throw it out when we are done.
1658 std::set<std::string> knownDependencies;
1659 knownDependencies.swap(this->CombinedBuildOutputs);
1661 // now write out the unknown explicit dependencies.
1663 // union the configured files, evaluations files and the
1664 // CombinedBuildOutputs,
1665 // and then difference with CombinedExplicitDependencies to find the explicit
1666 // dependencies that we have no rule for
1668 cmGlobalNinjaGenerator::WriteDivider(os);
1669 /* clang-format off */
1670 os << "# Unknown Build Time Dependencies.\n"
1671 << "# Tell Ninja that they may appear as side effects of build rules\n"
1672 << "# otherwise ordered by order-only dependencies.\n\n";
1673 /* clang-format on */
1675 // get the list of files that cmake itself has generated as a
1676 // product of configuration.
1678 for (const auto& lg : this->LocalGenerators) {
1679 // get the vector of files created by this makefile and convert them
1680 // to ninja paths, which are all relative in respect to the build directory
1681 for (std::string const& file : lg->GetMakefile()->GetOutputFiles()) {
1682 knownDependencies.insert(this->ConvertToNinjaPath(file));
1684 if (!this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1685 // get list files which are implicit dependencies as well and will be
1686 // phony for rebuild manifest
1687 for (std::string const& j : lg->GetMakefile()->GetListFiles()) {
1688 knownDependencies.insert(this->ConvertToNinjaPath(j));
1691 for (const auto& li : lg->GetMakefile()->GetEvaluationFiles()) {
1692 // get all the files created by generator expressions and convert them
1694 for (std::string const& evaluationFile : li->GetFiles()) {
1695 knownDependencies.insert(this->ConvertToNinjaPath(evaluationFile));
1699 knownDependencies.insert(this->CMakeCacheFile);
1701 for (auto const& ta : this->TargetAliases) {
1702 knownDependencies.insert(this->ConvertToNinjaPath(ta.first));
1705 // remove all source files we know will exist.
1706 for (auto const& i : this->AssumedSourceDependencies) {
1707 knownDependencies.insert(this->ConvertToNinjaPath(i.first));
1710 // now we difference with CombinedCustomCommandExplicitDependencies to find
1711 // the list of items we know nothing about.
1712 // We have encoded all the paths in CombinedCustomCommandExplicitDependencies
1713 // and knownDependencies so no matter if unix or windows paths they
1714 // should all match now.
1716 std::vector<std::string> unknownExplicitDepends;
1717 this->CombinedCustomCommandExplicitDependencies.erase(this->TargetAll);
1719 std::set_difference(this->CombinedCustomCommandExplicitDependencies.begin(),
1720 this->CombinedCustomCommandExplicitDependencies.end(),
1721 knownDependencies.begin(), knownDependencies.end(),
1722 std::back_inserter(unknownExplicitDepends));
1724 std::vector<std::string> warnExplicitDepends;
1725 if (!unknownExplicitDepends.empty()) {
1726 cmake* cmk = this->GetCMakeInstance();
1727 std::string const& buildRoot = cmk->GetHomeOutputDirectory();
1728 bool const inSource = (buildRoot == cmk->GetHomeDirectory());
1729 bool const warn = (!inSource && (this->PolicyCMP0058 == cmPolicies::WARN));
1730 cmNinjaBuild build("phony");
1731 build.Outputs.emplace_back("");
1732 for (std::string const& ued : unknownExplicitDepends) {
1733 // verify the file is in the build directory
1734 std::string const absDepPath =
1735 cmSystemTools::CollapseFullPath(ued, buildRoot);
1736 if (cmSystemTools::IsSubDirectory(absDepPath, buildRoot)) {
1737 // Generate phony build statement
1738 build.Outputs[0] = ued;
1739 this->WriteBuild(os, build);
1740 // Add to warning on demand
1741 if (warn && warnExplicitDepends.size() < 10) {
1742 warnExplicitDepends.push_back(ued);
1748 if (!warnExplicitDepends.empty()) {
1749 std::ostringstream w;
1750 /* clang-format off */
1751 w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0058) << "\n"
1752 "This project specifies custom command DEPENDS on files "
1753 "in the build tree that are not specified as the OUTPUT or "
1754 "BYPRODUCTS of any add_custom_command or add_custom_target:\n"
1755 " " << cmJoin(warnExplicitDepends, "\n ") <<
1757 "For compatibility with versions of CMake that did not have "
1758 "the BYPRODUCTS option, CMake is generating phony rules for "
1759 "such files to convince 'ninja' to build."
1761 "Project authors should add the missing BYPRODUCTS or OUTPUT "
1762 "options to the custom commands that produce these files."
1764 /* clang-format on */
1765 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1770 void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os)
1773 cmGlobalNinjaGenerator::WriteDivider(os);
1774 os << "# Built-in targets\n\n";
1776 this->WriteTargetRebuildManifest(os);
1777 this->WriteTargetClean(os);
1778 this->WriteTargetHelp(os);
1780 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
1781 cmMakefile::IncludeEmptyConfig)) {
1782 this->WriteTargetDefault(*this->GetConfigFileStream(config));
1785 if (!this->DefaultFileConfig.empty()) {
1786 this->WriteTargetDefault(*this->GetDefaultFileStream());
1790 void cmGlobalNinjaGenerator::WriteTargetDefault(std::ostream& os)
1792 if (!this->HasOutputPathPrefix()) {
1794 all.push_back(this->TargetAll);
1795 cmGlobalNinjaGenerator::WriteDefault(os, all,
1796 "Make the all target the default.");
1800 void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
1802 if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1805 const auto& lg = this->LocalGenerators[0];
1808 cmNinjaRule rule("RERUN_CMAKE");
1810 cmStrCat(this->CMakeCmd(), " --regenerate-during-build -S",
1811 lg->ConvertToOutputFormat(lg->GetSourceDirectory(),
1812 cmOutputConverter::SHELL),
1814 lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
1815 cmOutputConverter::SHELL));
1816 rule.Description = "Re-running CMake...";
1817 rule.Comment = "Rule for re-running cmake.";
1818 rule.Generator = true;
1819 WriteRule(*this->RulesFileStream, rule);
1822 cmNinjaBuild reBuild("RERUN_CMAKE");
1823 reBuild.Comment = "Re-run CMake if any of its inputs changed.";
1824 this->AddRebuildManifestOutputs(reBuild.Outputs);
1826 for (const auto& localGen : this->LocalGenerators) {
1827 for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) {
1828 reBuild.ImplicitDeps.push_back(this->ConvertToNinjaPath(fi));
1831 reBuild.ImplicitDeps.push_back(this->CMakeCacheFile);
1833 // Use 'console' pool to get non buffered output of the CMake re-run call
1834 // Available since Ninja 1.5
1835 if (this->SupportsDirectConsole()) {
1836 reBuild.Variables["pool"] = "console";
1839 cmake* cm = this->GetCMakeInstance();
1840 if (this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) {
1842 cmNinjaRule rule("VERIFY_GLOBS");
1844 cmStrCat(this->CMakeCmd(), " -P ",
1845 lg->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
1846 cmOutputConverter::SHELL));
1847 rule.Description = "Re-checking globbed directories...";
1848 rule.Comment = "Rule for re-checking globbed directories.";
1849 rule.Generator = true;
1850 this->WriteRule(*this->RulesFileStream, rule);
1853 cmNinjaBuild phonyBuild("phony");
1854 phonyBuild.Comment = "Phony target to force glob verification run.";
1855 phonyBuild.Outputs.push_back(
1856 cmStrCat(cm->GetGlobVerifyScript(), "_force"));
1857 this->WriteBuild(os, phonyBuild);
1859 reBuild.Variables["restat"] = "1";
1860 std::string const verifyScriptFile =
1861 this->NinjaOutputPath(cm->GetGlobVerifyScript());
1862 std::string const verifyStampFile =
1863 this->NinjaOutputPath(cm->GetGlobVerifyStamp());
1865 cmNinjaBuild vgBuild("VERIFY_GLOBS");
1867 "Re-run CMake to check if globbed directories changed.";
1868 vgBuild.Outputs.push_back(verifyStampFile);
1869 vgBuild.ImplicitDeps = phonyBuild.Outputs;
1870 vgBuild.Variables = reBuild.Variables;
1871 this->WriteBuild(os, vgBuild);
1873 reBuild.Variables.erase("restat");
1874 reBuild.ImplicitDeps.push_back(verifyScriptFile);
1875 reBuild.ExplicitDeps.push_back(verifyStampFile);
1876 } else if (!this->SupportsManifestRestat() &&
1877 cm->DoWriteGlobVerifyTarget()) {
1878 std::ostringstream msg;
1879 msg << "The detected version of Ninja:\n"
1880 << " " << this->NinjaVersion << "\n"
1881 << "is less than the version of Ninja required by CMake for adding "
1882 "restat dependencies to the build.ninja manifest regeneration "
1885 << cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat()
1887 msg << "Any pre-check scripts, such as those generated for file(GLOB "
1888 "CONFIGURE_DEPENDS), will not be run by Ninja.";
1889 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1893 std::sort(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end());
1894 reBuild.ImplicitDeps.erase(
1895 std::unique(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()),
1896 reBuild.ImplicitDeps.end());
1898 this->WriteBuild(os, reBuild);
1901 cmNinjaBuild build("phony");
1902 build.Comment = "A missing CMake input file is not an error.";
1903 std::set_difference(std::make_move_iterator(reBuild.ImplicitDeps.begin()),
1904 std::make_move_iterator(reBuild.ImplicitDeps.end()),
1905 this->CustomCommandOutputs.begin(),
1906 this->CustomCommandOutputs.end(),
1907 std::back_inserter(build.Outputs));
1908 this->WriteBuild(os, build);
1912 std::string cmGlobalNinjaGenerator::CMakeCmd() const
1914 const auto& lgen = this->LocalGenerators.at(0);
1915 return lgen->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
1916 cmOutputConverter::SHELL);
1919 std::string cmGlobalNinjaGenerator::NinjaCmd() const
1921 const auto& lgen = this->LocalGenerators[0];
1922 if (lgen != nullptr) {
1923 return lgen->ConvertToOutputFormat(this->NinjaCommand,
1924 cmOutputConverter::SHELL);
1929 bool cmGlobalNinjaGenerator::SupportsDirectConsole() const
1931 return this->NinjaSupportsConsolePool;
1934 bool cmGlobalNinjaGenerator::SupportsImplicitOuts() const
1936 return this->NinjaSupportsImplicitOuts;
1939 bool cmGlobalNinjaGenerator::SupportsManifestRestat() const
1941 return this->NinjaSupportsManifestRestat;
1944 bool cmGlobalNinjaGenerator::SupportsMultilineDepfile() const
1946 return this->NinjaSupportsMultilineDepfile;
1949 bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
1951 const auto& lgr = this->LocalGenerators.at(0);
1952 std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake";
1953 std::string cleanScriptAbs =
1954 cmStrCat(lgr->GetBinaryDirectory(), '/', cleanScriptRel);
1955 std::vector<std::string> configs =
1956 this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
1958 // Check if there are additional files to clean
1960 for (auto const& config : configs) {
1961 auto const it = this->Configs.find(config);
1962 if (it != this->Configs.end() &&
1963 !it->second.AdditionalCleanFiles.empty()) {
1969 // Remove cmake clean script file if it exists
1970 cmSystemTools::RemoveFile(cleanScriptAbs);
1974 // Write cmake clean script file
1976 cmGeneratedFileStream fout(cleanScriptAbs);
1980 fout << "# Additional clean files\ncmake_minimum_required(VERSION 3.16)\n";
1981 for (auto const& config : configs) {
1982 auto const it = this->Configs.find(config);
1983 if (it != this->Configs.end() &&
1984 !it->second.AdditionalCleanFiles.empty()) {
1985 fout << "\nif(\"${CONFIG}\" STREQUAL \"\" OR \"${CONFIG}\" STREQUAL \""
1986 << config << "\")\n";
1987 fout << " file(REMOVE_RECURSE\n";
1988 for (std::string const& acf : it->second.AdditionalCleanFiles) {
1990 << cmOutputConverter::EscapeForCMake(
1991 this->ConvertToNinjaPath(acf))
1995 fout << "endif()\n";
1999 // Register clean script file
2000 lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs);
2004 cmNinjaRule rule("CLEAN_ADDITIONAL");
2005 rule.Command = cmStrCat(
2006 this->CMakeCmd(), " -DCONFIG=$CONFIG -P ",
2007 lgr->ConvertToOutputFormat(this->NinjaOutputPath(cleanScriptRel),
2008 cmOutputConverter::SHELL));
2009 rule.Description = "Cleaning additional files...";
2010 rule.Comment = "Rule for cleaning additional files.";
2011 WriteRule(*this->RulesFileStream, rule);
2016 cmNinjaBuild build("CLEAN_ADDITIONAL");
2017 build.Comment = "Clean additional files.";
2018 build.Outputs.emplace_back();
2019 for (auto const& config : configs) {
2020 build.Outputs.front() = this->BuildAlias(
2021 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()), config);
2022 build.Variables["CONFIG"] = config;
2023 this->WriteBuild(os, build);
2025 if (this->IsMultiConfig()) {
2026 build.Outputs.front() =
2027 this->NinjaOutputPath(this->GetAdditionalCleanTargetName());
2028 build.Variables["CONFIG"] = "";
2029 this->WriteBuild(os, build);
2036 void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
2038 // -- Additional clean target
2039 bool additionalFiles = this->WriteTargetCleanAdditional(os);
2041 // -- Default clean target
2044 cmNinjaRule rule("CLEAN");
2045 rule.Command = cmStrCat(this->NinjaCmd(), " $FILE_ARG -t clean $TARGETS");
2046 rule.Description = "Cleaning all built files...";
2047 rule.Comment = "Rule for cleaning all built files.";
2048 WriteRule(*this->RulesFileStream, rule);
2051 auto const configs = this->Makefiles.front()->GetGeneratorConfigs(
2052 cmMakefile::IncludeEmptyConfig);
2056 cmNinjaBuild build("CLEAN");
2057 build.Comment = "Clean all the built files.";
2058 build.Outputs.emplace_back();
2060 for (auto const& config : configs) {
2061 build.Outputs.front() = this->BuildAlias(
2062 this->NinjaOutputPath(this->GetCleanTargetName()), config);
2063 if (this->IsMultiConfig()) {
2064 build.Variables["TARGETS"] =
2065 cmStrCat(this->BuildAlias(GetByproductsForCleanTargetName(), config),
2066 " ", GetByproductsForCleanTargetName());
2068 build.ExplicitDeps.clear();
2069 if (additionalFiles) {
2070 build.ExplicitDeps.push_back(this->BuildAlias(
2071 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2074 for (auto const& fileConfig : configs) {
2075 if (fileConfig != config && !this->EnableCrossConfigBuild()) {
2078 if (this->IsMultiConfig()) {
2079 build.Variables["FILE_ARG"] = cmStrCat(
2081 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
2083 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2087 if (this->EnableCrossConfigBuild()) {
2088 build.Outputs.front() = this->BuildAlias(
2089 this->NinjaOutputPath(this->GetCleanTargetName()), "all");
2090 build.ExplicitDeps.clear();
2092 if (additionalFiles) {
2093 for (auto const& config : this->CrossConfigs) {
2094 build.ExplicitDeps.push_back(this->BuildAlias(
2095 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2100 std::vector<std::string> byproducts;
2101 for (auto const& config : this->CrossConfigs) {
2102 byproducts.push_back(
2103 this->BuildAlias(GetByproductsForCleanTargetName(), config));
2105 byproducts.emplace_back(GetByproductsForCleanTargetName());
2106 build.Variables["TARGETS"] = cmJoin(byproducts, " ");
2108 for (auto const& fileConfig : configs) {
2109 build.Variables["FILE_ARG"] = cmStrCat(
2111 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
2112 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2117 if (this->IsMultiConfig()) {
2118 cmNinjaBuild build("phony");
2119 build.Outputs.emplace_back(
2120 this->NinjaOutputPath(this->GetCleanTargetName()));
2121 build.ExplicitDeps.emplace_back();
2123 for (auto const& config : configs) {
2124 build.ExplicitDeps.front() = this->BuildAlias(
2125 this->NinjaOutputPath(this->GetCleanTargetName()), config);
2126 this->WriteBuild(*this->GetConfigFileStream(config), build);
2129 if (!this->DefaultConfigs.empty()) {
2130 build.ExplicitDeps.clear();
2131 for (auto const& config : this->DefaultConfigs) {
2132 build.ExplicitDeps.push_back(this->BuildAlias(
2133 this->NinjaOutputPath(this->GetCleanTargetName()), config));
2135 this->WriteBuild(*this->GetDefaultFileStream(), build);
2140 if (this->IsMultiConfig()) {
2141 cmNinjaBuild build("phony");
2142 build.Comment = "Clean byproducts.";
2143 build.Outputs.emplace_back(
2144 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()));
2145 build.ExplicitDeps = this->ByproductsForCleanTarget;
2146 this->WriteBuild(os, build);
2148 for (auto const& config : configs) {
2149 build.Outputs.front() = this->BuildAlias(
2150 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()), config);
2151 build.ExplicitDeps = this->Configs[config].ByproductsForCleanTarget;
2152 this->WriteBuild(os, build);
2157 void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os)
2160 cmNinjaRule rule("HELP");
2161 rule.Command = cmStrCat(this->NinjaCmd(), " -t targets");
2162 rule.Description = "All primary targets available:";
2163 rule.Comment = "Rule for printing all primary targets available.";
2164 WriteRule(*this->RulesFileStream, rule);
2167 cmNinjaBuild build("HELP");
2168 build.Comment = "Print all primary targets available.";
2169 build.Outputs.push_back(this->NinjaOutputPath("help"));
2170 this->WriteBuild(os, build);
2174 void cmGlobalNinjaGenerator::InitOutputPathPrefix()
2176 this->OutputPathPrefix =
2177 this->LocalGenerators[0]->GetMakefile()->GetSafeDefinition(
2178 "CMAKE_NINJA_OUTPUT_PATH_PREFIX");
2179 EnsureTrailingSlash(this->OutputPathPrefix);
2182 std::string cmGlobalNinjaGenerator::NinjaOutputPath(
2183 std::string const& path) const
2185 if (!this->HasOutputPathPrefix() || cmSystemTools::FileIsFullPath(path)) {
2188 return cmStrCat(this->OutputPathPrefix, path);
2191 void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix(
2197 EnsureTrailingSlash(path);
2198 cmStripSuffixIfExists(path, this->OutputPathPrefix);
2201 #if !defined(CMAKE_BOOTSTRAP)
2205 We use the following approach to support Fortran. Each target already
2206 has a <target>.dir/ directory used to hold intermediate files for CMake.
2207 For each target, a FortranDependInfo.json file is generated by CMake with
2208 information about include directories, module directories, and the locations
2209 the per-target directories for target dependencies.
2211 Compilation of source files within a target is split into the following steps:
2213 1. Preprocess all sources, scan preprocessed output for module dependencies.
2214 This step is done with independent build statements for each source,
2215 and can therefore be done in parallel.
2217 rule Fortran_PREPROCESS
2219 command = gfortran -cpp $DEFINES $INCLUDES $FLAGS -E $in -o $out &&
2220 cmake -E cmake_ninja_depends \
2221 --tdi=FortranDependInfo.json --pp=$out --dep=$DEP_FILE \
2222 --obj=$OBJ_FILE --ddi=$DYNDEP_INTERMEDIATE_FILE \
2225 build src.f90-pp.f90 | src.f90.o.ddi: Fortran_PREPROCESS src.f90
2226 OBJ_FILE = src.f90.o
2227 DEP_FILE = src.f90.o.d
2228 DYNDEP_INTERMEDIATE_FILE = src.f90.o.ddi
2230 The ``cmake -E cmake_ninja_depends`` tool reads the preprocessed output
2231 and generates the ninja depfile for preprocessor dependencies. It also
2232 generates a "ddi" file (in a format private to CMake) that lists the
2233 object file that compilation will produce along with the module names
2234 it provides and/or requires. The "ddi" file is an implicit output
2235 because it should not appear in "$out" but is generated by the rule.
2237 2. Consolidate the per-source module dependencies saved in the "ddi"
2238 files from all sources to produce a ninja "dyndep" file, ``Fortran.dd``.
2241 command = cmake -E cmake_ninja_dyndep \
2242 --tdi=FortranDependInfo.json --lang=Fortran --dd=$out $in
2244 build Fortran.dd: Fortran_DYNDEP src1.f90.o.ddi src2.f90.o.ddi
2246 The ``cmake -E cmake_ninja_dyndep`` tool reads the "ddi" files from all
2247 sources in the target and the ``FortranModules.json`` files from targets
2248 on which the target depends. It computes dependency edges on compilations
2249 that require modules to those that provide the modules. This information
2250 is placed in the ``Fortran.dd`` file for ninja to load later. It also
2251 writes the expected location of modules provided by this target into
2252 ``FortranModules.json`` for use by dependent targets.
2254 3. Compile all sources after loading dynamically discovered dependencies
2255 of the compilation build statements from their ``dyndep`` bindings.
2257 rule Fortran_COMPILE
2258 command = gfortran $INCLUDES $FLAGS -c $in -o $out
2260 build src1.f90.o: Fortran_COMPILE src1.f90-pp.f90 || Fortran.dd
2263 The "dyndep" binding tells ninja to load dynamically discovered
2264 dependency information from ``Fortran.dd``. This adds information
2267 build src1.f90.o | mod1.mod: dyndep
2270 This tells ninja that ``mod1.mod`` is an implicit output of compiling
2271 the object file ``src1.f90.o``. The ``restat`` binding tells it that
2272 the timestamp of the output may not always change. Additionally:
2274 build src2.f90.o: dyndep | mod1.mod
2276 This tells ninja that ``mod1.mod`` is a dependency of compiling the
2277 object file ``src2.f90.o``. This ensures that ``src1.f90.o`` and
2278 ``mod1.mod`` will always be up to date before ``src2.f90.o`` is built
2279 (because the latter consumes the module).
2286 cmScanDepInfo ScanDep;
2287 std::vector<std::string> Includes;
2290 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2291 std::string const& arg_tdi, std::string const& arg_pp);
2294 int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
2295 std::vector<std::string>::const_iterator argEnd)
2297 std::string arg_tdi;
2299 std::string arg_dep;
2300 std::string arg_obj;
2301 std::string arg_ddi;
2302 std::string arg_lang;
2303 for (std::string const& arg : cmMakeRange(argBeg, argEnd)) {
2304 if (cmHasLiteralPrefix(arg, "--tdi=")) {
2305 arg_tdi = arg.substr(6);
2306 } else if (cmHasLiteralPrefix(arg, "--pp=")) {
2307 arg_pp = arg.substr(5);
2308 } else if (cmHasLiteralPrefix(arg, "--dep=")) {
2309 arg_dep = arg.substr(6);
2310 } else if (cmHasLiteralPrefix(arg, "--obj=")) {
2311 arg_obj = arg.substr(6);
2312 } else if (cmHasLiteralPrefix(arg, "--ddi=")) {
2313 arg_ddi = arg.substr(6);
2314 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2315 arg_lang = arg.substr(7);
2317 cmSystemTools::Error(
2318 cmStrCat("-E cmake_ninja_depends unknown argument: ", arg));
2322 if (arg_tdi.empty()) {
2323 cmSystemTools::Error("-E cmake_ninja_depends requires value for --tdi=");
2326 if (arg_pp.empty()) {
2327 cmSystemTools::Error("-E cmake_ninja_depends requires value for --pp=");
2330 if (arg_dep.empty()) {
2331 cmSystemTools::Error("-E cmake_ninja_depends requires value for --dep=");
2334 if (arg_obj.empty()) {
2335 cmSystemTools::Error("-E cmake_ninja_depends requires value for --obj=");
2338 if (arg_ddi.empty()) {
2339 cmSystemTools::Error("-E cmake_ninja_depends requires value for --ddi=");
2342 if (arg_lang.empty()) {
2343 cmSystemTools::Error("-E cmake_ninja_depends requires value for --lang=");
2347 cm::optional<cmSourceInfo> info;
2348 if (arg_lang == "Fortran") {
2349 info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_pp);
2351 cmSystemTools::Error(
2352 cmStrCat("-E cmake_ninja_depends does not understand the ", arg_lang,
2358 // The error message is already expected to have been output.
2362 info->ScanDep.PrimaryOutput = arg_obj;
2365 cmGeneratedFileStream depfile(arg_dep);
2366 depfile << cmSystemTools::ConvertToUnixOutputPath(arg_pp) << ":";
2367 for (std::string const& include : info->Includes) {
2368 depfile << " \\\n " << cmSystemTools::ConvertToUnixOutputPath(include);
2373 if (!cmScanDepFormat_P1689_Write(arg_ddi, info->ScanDep)) {
2374 cmSystemTools::Error(
2375 cmStrCat("-E cmake_ninja_depends failed to write ", arg_ddi));
2383 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2384 std::string const& arg_tdi, std::string const& arg_pp)
2386 cm::optional<cmSourceInfo> info;
2387 cmFortranCompiler fc;
2388 std::vector<std::string> includes;
2389 std::string dir_top_bld;
2390 std::string module_dir;
2393 Json::Value const& tdi = tdio;
2395 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2396 Json::Reader reader;
2397 if (!reader.parse(tdif, tdio, false)) {
2398 cmSystemTools::Error(
2399 cmStrCat("-E cmake_ninja_depends failed to parse ", arg_tdi,
2400 reader.getFormattedErrorMessages()));
2405 dir_top_bld = tdi["dir-top-bld"].asString();
2406 if (!dir_top_bld.empty() && !cmHasLiteralSuffix(dir_top_bld, "/")) {
2410 Json::Value const& tdi_include_dirs = tdi["include-dirs"];
2411 if (tdi_include_dirs.isArray()) {
2412 for (auto const& tdi_include_dir : tdi_include_dirs) {
2413 includes.push_back(tdi_include_dir.asString());
2417 Json::Value const& tdi_module_dir = tdi["module-dir"];
2418 module_dir = tdi_module_dir.asString();
2419 if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
2423 Json::Value const& tdi_compiler_id = tdi["compiler-id"];
2424 fc.Id = tdi_compiler_id.asString();
2426 Json::Value const& tdi_submodule_sep = tdi["submodule-sep"];
2427 fc.SModSep = tdi_submodule_sep.asString();
2429 Json::Value const& tdi_submodule_ext = tdi["submodule-ext"];
2430 fc.SModExt = tdi_submodule_ext.asString();
2433 cmFortranSourceInfo finfo;
2434 std::set<std::string> defines;
2435 cmFortranParser parser(fc, includes, defines, finfo);
2436 if (!cmFortranParser_FilePush(&parser, arg_pp.c_str())) {
2437 cmSystemTools::Error(
2438 cmStrCat("-E cmake_ninja_depends failed to open ", arg_pp));
2441 if (cmFortran_yyparse(parser.Scanner) != 0) {
2442 // Failed to parse the file.
2446 info = cmSourceInfo();
2447 for (std::string const& provide : finfo.Provides) {
2448 cmSourceReqInfo src_info;
2449 src_info.LogicalName = provide;
2450 if (!module_dir.empty()) {
2451 std::string mod = cmStrCat(module_dir, provide);
2452 if (!dir_top_bld.empty() && cmHasPrefix(mod, dir_top_bld)) {
2453 mod = mod.substr(dir_top_bld.size());
2455 src_info.CompiledModulePath = std::move(mod);
2457 info->ScanDep.Provides.emplace_back(src_info);
2459 for (std::string const& require : finfo.Requires) {
2460 // Require modules not provided in the same source.
2461 if (finfo.Provides.count(require)) {
2464 cmSourceReqInfo src_info;
2465 src_info.LogicalName = require;
2466 info->ScanDep.Requires.emplace_back(src_info);
2468 for (std::string const& include : finfo.Includes) {
2469 info->Includes.push_back(include);
2475 struct CxxModuleFileSet
2478 std::string RelativeDirectory;
2479 std::string SourcePath;
2481 cmFileSetVisibility Visibility;
2482 cm::optional<std::string> Destination;
2485 struct CxxModuleBmiInstall
2487 std::string Component;
2488 std::string Destination;
2489 bool ExcludeFromAll;
2491 std::string Permissions;
2492 std::string MessageLevel;
2493 std::string ScriptLocation;
2496 struct CxxModuleExport
2499 std::string Destination;
2501 std::string CxxModuleInfoDir;
2502 std::string Namespace;
2506 struct cmGlobalNinjaGenerator::CxxModuleExportInfo
2508 std::map<std::string, CxxModuleFileSet> ObjectToFileSet;
2509 cm::optional<CxxModuleBmiInstall> BmiInstallation;
2510 std::vector<CxxModuleExport> Exports;
2514 bool cmGlobalNinjaGenerator::WriteDyndepFile(
2515 std::string const& dir_top_src, std::string const& dir_top_bld,
2516 std::string const& dir_cur_src, std::string const& dir_cur_bld,
2517 std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
2518 std::string const& module_dir,
2519 std::vector<std::string> const& linked_target_dirs,
2520 std::string const& arg_lang, std::string const& arg_modmapfmt,
2521 CxxModuleExportInfo const& export_info)
2523 // Setup path conversions.
2525 cmStateSnapshot snapshot = this->GetCMakeInstance()->GetCurrentSnapshot();
2526 snapshot.GetDirectory().SetCurrentSource(dir_cur_src);
2527 snapshot.GetDirectory().SetCurrentBinary(dir_cur_bld);
2528 auto mfd = cm::make_unique<cmMakefile>(this, snapshot);
2529 auto lgd = this->CreateLocalGenerator(mfd.get());
2530 lgd->SetRelativePathTop(dir_top_src, dir_top_bld);
2531 this->Makefiles.push_back(std::move(mfd));
2532 this->LocalGenerators.push_back(std::move(lgd));
2535 std::vector<cmScanDepInfo> objects;
2536 for (std::string const& arg_ddi : arg_ddis) {
2538 if (!cmScanDepFormat_P1689_Parse(arg_ddi, &info)) {
2539 cmSystemTools::Error(
2540 cmStrCat("-E cmake_ninja_dyndep failed to parse ddi file ", arg_ddi));
2543 objects.push_back(std::move(info));
2546 CxxModuleUsage usages;
2548 // Map from module name to module file path, if known.
2549 std::map<std::string, std::string> mod_files;
2551 // Populate the module map with those provided by linked targets first.
2552 for (std::string const& linked_target_dir : linked_target_dirs) {
2553 std::string const ltmn =
2554 cmStrCat(linked_target_dir, '/', arg_lang, "Modules.json");
2556 cmsys::ifstream ltmf(ltmn.c_str(), std::ios::in | std::ios::binary);
2557 Json::Reader reader;
2558 if (ltmf && !reader.parse(ltmf, ltm, false)) {
2559 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2561 reader.getFormattedErrorMessages()));
2564 if (ltm.isObject()) {
2565 Json::Value const& target_modules = ltm["modules"];
2566 if (target_modules.isObject()) {
2567 for (auto i = target_modules.begin(); i != target_modules.end(); ++i) {
2568 mod_files[i.key().asString()] = i->asString();
2571 Json::Value const& target_modules_references = ltm["references"];
2572 if (target_modules_references.isObject()) {
2573 for (auto i = target_modules_references.begin();
2574 i != target_modules_references.end(); ++i) {
2575 if (i->isObject()) {
2576 Json::Value const& reference_path = (*i)["path"];
2577 CxxModuleReference module_reference;
2578 if (reference_path.isString()) {
2579 module_reference.Path = reference_path.asString();
2581 Json::Value const& reference_method = (*i)["lookup-method"];
2582 if (reference_method.isString()) {
2583 std::string reference = reference_method.asString();
2584 if (reference == "by-name") {
2585 module_reference.Method = LookupMethod::ByName;
2586 } else if (reference == "include-angle") {
2587 module_reference.Method = LookupMethod::IncludeAngle;
2588 } else if (reference == "include-quote") {
2589 module_reference.Method = LookupMethod::IncludeQuote;
2592 usages.Reference[i.key().asString()] = module_reference;
2596 Json::Value const& target_modules_usage = ltm["usages"];
2597 if (target_modules_usage.isObject()) {
2598 for (auto i = target_modules_usage.begin();
2599 i != target_modules_usage.end(); ++i) {
2601 for (auto j = i->begin(); j != i->end(); ++j) {
2602 usages.Usage[i.key().asString()].insert(j->asString());
2610 cm::optional<CxxModuleMapFormat> modmap_fmt;
2611 if (arg_modmapfmt.empty()) {
2613 } else if (arg_modmapfmt == "gcc") {
2614 modmap_fmt = CxxModuleMapFormat::Gcc;
2615 } else if (arg_modmapfmt == "msvc") {
2616 modmap_fmt = CxxModuleMapFormat::Msvc;
2618 cmSystemTools::Error(
2619 cmStrCat("-E cmake_ninja_dyndep does not understand the ", arg_modmapfmt,
2620 " module map format"));
2624 auto module_ext = CxxModuleMapExtension(modmap_fmt);
2626 // Extend the module map with those provided by this target.
2627 // We do this after loading the modules provided by linked targets
2628 // in case we have one of the same name that must be preferred.
2629 Json::Value target_modules = Json::objectValue;
2630 for (cmScanDepInfo const& object : objects) {
2631 for (auto const& p : object.Provides) {
2633 if (!p.CompiledModulePath.empty()) {
2634 // The scanner provided the path to the module file.
2635 mod = p.CompiledModulePath;
2636 if (!cmSystemTools::FileIsFullPath(mod)) {
2637 // Treat relative to work directory (top of build tree).
2638 mod = cmSystemTools::CollapseFullPath(mod, dir_top_bld);
2641 // Assume the module file path matches the logical module name.
2642 std::string safe_logical_name =
2643 p.LogicalName; // TODO: needs fixing for header units
2644 cmSystemTools::ReplaceString(safe_logical_name, ":", "-");
2645 mod = cmStrCat(module_dir, safe_logical_name, module_ext);
2647 mod_files[p.LogicalName] = mod;
2648 target_modules[p.LogicalName] = mod;
2652 cmGeneratedFileStream ddf(arg_dd);
2653 ddf << "ninja_dyndep_version = 1.0\n";
2656 CxxModuleLocations locs;
2657 locs.RootDirectory = ".";
2658 locs.PathForGenerator = [this](std::string const& path) -> std::string {
2659 return this->ConvertToNinjaPath(path);
2661 locs.BmiLocationForModule =
2662 [&mod_files](std::string const& logical) -> cm::optional<std::string> {
2663 auto m = mod_files.find(logical);
2664 if (m != mod_files.end()) {
2670 // Insert information about the current target's modules.
2672 auto cycle_modules = CxxModuleUsageSeed(locs, objects, usages);
2673 if (!cycle_modules.empty()) {
2674 cmSystemTools::Error(
2675 cmStrCat("Circular dependency detected in the C++ module import "
2676 "graph. See modules named: \"",
2677 cmJoin(cycle_modules, R"(", ")"_s), '"'));
2682 cmNinjaBuild build("dyndep");
2683 build.Outputs.emplace_back("");
2684 for (cmScanDepInfo const& object : objects) {
2685 build.Outputs[0] = this->ConvertToNinjaPath(object.PrimaryOutput);
2686 build.ImplicitOuts.clear();
2687 for (auto const& p : object.Provides) {
2688 build.ImplicitOuts.push_back(
2689 this->ConvertToNinjaPath(mod_files[p.LogicalName]));
2691 build.ImplicitDeps.clear();
2692 for (auto const& r : object.Requires) {
2693 auto mit = mod_files.find(r.LogicalName);
2694 if (mit != mod_files.end()) {
2695 build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second));
2698 build.Variables.clear();
2699 if (!object.Provides.empty()) {
2700 build.Variables.emplace("restat", "1");
2704 auto mm = CxxModuleMapContent(*modmap_fmt, locs, object, usages);
2706 // XXX(modmap): If changing this path construction, change
2707 // `cmNinjaTargetGenerator::WriteObjectBuildStatements` to generate the
2708 // corresponding file path.
2709 cmGeneratedFileStream mmf(cmStrCat(object.PrimaryOutput, ".modmap"));
2713 this->WriteBuild(ddf, build);
2717 Json::Value target_module_info = Json::objectValue;
2718 target_module_info["modules"] = target_modules;
2720 auto& target_usages = target_module_info["usages"] = Json::objectValue;
2721 for (auto const& u : usages.Usage) {
2722 auto& mod_usage = target_usages[u.first] = Json::arrayValue;
2723 for (auto const& v : u.second) {
2724 mod_usage.append(v);
2728 auto name_for_method = [](LookupMethod method) -> cm::static_string_view {
2730 case LookupMethod::ByName:
2732 case LookupMethod::IncludeAngle:
2733 return "include-angle"_s;
2734 case LookupMethod::IncludeQuote:
2735 return "include-quote"_s;
2737 assert(false && "unsupported lookup method");
2741 auto& target_references = target_module_info["references"] =
2743 for (auto const& r : usages.Reference) {
2744 auto& mod_ref = target_references[r.first] = Json::objectValue;
2745 mod_ref["path"] = r.second.Path;
2746 mod_ref["lookup-method"] = std::string(name_for_method(r.second.Method));
2749 // Store the map of modules provided by this target in a file for
2750 // use by dependents that reference this target in linked-target-dirs.
2751 std::string const target_mods_file = cmStrCat(
2752 cmSystemTools::GetFilenamePath(arg_dd), '/', arg_lang, "Modules.json");
2753 cmGeneratedFileStream tmf(target_mods_file);
2754 tmf << target_module_info;
2758 // Fortran doesn't support any of the file-set or BMI installation considered
2760 if (arg_lang != "Fortran"_s) {
2761 // Prepare the export information blocks.
2762 std::string const config_upper =
2763 cmSystemTools::UpperCase(export_info.Config);
2764 std::vector<std::pair<std::unique_ptr<cmGeneratedFileStream>,
2765 CxxModuleExport const*>>
2767 for (auto const& exp : export_info.Exports) {
2768 std::unique_ptr<cmGeneratedFileStream> properties;
2770 std::string const export_dir =
2771 cmStrCat(exp.Prefix, '/', exp.CxxModuleInfoDir, '/');
2772 std::string const property_file_path = cmStrCat(
2773 export_dir, "target-", exp.Name, '-', export_info.Config, ".cmake");
2774 properties = cm::make_unique<cmGeneratedFileStream>(property_file_path);
2776 // Set up the preamble.
2777 *properties << "set_property(TARGET \"" << exp.Namespace << exp.Name
2779 << " PROPERTY IMPORTED_CXX_MODULES_" << config_upper
2782 exports.emplace_back(std::move(properties), &exp);
2785 std::unique_ptr<cmGeneratedFileStream> bmi_install_script;
2786 if (export_info.BmiInstallation) {
2787 bmi_install_script = cm::make_unique<cmGeneratedFileStream>(
2788 export_info.BmiInstallation->ScriptLocation);
2791 auto cmEscape = [](cm::string_view str) {
2792 return cmOutputConverter::EscapeForCMake(
2793 str, cmOutputConverter::WrapQuotes::NoWrap);
2795 auto install_destination =
2796 [&cmEscape](std::string const& dest) -> std::pair<bool, std::string> {
2797 if (cmSystemTools::FileIsFullPath(dest)) {
2798 return std::make_pair(true, cmEscape(dest));
2800 return std::make_pair(false,
2801 cmStrCat("${_IMPORT_PREFIX}/", cmEscape(dest)));
2804 // public/private requirement tracking.
2805 std::set<std::string> private_modules;
2806 std::map<std::string, std::set<std::string>> public_source_requires;
2808 for (cmScanDepInfo const& object : objects) {
2809 // Convert to forward slashes.
2810 auto output_path = object.PrimaryOutput;
2812 cmSystemTools::ConvertToUnixSlashes(output_path);
2814 // Find the fileset for this object.
2815 auto fileset_info_itr = export_info.ObjectToFileSet.find(output_path);
2816 bool const has_provides = !object.Provides.empty();
2817 if (fileset_info_itr == export_info.ObjectToFileSet.end()) {
2818 // If it provides anything, it should have a `CXX_MODULES` or
2819 // `CXX_MODULE_INTERNAL_PARTITIONS` type and be present.
2821 // Take the first module provided to provide context.
2822 auto const& provides = object.Provides[0];
2823 char const* ok_types = "`CXX_MODULES`";
2824 if (provides.LogicalName.find(':') != std::string::npos) {
2825 ok_types = "`CXX_MODULES` (or `CXX_MODULE_INTERNAL_PARTITIONS` if "
2826 "it is not `export`ed)";
2828 cmSystemTools::Error(
2829 cmStrCat("Output ", object.PrimaryOutput, " provides the `",
2830 provides.LogicalName,
2831 "` module but it is not found in a `FILE_SET` of type ",
2836 // This object file does not provide anything, so nothing more needs to
2841 auto const& file_set = fileset_info_itr->second;
2843 // Verify the fileset type for the object.
2844 if (file_set.Type == "CXX_MODULES"_s) {
2845 if (!has_provides) {
2846 cmSystemTools::Error(cmStrCat(
2847 "Output ", object.PrimaryOutput,
2848 " is of type `CXX_MODULES` but does not provide a module"));
2852 } else if (file_set.Type == "CXX_MODULE_INTERNAL_PARTITIONS"_s) {
2853 if (!has_provides) {
2854 cmSystemTools::Error(cmStrCat(
2855 "Source ", file_set.SourcePath,
2856 " is of type `CXX_MODULE_INTERNAL_PARTITIONS` but does not "
2857 "provide a module"));
2861 auto const& provides = object.Provides[0];
2862 if (provides.LogicalName.find(':') == std::string::npos) {
2863 cmSystemTools::Error(cmStrCat(
2864 "Source ", file_set.SourcePath,
2865 " is of type `CXX_MODULE_INTERNAL_PARTITIONS` but does not "
2866 "provide a module partition"));
2870 } else if (file_set.Type == "CXX_MODULE_HEADERS"_s) {
2874 auto const& provides = object.Provides[0];
2875 char const* ok_types = "`CXX_MODULES`";
2876 if (provides.LogicalName.find(':') != std::string::npos) {
2877 ok_types = "`CXX_MODULES` (or `CXX_MODULE_INTERNAL_PARTITIONS` if "
2878 "it is not `export`ed)";
2880 cmSystemTools::Error(cmStrCat(
2881 "Source ", file_set.SourcePath, " provides the `",
2882 provides.LogicalName, "` C++ module but is of type `",
2883 file_set.Type, "` module but must be of type ", ok_types));
2887 // Not a C++ module; ignore.
2891 if (!cmFileSetVisibilityIsForInterface(file_set.Visibility)) {
2892 // Nothing needs to be conveyed about non-`PUBLIC` modules.
2893 for (auto const& p : object.Provides) {
2894 private_modules.insert(p.LogicalName);
2899 // The module is public. Record what it directly requires.
2901 auto& reqs = public_source_requires[file_set.SourcePath];
2902 for (auto const& r : object.Requires) {
2903 reqs.insert(r.LogicalName);
2907 // Write out properties and install rules for any exports.
2908 for (auto const& p : object.Provides) {
2909 bool bmi_dest_is_abs = false;
2910 std::string bmi_destination;
2911 if (export_info.BmiInstallation) {
2913 install_destination(export_info.BmiInstallation->Destination);
2914 bmi_dest_is_abs = dest.first;
2915 bmi_destination = cmStrCat(dest.second, '/');
2918 std::string install_bmi_path;
2919 std::string build_bmi_path;
2920 auto m = mod_files.find(p.LogicalName);
2921 if (m != mod_files.end()) {
2923 cmStrCat(bmi_destination,
2924 cmEscape(cmSystemTools::GetFilenameName(m->second)));
2925 build_bmi_path = cmEscape(m->second);
2928 for (auto const& exp : exports) {
2929 std::string iface_source;
2930 if (exp.second->Install && file_set.Destination) {
2931 auto dest = install_destination(*file_set.Destination);
2932 iface_source = cmStrCat(
2933 dest.second, '/', cmEscape(file_set.RelativeDirectory),
2934 cmEscape(cmSystemTools::GetFilenameName(file_set.SourcePath)));
2936 iface_source = cmEscape(file_set.SourcePath);
2939 std::string bmi_path;
2940 if (exp.second->Install && export_info.BmiInstallation) {
2941 bmi_path = install_bmi_path;
2942 } else if (!exp.second->Install) {
2943 bmi_path = build_bmi_path;
2946 if (iface_source.empty()) {
2947 // No destination for the C++ module source; ignore this property
2952 *exp.first << " \"" << cmEscape(p.LogicalName) << '='
2954 if (!bmi_path.empty()) {
2955 *exp.first << ',' << bmi_path;
2957 *exp.first << "\"\n";
2960 if (bmi_install_script) {
2961 auto const& bmi_install = *export_info.BmiInstallation;
2963 *bmi_install_script << "if (CMAKE_INSTALL_COMPONENT STREQUAL \""
2964 << cmEscape(bmi_install.Component) << '\"';
2965 if (!bmi_install.ExcludeFromAll) {
2966 *bmi_install_script << " OR NOT CMAKE_INSTALL_COMPONENT";
2968 *bmi_install_script << ")\n";
2969 *bmi_install_script << " file(INSTALL\n"
2971 if (!bmi_dest_is_abs) {
2972 *bmi_install_script << "${CMAKE_INSTALL_PREFIX}/";
2974 *bmi_install_script << cmEscape(bmi_install.Destination)
2977 if (bmi_install.Optional) {
2978 *bmi_install_script << " OPTIONAL\n";
2980 if (!bmi_install.MessageLevel.empty()) {
2981 *bmi_install_script << " " << bmi_install.MessageLevel << "\n";
2983 if (!bmi_install.Permissions.empty()) {
2984 *bmi_install_script << " PERMISSIONS" << bmi_install.Permissions
2987 *bmi_install_script << " FILES \"" << m->second << "\")\n";
2988 if (bmi_dest_is_abs) {
2990 << " list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES\n"
2992 << cmEscape(cmSystemTools::GetFilenameName(m->second))
2994 " if (CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION)\n"
2995 " message(WARNING\n"
2996 " \"ABSOLUTE path INSTALL DESTINATION : "
2997 "${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n"
2999 " if (CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION)\n"
3000 " message(FATAL_ERROR\n"
3001 " \"ABSOLUTE path INSTALL DESTINATION forbidden (by "
3002 "caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n"
3005 *bmi_install_script << "endif ()\n";
3010 // Add trailing parenthesis for the `set_property` call.
3011 for (auto const& exp : exports) {
3012 *exp.first << ")\n";
3015 // Check that public sources only require public modules.
3016 for (auto const& pub_reqs : public_source_requires) {
3017 for (auto const& req : pub_reqs.second) {
3018 if (private_modules.count(req)) {
3019 cmSystemTools::Error(cmStrCat(
3020 "Public C++ module source `", pub_reqs.first, "` requires the `",
3021 req, "` C++ module which is provided by a private source"));
3031 int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
3032 std::vector<std::string>::const_iterator argEnd)
3034 std::vector<std::string> arg_full =
3035 cmSystemTools::HandleResponseFile(argBeg, argEnd);
3038 std::string arg_lang;
3039 std::string arg_tdi;
3040 std::string arg_modmapfmt;
3041 std::vector<std::string> arg_ddis;
3042 for (std::string const& arg : arg_full) {
3043 if (cmHasLiteralPrefix(arg, "--tdi=")) {
3044 arg_tdi = arg.substr(6);
3045 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
3046 arg_lang = arg.substr(7);
3047 } else if (cmHasLiteralPrefix(arg, "--dd=")) {
3048 arg_dd = arg.substr(5);
3049 } else if (cmHasLiteralPrefix(arg, "--modmapfmt=")) {
3050 arg_modmapfmt = arg.substr(12);
3051 } else if (!cmHasLiteralPrefix(arg, "--") &&
3052 cmHasLiteralSuffix(arg, ".ddi")) {
3053 arg_ddis.push_back(arg);
3055 cmSystemTools::Error(
3056 cmStrCat("-E cmake_ninja_dyndep unknown argument: ", arg));
3060 if (arg_tdi.empty()) {
3061 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --tdi=");
3064 if (arg_lang.empty()) {
3065 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --lang=");
3068 if (arg_dd.empty()) {
3069 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --dd=");
3074 Json::Value const& tdi = tdio;
3076 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
3077 Json::Reader reader;
3078 if (!reader.parse(tdif, tdio, false)) {
3079 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
3081 reader.getFormattedErrorMessages()));
3086 std::string const dir_cur_bld = tdi["dir-cur-bld"].asString();
3087 std::string const dir_cur_src = tdi["dir-cur-src"].asString();
3088 std::string const dir_top_bld = tdi["dir-top-bld"].asString();
3089 std::string const dir_top_src = tdi["dir-top-src"].asString();
3090 std::string module_dir = tdi["module-dir"].asString();
3091 if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
3094 std::vector<std::string> linked_target_dirs;
3095 Json::Value const& tdi_linked_target_dirs = tdi["linked-target-dirs"];
3096 if (tdi_linked_target_dirs.isArray()) {
3097 for (auto const& tdi_linked_target_dir : tdi_linked_target_dirs) {
3098 linked_target_dirs.push_back(tdi_linked_target_dir.asString());
3102 cmGlobalNinjaGenerator::CxxModuleExportInfo export_info;
3103 export_info.Config = tdi["config"].asString();
3104 if (export_info.Config.empty()) {
3105 export_info.Config = "noconfig";
3107 Json::Value const& tdi_exports = tdi["exports"];
3108 if (tdi_exports.isArray()) {
3109 for (auto const& tdi_export : tdi_exports) {
3110 CxxModuleExport exp;
3111 exp.Install = tdi_export["install"].asBool();
3112 exp.Name = tdi_export["export-name"].asString();
3113 exp.Destination = tdi_export["destination"].asString();
3114 exp.Prefix = tdi_export["export-prefix"].asString();
3115 exp.CxxModuleInfoDir = tdi_export["cxx-module-info-dir"].asString();
3116 exp.Namespace = tdi_export["namespace"].asString();
3118 export_info.Exports.push_back(exp);
3121 auto const& bmi_installation = tdi["bmi-installation"];
3122 if (bmi_installation.isObject()) {
3123 CxxModuleBmiInstall bmi_install;
3125 bmi_install.Component = bmi_installation["component"].asString();
3126 bmi_install.Destination = bmi_installation["destination"].asString();
3127 bmi_install.ExcludeFromAll = bmi_installation["exclude-from-all"].asBool();
3128 bmi_install.Optional = bmi_installation["optional"].asBool();
3129 bmi_install.Permissions = bmi_installation["permissions"].asString();
3130 bmi_install.MessageLevel = bmi_installation["message-level"].asString();
3131 bmi_install.ScriptLocation =
3132 bmi_installation["script-location"].asString();
3134 export_info.BmiInstallation = bmi_install;
3136 Json::Value const& tdi_cxx_modules = tdi["cxx-modules"];
3137 if (tdi_cxx_modules.isObject()) {
3138 for (auto i = tdi_cxx_modules.begin(); i != tdi_cxx_modules.end(); ++i) {
3139 CxxModuleFileSet& fsi = export_info.ObjectToFileSet[i.key().asString()];
3140 auto const& tdi_cxx_module_info = *i;
3141 fsi.Name = tdi_cxx_module_info["name"].asString();
3142 fsi.RelativeDirectory =
3143 tdi_cxx_module_info["relative-directory"].asString();
3144 fsi.SourcePath = tdi_cxx_module_info["source"].asString();
3145 fsi.Type = tdi_cxx_module_info["type"].asString();
3146 fsi.Visibility = cmFileSetVisibilityFromName(
3147 tdi_cxx_module_info["visibility"].asString(), nullptr);
3148 auto const& tdi_fs_dest = tdi_cxx_module_info["destination"];
3149 if (tdi_fs_dest.isString()) {
3150 fsi.Destination = tdi_fs_dest.asString();
3155 cmake cm(cmake::RoleInternal, cmState::Unknown);
3156 cm.SetHomeDirectory(dir_top_src);
3157 cm.SetHomeOutputDirectory(dir_top_bld);
3158 auto ggd = cm.CreateGlobalGenerator("Ninja");
3160 !cm::static_reference_cast<cmGlobalNinjaGenerator>(ggd).WriteDyndepFile(
3161 dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld, arg_dd, arg_ddis,
3162 module_dir, linked_target_dirs, arg_lang, arg_modmapfmt,
3171 bool cmGlobalNinjaGenerator::EnableCrossConfigBuild() const
3173 return !this->CrossConfigs.empty();
3176 void cmGlobalNinjaGenerator::AppendDirectoryForConfig(
3177 const std::string& prefix, const std::string& config,
3178 const std::string& suffix, std::string& dir)
3180 if (!config.empty() && this->IsMultiConfig()) {
3181 dir += cmStrCat(prefix, config, suffix);
3185 std::set<std::string> cmGlobalNinjaGenerator::GetCrossConfigs(
3186 const std::string& fileConfig) const
3188 auto result = this->CrossConfigs;
3189 result.insert(fileConfig);
3193 bool cmGlobalNinjaGenerator::IsSingleConfigUtility(
3194 cmGeneratorTarget const* target) const
3196 return target->GetType() == cmStateEnums::UTILITY &&
3197 !this->PerConfigUtilityTargets.count(target->GetName());
3200 const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE =
3201 "CMakeFiles/common.ninja";
3202 const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja";
3204 cmGlobalNinjaMultiGenerator::cmGlobalNinjaMultiGenerator(cmake* cm)
3205 : cmGlobalNinjaGenerator(cm)
3207 cm->GetState()->SetIsGeneratorMultiConfig(true);
3208 cm->GetState()->SetNinjaMulti(true);
3211 void cmGlobalNinjaMultiGenerator::GetDocumentation(cmDocumentationEntry& entry)
3213 entry.Name = cmGlobalNinjaMultiGenerator::GetActualName();
3214 entry.Brief = "Generates build-<Config>.ninja files.";
3217 std::string cmGlobalNinjaMultiGenerator::ExpandCFGIntDir(
3218 const std::string& str, const std::string& config) const
3220 std::string result = str;
3221 cmSystemTools::ReplaceString(result, this->GetCMakeCFGIntDir(), config);
3225 bool cmGlobalNinjaMultiGenerator::OpenBuildFileStreams()
3227 if (!this->OpenFileStream(this->CommonFileStream,
3228 cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE)) {
3232 if (!this->OpenFileStream(this->DefaultFileStream, NINJA_BUILD_FILE)) {
3235 *this->DefaultFileStream << "# Build using rules for '"
3236 << this->DefaultFileConfig << "'.\n\n"
3238 << GetNinjaImplFilename(this->DefaultFileConfig)
3241 // Write a comment about this file.
3242 *this->CommonFileStream
3243 << "# This file contains build statements common to all "
3244 "configurations.\n\n";
3246 auto const& configs =
3247 this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
3249 configs.begin(), configs.end(), [this](std::string const& config) -> bool {
3251 if (!this->OpenFileStream(this->ImplFileStreams[config],
3252 GetNinjaImplFilename(config))) {
3256 // Write a comment about this file.
3257 *this->ImplFileStreams[config]
3258 << "# This file contains build statements specific to the \"" << config
3259 << "\"\n# configuration.\n\n";
3261 // Open config file.
3262 if (!this->OpenFileStream(this->ConfigFileStreams[config],
3263 GetNinjaConfigFilename(config))) {
3267 // Write a comment about this file.
3268 *this->ConfigFileStreams[config]
3269 << "# This file contains aliases specific to the \"" << config
3270 << "\"\n# configuration.\n\n"
3271 << "include " << GetNinjaImplFilename(config) << "\n\n";
3277 void cmGlobalNinjaMultiGenerator::CloseBuildFileStreams()
3279 if (this->CommonFileStream) {
3280 this->CommonFileStream.reset();
3282 cmSystemTools::Error("Common file stream was not open.");
3285 if (this->DefaultFileStream) {
3286 this->DefaultFileStream.reset();
3287 } // No error if it wasn't open
3289 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
3290 cmMakefile::IncludeEmptyConfig)) {
3291 if (this->ImplFileStreams[config]) {
3292 this->ImplFileStreams[config].reset();
3294 cmSystemTools::Error(
3295 cmStrCat("Impl file stream for \"", config, "\" was not open."));
3297 if (this->ConfigFileStreams[config]) {
3298 this->ConfigFileStreams[config].reset();
3300 cmSystemTools::Error(
3301 cmStrCat("Config file stream for \"", config, "\" was not open."));
3306 void cmGlobalNinjaMultiGenerator::AppendNinjaFileArgument(
3307 GeneratedMakeCommand& command, const std::string& config) const
3309 if (!config.empty()) {
3311 command.Add(GetNinjaConfigFilename(config));
3315 std::string cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(
3316 const std::string& config)
3318 return cmStrCat("CMakeFiles/impl-", config,
3319 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
3322 std::string cmGlobalNinjaMultiGenerator::GetNinjaConfigFilename(
3323 const std::string& config)
3325 return cmStrCat("build-", config,
3326 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
3329 void cmGlobalNinjaMultiGenerator::AddRebuildManifestOutputs(
3330 cmNinjaDeps& outputs) const
3332 for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs(
3333 cmMakefile::IncludeEmptyConfig)) {
3334 outputs.push_back(this->NinjaOutputPath(GetNinjaImplFilename(config)));
3335 outputs.push_back(this->NinjaOutputPath(GetNinjaConfigFilename(config)));
3337 if (!this->DefaultFileConfig.empty()) {
3338 outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
3342 void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs(
3343 std::vector<std::string>& configs) const
3346 this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
3347 configs.insert(configs.end(), cm::cbegin(allConfigs), cm::cend(allConfigs));
3350 bool cmGlobalNinjaMultiGenerator::InspectConfigTypeVariables()
3352 std::vector<std::string> configsVec;
3354 this->Makefiles.front()->GetSafeDefinition("CMAKE_CONFIGURATION_TYPES"),
3356 if (configsVec.empty()) {
3357 configsVec.emplace_back();
3359 std::set<std::string> configs(configsVec.cbegin(), configsVec.cend());
3361 this->DefaultFileConfig =
3362 this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_BUILD_TYPE");
3363 if (this->DefaultFileConfig.empty()) {
3364 this->DefaultFileConfig = configsVec.front();
3366 if (!configs.count(this->DefaultFileConfig)) {
3367 std::ostringstream msg;
3368 msg << "The configuration specified by "
3369 << "CMAKE_DEFAULT_BUILD_TYPE (" << this->DefaultFileConfig
3370 << ") is not present in CMAKE_CONFIGURATION_TYPES";
3371 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3376 std::vector<std::string> crossConfigsVec;
3378 this->Makefiles.front()->GetSafeDefinition("CMAKE_CROSS_CONFIGS"),
3380 auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsVec);
3381 if (!crossConfigs) {
3382 std::ostringstream msg;
3383 msg << "CMAKE_CROSS_CONFIGS is not a subset of "
3384 << "CMAKE_CONFIGURATION_TYPES";
3385 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3389 this->CrossConfigs = *crossConfigs;
3391 auto defaultConfigsString =
3392 this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_CONFIGS");
3393 if (defaultConfigsString.empty()) {
3394 defaultConfigsString = this->DefaultFileConfig;
3396 if (!defaultConfigsString.empty() &&
3397 defaultConfigsString != this->DefaultFileConfig &&
3398 (this->DefaultFileConfig.empty() || this->CrossConfigs.empty())) {
3399 std::ostringstream msg;
3400 msg << "CMAKE_DEFAULT_CONFIGS cannot be used without "
3401 << "CMAKE_DEFAULT_BUILD_TYPE or CMAKE_CROSS_CONFIGS";
3402 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3407 std::vector<std::string> defaultConfigsVec;
3408 cmExpandList(defaultConfigsString, defaultConfigsVec);
3409 if (!this->DefaultFileConfig.empty()) {
3410 auto defaultConfigs =
3411 ListSubsetWithAll(this->GetCrossConfigs(this->DefaultFileConfig),
3412 this->CrossConfigs, defaultConfigsVec);
3413 if (!defaultConfigs) {
3414 std::ostringstream msg;
3415 msg << "CMAKE_DEFAULT_CONFIGS is not a subset of CMAKE_CROSS_CONFIGS";
3416 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
3420 this->DefaultConfigs = *defaultConfigs;
3426 std::string cmGlobalNinjaMultiGenerator::GetDefaultBuildConfig() const
3431 std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTarget(
3432 cmGeneratorTarget const* target, const std::string& config) const
3434 return cmStrCat("cmake_object_order_depends_target_", target->GetName(), '_',
3435 cmSystemTools::UpperCase(config));