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"
11 #include <cm/iterator>
13 #include <cm/optional>
14 #include <cm/string_view>
15 #include <cmext/algorithm>
16 #include <cmext/memory>
18 #include <cm3p/json/reader.h>
19 #include <cm3p/json/value.h>
20 #include <cm3p/json/writer.h>
22 #include "cmsys/FStream.hxx"
24 #include "cmDocumentationEntry.h"
25 #include "cmFortranParser.h"
26 #include "cmGeneratedFileStream.h"
27 #include "cmGeneratorExpressionEvaluationFile.h"
28 #include "cmGeneratorTarget.h"
29 #include "cmGlobalGenerator.h"
30 #include "cmLinkLineComputer.h"
31 #include "cmListFileCache.h"
32 #include "cmLocalGenerator.h"
33 #include "cmLocalNinjaGenerator.h"
34 #include "cmMakefile.h"
35 #include "cmMessageType.h"
36 #include "cmNinjaLinkLineComputer.h"
37 #include "cmOutputConverter.h"
39 #include "cmScanDepFormat.h"
41 #include "cmStateDirectory.h"
42 #include "cmStateSnapshot.h"
43 #include "cmStateTypes.h"
44 #include "cmStringAlgorithms.h"
45 #include "cmSystemTools.h"
47 #include "cmTargetDepend.h"
49 #include "cmVersion.h"
52 const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja";
53 const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE =
54 "CMakeFiles/rules.ninja";
55 const char* cmGlobalNinjaGenerator::INDENT = " ";
57 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd .";
59 std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":";
63 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
64 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
66 return lhs.Target == rhs.Target && lhs.Config == rhs.Config &&
67 lhs.GenexOutput == rhs.GenexOutput;
71 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
72 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
78 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
79 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
81 return lhs.Target < rhs.Target ||
82 (lhs.Target == rhs.Target &&
83 (lhs.Config < rhs.Config ||
84 (lhs.Config == rhs.Config && lhs.GenexOutput < rhs.GenexOutput)));
88 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
89 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
95 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
96 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
102 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& lhs,
103 const cmGlobalNinjaGenerator::ByConfig::TargetDependsClosureKey& rhs)
108 void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
110 for (int i = 0; i < count; ++i) {
111 os << cmGlobalNinjaGenerator::INDENT;
115 void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os)
117 os << "# ======================================"
118 "=======================================\n";
121 void cmGlobalNinjaGenerator::WriteComment(std::ostream& os,
122 const std::string& comment)
124 if (comment.empty()) {
128 std::string::size_type lpos = 0;
129 std::string::size_type rpos;
130 os << "\n#############################################\n";
131 while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
132 os << "# " << comment.substr(lpos, rpos - lpos) << "\n";
135 os << "# " << comment.substr(lpos) << "\n\n";
138 std::unique_ptr<cmLinkLineComputer>
139 cmGlobalNinjaGenerator::CreateLinkLineComputer(
140 cmOutputConverter* outputConverter,
141 cmStateDirectory const& /* stateDir */) const
143 return std::unique_ptr<cmLinkLineComputer>(
144 cm::make_unique<cmNinjaLinkLineComputer>(
146 this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this));
149 std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name)
151 // Ninja rule names must match "[a-zA-Z0-9_.-]+". Use ".xx" to encode
152 // "." and all invalid characters as hexadecimal.
154 for (char i : name) {
155 if (isalnum(i) || i == '_' || i == '-') {
159 snprintf(buf, sizeof(buf), ".%02x", static_cast<unsigned int>(i));
166 std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
168 std::string result = lit;
169 EncodeLiteralInplace(result);
173 void cmGlobalNinjaGenerator::EncodeLiteralInplace(std::string& lit)
175 cmSystemTools::ReplaceString(lit, "$", "$$");
176 cmSystemTools::ReplaceString(lit, "\n", "$\n");
177 if (this->IsMultiConfig()) {
178 cmSystemTools::ReplaceString(lit, cmStrCat('$', this->GetCMakeCFGIntDir()),
179 this->GetCMakeCFGIntDir());
183 std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
185 std::string result = path;
187 if (this->IsGCCOnWindows())
188 std::replace(result.begin(), result.end(), '\\', '/');
190 std::replace(result.begin(), result.end(), '/', '\\');
192 this->EncodeLiteralInplace(result);
193 cmSystemTools::ReplaceString(result, " ", "$ ");
194 cmSystemTools::ReplaceString(result, ":", "$:");
198 void cmGlobalNinjaGenerator::WriteBuild(std::ostream& os,
199 cmNinjaBuild const& build,
201 bool* usedResponseFile)
203 // Make sure there is a rule.
204 if (build.Rule.empty()) {
205 cmSystemTools::Error(cmStrCat(
206 "No rule for WriteBuild! called with comment: ", build.Comment));
210 // Make sure there is at least one output file.
211 if (build.Outputs.empty()) {
212 cmSystemTools::Error(cmStrCat(
213 "No output files for WriteBuild! called with comment: ", build.Comment));
217 cmGlobalNinjaGenerator::WriteComment(os, build.Comment);
219 // Write output files.
220 std::string buildStr("build");
222 // Write explicit outputs
223 for (std::string const& output : build.Outputs) {
224 buildStr = cmStrCat(buildStr, ' ', this->EncodePath(output));
225 if (this->ComputingUnknownDependencies) {
226 this->CombinedBuildOutputs.insert(output);
229 // Write implicit outputs
230 if (!build.ImplicitOuts.empty()) {
231 // Assume Ninja is new enough to support implicit outputs.
232 // Callers should not populate this field otherwise.
233 buildStr = cmStrCat(buildStr, " |");
234 for (std::string const& implicitOut : build.ImplicitOuts) {
235 buildStr = cmStrCat(buildStr, ' ', this->EncodePath(implicitOut));
236 if (this->ComputingUnknownDependencies) {
237 this->CombinedBuildOutputs.insert(implicitOut);
242 // Repeat some outputs, but expressed as absolute paths.
243 // This helps Ninja handle absolute paths found in a depfile.
244 // FIXME: Unfortunately this causes Ninja to stat the file twice.
245 // We could avoid this if Ninja Issue 1251 were fixed.
246 if (!build.WorkDirOuts.empty()) {
247 if (this->SupportsImplicitOuts() && build.ImplicitOuts.empty()) {
248 // Make them implicit outputs if supported by this version of Ninja.
249 buildStr = cmStrCat(buildStr, " |");
251 for (std::string const& workdirOut : build.WorkDirOuts) {
252 buildStr = cmStrCat(buildStr, " ${cmake_ninja_workdir}",
253 this->EncodePath(workdirOut));
258 buildStr = cmStrCat(buildStr, ": ", build.Rule);
261 std::string arguments;
263 // TODO: Better formatting for when there are multiple input/output files.
265 // Write explicit dependencies.
266 for (std::string const& explicitDep : build.ExplicitDeps) {
267 arguments += cmStrCat(' ', this->EncodePath(explicitDep));
270 // Write implicit dependencies.
271 if (!build.ImplicitDeps.empty()) {
273 for (std::string const& implicitDep : build.ImplicitDeps) {
274 arguments += cmStrCat(' ', this->EncodePath(implicitDep));
278 // Write order-only dependencies.
279 if (!build.OrderOnlyDeps.empty()) {
281 for (std::string const& orderOnlyDep : build.OrderOnlyDeps) {
282 arguments += cmStrCat(' ', this->EncodePath(orderOnlyDep));
289 // Write the variables bound to this build statement.
290 std::string assignments;
292 std::ostringstream variable_assignments;
293 for (auto const& variable : build.Variables) {
294 cmGlobalNinjaGenerator::WriteVariable(
295 variable_assignments, variable.first, variable.second, "", 1);
298 // check if a response file rule should be used
299 assignments = variable_assignments.str();
300 bool useResponseFile = false;
301 if (cmdLineLimit < 0 ||
303 (arguments.size() + buildStr.size() + assignments.size() + 1000) >
304 static_cast<size_t>(cmdLineLimit))) {
305 variable_assignments.str(std::string());
306 cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
307 build.RspFile, "", 1);
308 assignments += variable_assignments.str();
309 useResponseFile = true;
311 if (usedResponseFile) {
312 *usedResponseFile = useResponseFile;
316 if (build.Variables.count("dyndep") > 0) {
317 // The ninja 'cleandead' operation does not account for outputs
318 // discovered by 'dyndep' bindings. Avoid removing them.
319 this->DisableCleandead = true;
322 os << buildStr << arguments << assignments << "\n";
325 void cmGlobalNinjaGenerator::AddCustomCommandRule()
327 cmNinjaRule rule("CUSTOM_COMMAND");
328 rule.Command = "$COMMAND";
329 rule.Description = "$DESC";
330 rule.Comment = "Rule for running custom commands.";
334 void cmGlobalNinjaGenerator::CCOutputs::Add(
335 std::vector<std::string> const& paths)
337 for (std::string const& path : paths) {
338 std::string out = this->GG->ConvertToNinjaPath(path);
339 if (!cmSystemTools::FileIsFullPath(out)) {
340 // This output is expressed as a relative path. Repeat it,
341 // but expressed as an absolute path for Ninja Issue 1251.
342 this->WorkDirOuts.emplace_back(out);
343 this->GG->SeenCustomCommandOutput(this->GG->ConvertToNinjaAbsPath(path));
345 this->GG->SeenCustomCommandOutput(out);
346 this->ExplicitOuts.emplace_back(std::move(out));
350 void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
351 std::string const& command, std::string const& description,
352 std::string const& comment, std::string const& depfile,
353 std::string const& job_pool, bool uses_terminal, bool restat,
354 std::string const& config, CCOutputs outputs, cmNinjaDeps explicitDeps,
355 cmNinjaDeps orderOnlyDeps)
357 this->AddCustomCommandRule();
359 if (this->ComputingUnknownDependencies) {
360 // we need to track every dependency that comes in, since we are trying
361 // to find dependencies that are side effects of build commands
362 for (std::string const& dep : explicitDeps) {
363 this->CombinedCustomCommandExplicitDependencies.insert(dep);
368 cmNinjaBuild build("CUSTOM_COMMAND");
369 build.Comment = comment;
370 build.Outputs = std::move(outputs.ExplicitOuts);
371 build.WorkDirOuts = std::move(outputs.WorkDirOuts);
372 build.ExplicitDeps = std::move(explicitDeps);
373 build.OrderOnlyDeps = std::move(orderOnlyDeps);
375 cmNinjaVars& vars = build.Variables;
377 std::string cmd = command; // NOLINT(*)
380 // TODO Shouldn't an empty command be handled by ninja?
383 vars["COMMAND"] = std::move(cmd);
385 vars["DESC"] = this->EncodeLiteral(description);
387 vars["restat"] = "1";
389 if (uses_terminal && this->SupportsDirectConsole()) {
390 vars["pool"] = "console";
391 } else if (!job_pool.empty()) {
392 vars["pool"] = job_pool;
394 if (!depfile.empty()) {
395 vars["depfile"] = depfile;
397 if (config.empty()) {
398 this->WriteBuild(*this->GetCommonFileStream(), build);
400 this->WriteBuild(*this->GetImplFileStream(config), build);
405 void cmGlobalNinjaGenerator::AddMacOSXContentRule()
407 cmNinjaRule rule("COPY_OSX_CONTENT");
408 rule.Command = cmStrCat(this->CMakeCmd(), " -E copy $in $out");
409 rule.Description = "Copying OS X Content $out";
410 rule.Comment = "Rule for copying OS X bundle content file.";
414 void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(std::string input,
416 const std::string& config)
418 this->AddMacOSXContentRule();
420 cmNinjaBuild build("COPY_OSX_CONTENT");
421 build.Outputs.push_back(std::move(output));
422 build.ExplicitDeps.push_back(std::move(input));
423 this->WriteBuild(*this->GetImplFileStream(config), build);
427 void cmGlobalNinjaGenerator::WriteRule(std::ostream& os,
428 cmNinjaRule const& rule)
430 // -- Parameter checks
431 // Make sure the rule has a name.
432 if (rule.Name.empty()) {
433 cmSystemTools::Error(cmStrCat(
434 "No name given for WriteRule! called with comment: ", rule.Comment));
438 // Make sure a command is given.
439 if (rule.Command.empty()) {
440 cmSystemTools::Error(cmStrCat(
441 "No command given for WriteRule! called with comment: ", rule.Comment));
445 // Make sure response file content is given
446 if (!rule.RspFile.empty() && rule.RspContent.empty()) {
447 cmSystemTools::Error(
448 cmStrCat("rspfile but no rspfile_content given for WriteRule! "
449 "called with comment: ",
456 cmGlobalNinjaGenerator::WriteComment(os, rule.Comment);
457 os << "rule " << rule.Name << '\n';
459 // Write rule key/value pairs
460 auto writeKV = [&os](const char* key, std::string const& value) {
461 if (!value.empty()) {
462 cmGlobalNinjaGenerator::Indent(os, 1);
463 os << key << " = " << value << '\n';
467 writeKV("depfile", rule.DepFile);
468 writeKV("deps", rule.DepType);
469 writeKV("command", rule.Command);
470 writeKV("description", rule.Description);
471 if (!rule.RspFile.empty()) {
472 writeKV("rspfile", rule.RspFile);
473 writeKV("rspfile_content", rule.RspContent);
475 writeKV("restat", rule.Restat);
476 if (rule.Generator) {
477 writeKV("generator", "1");
484 void cmGlobalNinjaGenerator::WriteVariable(std::ostream& os,
485 const std::string& name,
486 const std::string& value,
487 const std::string& comment,
490 // Make sure we have a name.
492 cmSystemTools::Error(cmStrCat("No name given for WriteVariable! called "
498 // Do not add a variable if the value is empty.
499 std::string val = cmTrimWhitespace(value);
504 cmGlobalNinjaGenerator::WriteComment(os, comment);
505 cmGlobalNinjaGenerator::Indent(os, indent);
506 os << name << " = " << val << "\n";
509 void cmGlobalNinjaGenerator::WriteInclude(std::ostream& os,
510 const std::string& filename,
511 const std::string& comment)
513 cmGlobalNinjaGenerator::WriteComment(os, comment);
514 os << "include " << filename << "\n";
517 void cmGlobalNinjaGenerator::WriteDefault(std::ostream& os,
518 const cmNinjaDeps& targets,
519 const std::string& comment)
521 cmGlobalNinjaGenerator::WriteComment(os, comment);
523 for (std::string const& target : targets) {
529 cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
530 : cmGlobalCommonGenerator(cm)
533 cm->GetState()->SetWindowsShell(true);
535 this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake";
538 // Virtual public methods.
540 std::unique_ptr<cmLocalGenerator> cmGlobalNinjaGenerator::CreateLocalGenerator(
543 return std::unique_ptr<cmLocalGenerator>(
544 cm::make_unique<cmLocalNinjaGenerator>(this, mf));
547 codecvt::Encoding cmGlobalNinjaGenerator::GetMakefileEncoding() const
549 return this->NinjaExpectedEncoding;
552 void cmGlobalNinjaGenerator::GetDocumentation(cmDocumentationEntry& entry)
554 entry.Name = cmGlobalNinjaGenerator::GetActualName();
555 entry.Brief = "Generates build.ninja files.";
558 // Implemented in all cmGlobaleGenerator sub-classes.
560 // Source/cmLocalGenerator.cxx
562 void cmGlobalNinjaGenerator::Generate()
564 // Check minimum Ninja version.
565 if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
566 RequiredNinjaVersion())) {
567 std::ostringstream msg;
568 msg << "The detected version of Ninja (" << this->NinjaVersion;
569 msg << ") is less than the version of Ninja required by CMake (";
570 msg << cmGlobalNinjaGenerator::RequiredNinjaVersion() << ").";
571 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
575 if (!this->OpenBuildFileStreams()) {
578 if (!this->OpenRulesFileStream()) {
582 for (auto& it : this->Configs) {
583 it.second.TargetDependsClosures.clear();
586 this->InitOutputPathPrefix();
587 this->TargetAll = this->NinjaOutputPath("all");
588 this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
589 this->DisableCleandead = false;
590 this->DiagnosedCxxModuleSupport = false;
592 this->PolicyCMP0058 =
593 this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
594 cmPolicies::CMP0058);
595 this->ComputingUnknownDependencies =
596 (this->PolicyCMP0058 == cmPolicies::OLD ||
597 this->PolicyCMP0058 == cmPolicies::WARN);
599 this->cmGlobalGenerator::Generate();
601 this->WriteAssumedSourceDependencies();
602 this->WriteTargetAliases(*this->GetCommonFileStream());
603 this->WriteFolderTargets(*this->GetCommonFileStream());
604 this->WriteUnknownExplicitDependencies(*this->GetCommonFileStream());
605 this->WriteBuiltinTargets(*this->GetCommonFileStream());
607 if (cmSystemTools::GetErrorOccurredFlag()) {
608 this->RulesFileStream->setstate(std::ios::failbit);
609 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
610 cmMakefile::IncludeEmptyConfig)) {
611 this->GetImplFileStream(config)->setstate(std::ios::failbit);
612 this->GetConfigFileStream(config)->setstate(std::ios::failbit);
614 this->GetCommonFileStream()->setstate(std::ios::failbit);
617 this->CloseCompileCommandsStream();
618 this->CloseRulesFileStream();
619 this->CloseBuildFileStreams();
622 // Older ninja tools will not be able to update metadata on Windows
623 // when we are re-generating inside an existing 'ninja' invocation
624 // because the outer tool has the files open for write.
625 if (this->NinjaSupportsMetadataOnRegeneration ||
626 !this->GetCMakeInstance()->GetRegenerateDuringBuild())
629 this->CleanMetaData();
633 void cmGlobalNinjaGenerator::CleanMetaData()
635 auto run_ninja_tool = [this](std::vector<char const*> const& args) {
636 std::vector<std::string> command;
637 command.push_back(this->NinjaCommand);
638 command.emplace_back("-C");
639 command.emplace_back(this->GetCMakeInstance()->GetHomeOutputDirectory());
640 command.emplace_back("-t");
641 for (auto const& arg : args) {
642 command.emplace_back(arg);
645 if (!cmSystemTools::RunSingleCommand(command, nullptr, &error, nullptr,
647 cmSystemTools::OUTPUT_NONE)) {
648 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
649 cmStrCat("Running\n '",
650 cmJoin(command, "' '"),
654 cmSystemTools::SetFatalErrorOccurred();
658 // Can the tools below expect 'build.ninja' to be loadable?
659 bool const expectBuildManifest =
660 !this->IsMultiConfig() && this->OutputPathPrefix.empty();
662 // Skip some ninja tools if they need 'build.ninja' but it is missing.
663 bool const missingBuildManifest = expectBuildManifest &&
664 this->NinjaSupportsUnconditionalRecompactTool &&
665 !cmSystemTools::FileExists("build.ninja");
667 // The `recompact` tool loads the manifest. As above, we don't have a single
668 // `build.ninja` to load for this in Ninja-Multi. This may be relaxed in the
669 // future pending further investigation into how Ninja works upstream
671 if (this->NinjaSupportsUnconditionalRecompactTool &&
672 !this->GetCMakeInstance()->GetRegenerateDuringBuild() &&
673 expectBuildManifest && !missingBuildManifest) {
674 run_ninja_tool({ "recompact" });
676 if (this->NinjaSupportsRestatTool && this->OutputPathPrefix.empty()) {
677 // XXX(ninja): We only list `build.ninja` entry files here because CMake
678 // *always* rewrites these files on a reconfigure. If CMake ever gets
679 // smarter about this, all CMake-time created/edited files listed as
680 // outputs for the reconfigure build statement will need to be listed here.
682 this->AddRebuildManifestOutputs(outputs);
683 std::vector<const char*> args;
684 args.reserve(outputs.size() + 1);
685 args.push_back("restat");
686 for (auto const& output : outputs) {
687 args.push_back(output.c_str());
689 run_ninja_tool(args);
693 bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
695 if (!this->cmGlobalGenerator::FindMakeProgram(mf)) {
698 if (cmValue ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
699 this->NinjaCommand = *ninjaCommand;
700 std::vector<std::string> command;
701 command.push_back(this->NinjaCommand);
702 command.emplace_back("--version");
705 if (!cmSystemTools::RunSingleCommand(command, &version, &error, nullptr,
707 cmSystemTools::OUTPUT_NONE)) {
708 mf->IssueMessage(MessageType::FATAL_ERROR,
709 cmStrCat("Running\n '", cmJoin(command, "' '"),
713 cmSystemTools::SetFatalErrorOccurred();
716 this->NinjaVersion = cmTrimWhitespace(version);
717 this->CheckNinjaFeatures();
722 void cmGlobalNinjaGenerator::CheckNinjaFeatures()
724 this->NinjaSupportsConsolePool =
725 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
726 RequiredNinjaVersionForConsolePool());
727 this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare(
728 cmSystemTools::OP_LESS, this->NinjaVersion,
729 cmGlobalNinjaGenerator::RequiredNinjaVersionForImplicitOuts());
730 this->NinjaSupportsManifestRestat =
731 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
732 RequiredNinjaVersionForManifestRestat());
733 this->NinjaSupportsMultilineDepfile =
734 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
735 RequiredNinjaVersionForMultilineDepfile());
736 this->NinjaSupportsDyndeps =
737 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
738 RequiredNinjaVersionForDyndeps());
739 if (!this->NinjaSupportsDyndeps) {
740 // The ninja version number is not new enough to have upstream support.
741 // Our ninja branch adds ".dyndep-#" to its version number,
742 // where '#' is a feature-specific version number. Extract it.
743 static std::string const k_DYNDEP_ = ".dyndep-";
744 std::string::size_type pos = this->NinjaVersion.find(k_DYNDEP_);
745 if (pos != std::string::npos) {
746 const char* fv = &this->NinjaVersion[pos + k_DYNDEP_.size()];
747 unsigned long dyndep = 0;
748 cmStrToULong(fv, &dyndep);
750 this->NinjaSupportsDyndeps = true;
754 this->NinjaSupportsUnconditionalRecompactTool =
755 !cmSystemTools::VersionCompare(
756 cmSystemTools::OP_LESS, this->NinjaVersion,
757 RequiredNinjaVersionForUnconditionalRecompactTool());
758 this->NinjaSupportsRestatTool =
759 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
760 RequiredNinjaVersionForRestatTool());
761 this->NinjaSupportsMultipleOutputs =
762 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
763 RequiredNinjaVersionForMultipleOutputs());
764 this->NinjaSupportsMetadataOnRegeneration = !cmSystemTools::VersionCompare(
765 cmSystemTools::OP_LESS, this->NinjaVersion,
766 RequiredNinjaVersionForMetadataOnRegeneration());
768 this->NinjaSupportsCodePage =
769 !cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, this->NinjaVersion,
770 RequiredNinjaVersionForCodePage());
771 if (this->NinjaSupportsCodePage) {
772 this->CheckNinjaCodePage();
774 this->NinjaExpectedEncoding = codecvt::ANSI;
779 void cmGlobalNinjaGenerator::CheckNinjaCodePage()
781 std::vector<std::string> command{ this->NinjaCommand, "-t", "wincodepage" };
785 if (!cmSystemTools::RunSingleCommand(command, &output, &error, &result,
786 nullptr, cmSystemTools::OUTPUT_NONE)) {
787 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
788 cmStrCat("Running\n '",
789 cmJoin(command, "' '"),
793 cmSystemTools::SetFatalErrorOccurred();
794 } else if (result == 0) {
795 std::istringstream outputStream(output);
798 while (cmSystemTools::GetLineFromStream(outputStream, line)) {
799 if (cmHasLiteralPrefix(line, "Build file encoding: ")) {
800 cm::string_view lineView(line);
801 cm::string_view encoding =
802 lineView.substr(cmStrLen("Build file encoding: "));
803 if (encoding == "UTF-8") {
804 // Ninja expects UTF-8. We use that internally. No conversion needed.
805 this->NinjaExpectedEncoding = codecvt::None;
807 this->NinjaExpectedEncoding = codecvt::ANSI;
814 this->GetCMakeInstance()->IssueMessage(
815 MessageType::WARNING,
816 "Could not determine Ninja's code page, defaulting to UTF-8");
817 this->NinjaExpectedEncoding = codecvt::None;
820 this->NinjaExpectedEncoding = codecvt::ANSI;
824 bool cmGlobalNinjaGenerator::CheckLanguages(
825 std::vector<std::string> const& languages, cmMakefile* mf) const
827 if (cm::contains(languages, "Fortran")) {
828 return this->CheckFortran(mf);
830 if (cm::contains(languages, "ISPC")) {
831 return this->CheckISPC(mf);
833 if (cm::contains(languages, "Swift")) {
834 const std::string architectures =
835 mf->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
836 if (architectures.find_first_of(';') != std::string::npos) {
837 mf->IssueMessage(MessageType::FATAL_ERROR,
838 "multiple values for CMAKE_OSX_ARCHITECTURES not "
839 "supported with Swift");
840 cmSystemTools::SetFatalErrorOccurred();
847 bool cmGlobalNinjaGenerator::CheckCxxModuleSupport()
849 bool const diagnose = !this->DiagnosedCxxModuleSupport &&
850 !this->CMakeInstance->GetIsInTryCompile();
852 this->DiagnosedCxxModuleSupport = true;
853 this->GetCMakeInstance()->IssueMessage(
854 MessageType::AUTHOR_WARNING,
855 "C++20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP "
856 "is experimental. It is meant only for compiler developers to try.");
858 if (this->NinjaSupportsDyndeps) {
862 std::ostringstream e;
863 /* clang-format off */
865 "The Ninja generator does not support C++20 modules "
866 "using Ninja version \n"
867 " " << this->NinjaVersion << "\n"
868 "due to lack of required features. "
869 "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
871 /* clang-format on */
872 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str());
873 cmSystemTools::SetFatalErrorOccurred();
878 bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
880 if (this->NinjaSupportsDyndeps) {
884 std::ostringstream e;
885 /* clang-format off */
887 "The Ninja generator does not support Fortran using Ninja version\n"
888 " " << this->NinjaVersion << "\n"
889 "due to lack of required features. "
890 "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
892 /* clang-format on */
893 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
894 cmSystemTools::SetFatalErrorOccurred();
898 bool cmGlobalNinjaGenerator::CheckISPC(cmMakefile* mf) const
900 if (this->NinjaSupportsMultipleOutputs) {
904 std::ostringstream e;
905 /* clang-format off */
907 "The Ninja generator does not support ISPC using Ninja version\n"
908 " " << this->NinjaVersion << "\n"
909 "due to lack of required features. "
910 "Ninja " << RequiredNinjaVersionForMultipleOutputs() <<
911 " or higher is required."
913 /* clang-format on */
914 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
915 cmSystemTools::SetFatalErrorOccurred();
919 void cmGlobalNinjaGenerator::EnableLanguage(
920 std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
922 if (this->IsMultiConfig()) {
923 mf->InitCMAKE_CONFIGURATION_TYPES("Debug;Release;RelWithDebInfo");
926 this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
927 for (std::string const& l : langs) {
931 this->ResolveLanguageCompiler(l, mf, optional);
933 std::string const& compilerId =
934 mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_COMPILER_ID"));
935 std::string const& simulateId =
936 mf->GetSafeDefinition(cmStrCat("CMAKE_", l, "_SIMULATE_ID"));
937 std::string const& compilerFrontendVariant = mf->GetSafeDefinition(
938 cmStrCat("CMAKE_", l, "_COMPILER_FRONTEND_VARIANT"));
939 if ((compilerId == "Clang" && compilerFrontendVariant == "GNU") ||
940 (simulateId != "MSVC" &&
941 (compilerId == "GNU" || compilerId == "QCC" ||
942 cmHasLiteralSuffix(compilerId, "Clang")))) {
943 this->UsingGCCOnWindows = true;
950 // cmGlobalUnixMakefileGenerator3
951 // cmGlobalGhsMultiGenerator
952 // cmGlobalVisualStudio10Generator
953 // cmGlobalVisualStudio7Generator
954 // cmGlobalXCodeGenerator
956 // cmGlobalGenerator::Build()
957 std::vector<cmGlobalGenerator::GeneratedMakeCommand>
958 cmGlobalNinjaGenerator::GenerateBuildCommand(
959 const std::string& makeProgram, const std::string& /*projectName*/,
960 const std::string& /*projectDir*/,
961 std::vector<std::string> const& targetNames, const std::string& config,
962 int jobs, bool verbose, const cmBuildOptions& /*buildOptions*/,
963 std::vector<std::string> const& makeOptions)
965 GeneratedMakeCommand makeCommand;
966 makeCommand.Add(this->SelectMakeProgram(makeProgram));
969 makeCommand.Add("-v");
972 if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) &&
973 (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) {
974 makeCommand.Add("-j", std::to_string(jobs));
977 this->AppendNinjaFileArgument(makeCommand, config);
979 makeCommand.Add(makeOptions.begin(), makeOptions.end());
980 for (const auto& tname : targetNames) {
981 if (!tname.empty()) {
982 makeCommand.Add(tname);
985 return { std::move(makeCommand) };
988 // Non-virtual public methods.
990 void cmGlobalNinjaGenerator::AddRule(cmNinjaRule const& rule)
992 // Do not add the same rule twice.
993 if (!this->Rules.insert(rule.Name).second) {
996 // Store command length
997 this->RuleCmdLength[rule.Name] = static_cast<int>(rule.Command.size());
999 cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, rule);
1002 bool cmGlobalNinjaGenerator::HasRule(const std::string& name)
1004 return (this->Rules.find(name) != this->Rules.end());
1007 // Private virtual overrides
1009 void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
1010 cmGeneratorTarget* gt) const
1012 // Compute full path to object file directory for this target.
1013 std::string dir = cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(),
1014 '/', gt->LocalGenerator->GetTargetDirectory(gt),
1015 '/', this->GetCMakeCFGIntDir(), '/');
1016 gt->ObjectDirectory = dir;
1021 bool cmGlobalNinjaGenerator::OpenBuildFileStreams()
1023 if (!this->OpenFileStream(this->BuildFileStream,
1024 cmGlobalNinjaGenerator::NINJA_BUILD_FILE)) {
1028 // Write a comment about this file.
1029 *this->BuildFileStream
1030 << "# This file contains all the build statements describing the\n"
1031 << "# compilation DAG.\n\n";
1036 bool cmGlobalNinjaGenerator::OpenFileStream(
1037 std::unique_ptr<cmGeneratedFileStream>& stream, const std::string& name)
1039 // Get a stream where to generate things.
1041 // Compute Ninja's build file path.
1043 cmStrCat(this->GetCMakeInstance()->GetHomeOutputDirectory(), '/', name);
1044 stream = cm::make_unique<cmGeneratedFileStream>(
1045 path, false, this->GetMakefileEncoding());
1047 // An error message is generated by the constructor if it cannot
1052 // Write the do not edit header.
1053 this->WriteDisclaimer(*stream);
1059 cm::optional<std::set<std::string>> cmGlobalNinjaGenerator::ListSubsetWithAll(
1060 const std::set<std::string>& all, const std::set<std::string>& defaults,
1061 const std::vector<std::string>& items)
1063 std::set<std::string> result;
1065 for (auto const& item : items) {
1066 if (item == "all") {
1067 if (items.size() == 1) {
1072 } else if (all.count(item)) {
1073 result.insert(item);
1079 return cm::make_optional(result);
1082 void cmGlobalNinjaGenerator::CloseBuildFileStreams()
1084 if (this->BuildFileStream) {
1085 this->BuildFileStream.reset();
1087 cmSystemTools::Error("Build file stream was not open.");
1091 bool cmGlobalNinjaGenerator::OpenRulesFileStream()
1093 if (!this->OpenFileStream(this->RulesFileStream,
1094 cmGlobalNinjaGenerator::NINJA_RULES_FILE)) {
1098 // Write comment about this file.
1099 /* clang-format off */
1100 *this->RulesFileStream
1101 << "# This file contains all the rules used to get the outputs files\n"
1102 << "# built from the input files.\n"
1103 << "# It is included in the main '" << NINJA_BUILD_FILE << "'.\n\n"
1105 /* clang-format on */
1109 void cmGlobalNinjaGenerator::CloseRulesFileStream()
1111 if (this->RulesFileStream) {
1112 this->RulesFileStream.reset();
1114 cmSystemTools::Error("Rules file stream was not open.");
1118 static void EnsureTrailingSlash(std::string& path)
1123 std::string::value_type last = path.back();
1135 std::string const& cmGlobalNinjaGenerator::ConvertToNinjaPath(
1136 const std::string& path) const
1138 auto const f = this->ConvertToNinjaPathCache.find(path);
1139 if (f != this->ConvertToNinjaPathCache.end()) {
1143 std::string convPath =
1144 this->LocalGenerators[0]->MaybeRelativeToTopBinDir(path);
1145 convPath = this->NinjaOutputPath(convPath);
1147 std::replace(convPath.begin(), convPath.end(), '/', '\\');
1149 return this->ConvertToNinjaPathCache.emplace(path, std::move(convPath))
1153 std::string cmGlobalNinjaGenerator::ConvertToNinjaAbsPath(
1154 std::string path) const
1157 std::replace(path.begin(), path.end(), '/', '\\');
1162 void cmGlobalNinjaGenerator::AddAdditionalCleanFile(std::string fileName,
1163 const std::string& config)
1165 this->Configs[config].AdditionalCleanFiles.emplace(std::move(fileName));
1168 void cmGlobalNinjaGenerator::AddCXXCompileCommand(
1169 const std::string& commandLine, const std::string& sourceFile)
1171 // Compute Ninja's build file path.
1172 std::string buildFileDir =
1173 this->GetCMakeInstance()->GetHomeOutputDirectory();
1174 if (!this->CompileCommandsStream) {
1175 std::string buildFilePath =
1176 cmStrCat(buildFileDir, "/compile_commands.json");
1177 if (this->ComputingUnknownDependencies) {
1178 this->CombinedBuildOutputs.insert(
1179 this->NinjaOutputPath("compile_commands.json"));
1182 // Get a stream where to generate things.
1183 this->CompileCommandsStream =
1184 cm::make_unique<cmGeneratedFileStream>(buildFilePath);
1185 *this->CompileCommandsStream << "[\n";
1187 *this->CompileCommandsStream << ",\n";
1190 std::string sourceFileName = sourceFile;
1191 if (!cmSystemTools::FileIsFullPath(sourceFileName)) {
1192 sourceFileName = cmSystemTools::CollapseFullPath(
1193 sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory());
1196 /* clang-format off */
1197 *this->CompileCommandsStream << "{\n"
1198 << R"( "directory": ")"
1199 << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n"
1200 << R"( "command": ")"
1201 << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n"
1203 << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\"\n"
1205 /* clang-format on */
1208 void cmGlobalNinjaGenerator::CloseCompileCommandsStream()
1210 if (this->CompileCommandsStream) {
1211 *this->CompileCommandsStream << "\n]";
1212 this->CompileCommandsStream.reset();
1216 void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os) const
1218 os << "# CMAKE generated file: DO NOT EDIT!\n"
1219 << "# Generated by \"" << this->GetName() << "\""
1220 << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
1221 << cmVersion::GetMinorVersion() << "\n\n";
1224 void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies()
1226 for (auto const& asd : this->AssumedSourceDependencies) {
1227 CCOutputs outputs(this);
1228 outputs.ExplicitOuts.emplace_back(asd.first);
1229 cmNinjaDeps orderOnlyDeps;
1230 std::copy(asd.second.begin(), asd.second.end(),
1231 std::back_inserter(orderOnlyDeps));
1232 this->WriteCustomCommandBuild(
1233 /*command=*/"", /*description=*/"",
1234 "Assume dependencies for generated source file.",
1235 /*depfile*/ "", /*job_pool*/ "",
1236 /*uses_terminal*/ false,
1237 /*restat*/ true, std::string(), outputs, cmNinjaDeps(),
1238 std::move(orderOnlyDeps));
1242 std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
1243 cmGeneratorTarget const* target, const std::string& /*config*/) const
1245 return cmStrCat("cmake_object_order_depends_target_", target->GetName());
1248 void cmGlobalNinjaGenerator::AppendTargetOutputs(
1249 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1250 const std::string& config, cmNinjaTargetDepends depends) const
1252 // for frameworks, we want the real name, not smple name
1253 // frameworks always appear versioned, and the build.ninja
1254 // will always attempt to manage symbolic links instead
1255 // of letting cmOSXBundleGenerator do it.
1256 bool realname = target->IsFrameworkOnApple();
1258 switch (target->GetType()) {
1259 case cmStateEnums::SHARED_LIBRARY:
1260 case cmStateEnums::STATIC_LIBRARY:
1261 case cmStateEnums::MODULE_LIBRARY: {
1262 if (depends == DependOnTargetOrdering) {
1263 outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1268 case cmStateEnums::EXECUTABLE: {
1269 outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
1270 config, cmStateEnums::RuntimeBinaryArtifact, realname)));
1273 case cmStateEnums::OBJECT_LIBRARY: {
1274 if (depends == DependOnTargetOrdering) {
1275 outputs.push_back(this->OrderDependsTargetForTarget(target, config));
1280 case cmStateEnums::GLOBAL_TARGET:
1281 case cmStateEnums::INTERFACE_LIBRARY:
1282 case cmStateEnums::UTILITY: {
1284 cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1286 std::string output = this->ConvertToNinjaPath(path);
1287 if (target->Target->IsPerConfig()) {
1288 output = this->BuildAlias(output, config);
1290 outputs.push_back(output);
1294 case cmStateEnums::UNKNOWN_LIBRARY:
1299 void cmGlobalNinjaGenerator::AppendTargetDepends(
1300 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1301 const std::string& config, const std::string& fileConfig,
1302 cmNinjaTargetDepends depends)
1304 if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
1305 // These depend only on other CMake-provided targets, e.g. "all".
1306 for (BT<std::pair<std::string, bool>> const& util :
1307 target->GetUtilities()) {
1309 cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), '/',
1311 outputs.push_back(this->BuildAlias(this->ConvertToNinjaPath(d), config));
1316 auto computeISPCOuputs = [](cmGlobalNinjaGenerator* gg,
1317 cmGeneratorTarget const* depTarget,
1318 cmNinjaDeps& outputDeps,
1319 const std::string& targetConfig) {
1320 if (depTarget->CanCompileSources()) {
1321 auto headers = depTarget->GetGeneratedISPCHeaders(targetConfig);
1322 if (!headers.empty()) {
1323 std::transform(headers.begin(), headers.end(), headers.begin(),
1324 gg->MapToNinjaPath());
1325 outputDeps.insert(outputDeps.end(), headers.begin(), headers.end());
1327 auto objs = depTarget->GetGeneratedISPCObjects(targetConfig);
1328 if (!objs.empty()) {
1329 std::transform(objs.begin(), objs.end(), objs.begin(),
1330 gg->MapToNinjaPath());
1331 outputDeps.insert(outputDeps.end(), objs.begin(), objs.end());
1336 for (cmTargetDepend const& targetDep :
1337 this->GetTargetDirectDepends(target)) {
1338 if (!targetDep->IsInBuildSystem()) {
1341 if (targetDep.IsCross()) {
1342 this->AppendTargetOutputs(targetDep, outs, fileConfig, depends);
1343 computeISPCOuputs(this, targetDep, outs, fileConfig);
1345 this->AppendTargetOutputs(targetDep, outs, config, depends);
1346 computeISPCOuputs(this, targetDep, outs, config);
1349 std::sort(outs.begin(), outs.end());
1350 cm::append(outputs, outs);
1354 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1355 cmGeneratorTarget const* target, cmNinjaDeps& outputs,
1356 const std::string& config, const std::string& fileConfig, bool genexOutput)
1359 this->AppendTargetDependsClosure(target, outs, config, fileConfig,
1361 cm::append(outputs, outs);
1364 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
1365 cmGeneratorTarget const* target, cmNinjaOuts& outputs,
1366 const std::string& config, const std::string& fileConfig, bool genexOutput,
1370 // try to locate the target in the cache
1371 ByConfig::TargetDependsClosureKey key{
1376 auto find = this->Configs[fileConfig].TargetDependsClosures.lower_bound(key);
1378 if (find == this->Configs[fileConfig].TargetDependsClosures.end() ||
1379 find->first != key) {
1380 // We now calculate the closure outputs by inspecting the dependent
1381 // targets recursively.
1382 // For that we have to distinguish between a local result set that is only
1383 // relevant for filling the cache entries properly isolated and a global
1384 // result set that is relevant for the result of the top level call to
1385 // AppendTargetDependsClosure.
1386 cmNinjaOuts this_outs; // this will be the new cache entry
1388 for (auto const& dep_target : this->GetTargetDirectDepends(target)) {
1389 if (!dep_target->IsInBuildSystem()) {
1393 if (!this->IsSingleConfigUtility(target) &&
1394 !this->IsSingleConfigUtility(dep_target) &&
1395 this->EnableCrossConfigBuild() && !dep_target.IsCross() &&
1400 if (dep_target.IsCross()) {
1401 this->AppendTargetDependsClosure(dep_target, this_outs, fileConfig,
1402 fileConfig, genexOutput, false);
1404 this->AppendTargetDependsClosure(dep_target, this_outs, config,
1405 fileConfig, genexOutput, false);
1408 find = this->Configs[fileConfig].TargetDependsClosures.emplace_hint(
1409 find, key, std::move(this_outs));
1412 // now fill the outputs of the final result from the newly generated cache
1414 outputs.insert(find->second.begin(), find->second.end());
1416 // finally generate the outputs of the target itself, if applicable
1419 this->AppendTargetOutputs(target, outs, config, DependOnTargetArtifact);
1421 outputs.insert(outs.begin(), outs.end());
1424 void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias,
1425 cmGeneratorTarget* target,
1426 const std::string& config)
1428 std::string outputPath = this->NinjaOutputPath(alias);
1429 std::string buildAlias = this->BuildAlias(outputPath, config);
1430 cmNinjaDeps outputs;
1431 if (config != "all") {
1432 this->AppendTargetOutputs(target, outputs, config, DependOnTargetArtifact);
1434 // Mark the target's outputs as ambiguous to ensure that no other target
1435 // uses the output as an alias.
1436 for (std::string const& output : outputs) {
1437 this->TargetAliases[output].GeneratorTarget = nullptr;
1438 this->DefaultTargetAliases[output].GeneratorTarget = nullptr;
1439 for (const std::string& config2 :
1440 this->Makefiles.front()->GetGeneratorConfigs(
1441 cmMakefile::IncludeEmptyConfig)) {
1442 this->Configs[config2].TargetAliases[output].GeneratorTarget = nullptr;
1446 // Insert the alias into the map. If the alias was already present in the
1447 // map and referred to another target, mark it as ambiguous.
1449 ta.GeneratorTarget = target;
1452 auto newAliasGlobal =
1453 this->TargetAliases.insert(std::make_pair(buildAlias, ta));
1454 if (newAliasGlobal.second &&
1455 newAliasGlobal.first->second.GeneratorTarget != target) {
1456 newAliasGlobal.first->second.GeneratorTarget = nullptr;
1459 auto newAliasConfig =
1460 this->Configs[config].TargetAliases.insert(std::make_pair(outputPath, ta));
1461 if (newAliasConfig.second &&
1462 newAliasConfig.first->second.GeneratorTarget != target) {
1463 newAliasConfig.first->second.GeneratorTarget = nullptr;
1465 if (this->DefaultConfigs.count(config)) {
1466 auto newAliasDefaultGlobal =
1467 this->DefaultTargetAliases.insert(std::make_pair(outputPath, ta));
1468 if (newAliasDefaultGlobal.second &&
1469 newAliasDefaultGlobal.first->second.GeneratorTarget != target) {
1470 newAliasDefaultGlobal.first->second.GeneratorTarget = nullptr;
1475 void cmGlobalNinjaGenerator::WriteTargetAliases(std::ostream& os)
1477 cmGlobalNinjaGenerator::WriteDivider(os);
1478 os << "# Target aliases.\n\n";
1480 cmNinjaBuild build("phony");
1481 build.Outputs.emplace_back();
1482 for (auto const& ta : this->TargetAliases) {
1483 // Don't write ambiguous aliases.
1484 if (!ta.second.GeneratorTarget) {
1488 // Don't write alias if there is a already a custom command with
1490 if (this->HasCustomCommandOutput(ta.first)) {
1494 build.Outputs.front() = ta.first;
1495 build.ExplicitDeps.clear();
1496 if (ta.second.Config == "all") {
1497 for (auto const& config : this->CrossConfigs) {
1498 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1499 build.ExplicitDeps, config,
1500 DependOnTargetArtifact);
1503 this->AppendTargetOutputs(ta.second.GeneratorTarget, build.ExplicitDeps,
1504 ta.second.Config, DependOnTargetArtifact);
1506 this->WriteBuild(this->EnableCrossConfigBuild() &&
1507 (ta.second.Config == "all" ||
1508 this->CrossConfigs.count(ta.second.Config))
1510 : *this->GetImplFileStream(ta.second.Config),
1514 if (this->IsMultiConfig()) {
1515 for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs(
1516 cmMakefile::IncludeEmptyConfig)) {
1517 for (auto const& ta : this->Configs[config].TargetAliases) {
1518 // Don't write ambiguous aliases.
1519 if (!ta.second.GeneratorTarget) {
1523 // Don't write alias if there is a already a custom command with
1525 if (this->HasCustomCommandOutput(ta.first)) {
1529 build.Outputs.front() = ta.first;
1530 build.ExplicitDeps.clear();
1531 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1532 build.ExplicitDeps, config,
1533 DependOnTargetArtifact);
1534 this->WriteBuild(*this->GetConfigFileStream(config), build);
1538 if (!this->DefaultConfigs.empty()) {
1539 for (auto const& ta : this->DefaultTargetAliases) {
1540 // Don't write ambiguous aliases.
1541 if (!ta.second.GeneratorTarget) {
1545 // Don't write alias if there is a already a custom command with
1547 if (this->HasCustomCommandOutput(ta.first)) {
1551 build.Outputs.front() = ta.first;
1552 build.ExplicitDeps.clear();
1553 for (auto const& config : this->DefaultConfigs) {
1554 this->AppendTargetOutputs(ta.second.GeneratorTarget,
1555 build.ExplicitDeps, config,
1556 DependOnTargetArtifact);
1558 this->WriteBuild(*this->GetDefaultFileStream(), build);
1564 void cmGlobalNinjaGenerator::WriteFolderTargets(std::ostream& os)
1566 cmGlobalNinjaGenerator::WriteDivider(os);
1567 os << "# Folder targets.\n\n";
1569 std::map<std::string, DirectoryTarget> dirTargets =
1570 this->ComputeDirectoryTargets();
1572 for (auto const& it : dirTargets) {
1573 cmNinjaBuild build("phony");
1574 cmGlobalNinjaGenerator::WriteDivider(os);
1575 std::string const& currentBinaryDir = it.first;
1576 DirectoryTarget const& dt = it.second;
1577 std::vector<std::string> configs =
1578 dt.LG->GetMakefile()->GetGeneratorConfigs(
1579 cmMakefile::IncludeEmptyConfig);
1582 cmNinjaDeps configDeps;
1583 build.Comment = cmStrCat("Folder: ", currentBinaryDir);
1584 build.Outputs.emplace_back();
1585 std::string const buildDirAllTarget =
1586 this->ConvertToNinjaPath(cmStrCat(currentBinaryDir, "/all"));
1587 for (auto const& config : configs) {
1588 build.ExplicitDeps.clear();
1589 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, config);
1590 configDeps.emplace_back(build.Outputs.front());
1591 for (DirectoryTarget::Target const& t : dt.Targets) {
1592 if (!this->IsExcludedFromAllInConfig(t, config)) {
1593 this->AppendTargetOutputs(t.GT, build.ExplicitDeps, config,
1594 DependOnTargetArtifact);
1597 for (DirectoryTarget::Dir const& d : dt.Children) {
1598 if (!d.ExcludeFromAll) {
1599 build.ExplicitDeps.emplace_back(this->BuildAlias(
1600 this->ConvertToNinjaPath(cmStrCat(d.Path, "/all")), config));
1604 this->WriteBuild(this->EnableCrossConfigBuild() &&
1605 this->CrossConfigs.count(config)
1607 : *this->GetImplFileStream(config),
1611 // Add shortcut target
1612 if (this->IsMultiConfig()) {
1613 for (auto const& config : configs) {
1614 build.ExplicitDeps = { this->BuildAlias(buildDirAllTarget, config) };
1615 build.Outputs.front() = buildDirAllTarget;
1616 this->WriteBuild(*this->GetConfigFileStream(config), build);
1619 if (!this->DefaultFileConfig.empty()) {
1620 build.ExplicitDeps.clear();
1621 for (auto const& config : this->DefaultConfigs) {
1622 build.ExplicitDeps.push_back(
1623 this->BuildAlias(buildDirAllTarget, config));
1625 build.Outputs.front() = buildDirAllTarget;
1626 this->WriteBuild(*this->GetDefaultFileStream(), build);
1630 // Add target for all configs
1631 if (this->EnableCrossConfigBuild()) {
1632 build.ExplicitDeps.clear();
1633 for (auto const& config : this->CrossConfigs) {
1634 build.ExplicitDeps.push_back(
1635 this->BuildAlias(buildDirAllTarget, config));
1637 build.Outputs.front() = this->BuildAlias(buildDirAllTarget, "all");
1638 this->WriteBuild(os, build);
1643 void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os)
1645 if (!this->ComputingUnknownDependencies) {
1649 // We need to collect the set of known build outputs.
1650 // Start with those generated by WriteBuild calls.
1651 // No other method needs this so we can take ownership
1652 // of the set locally and throw it out when we are done.
1653 std::set<std::string> knownDependencies;
1654 knownDependencies.swap(this->CombinedBuildOutputs);
1656 // now write out the unknown explicit dependencies.
1658 // union the configured files, evaluations files and the
1659 // CombinedBuildOutputs,
1660 // and then difference with CombinedExplicitDependencies to find the explicit
1661 // dependencies that we have no rule for
1663 cmGlobalNinjaGenerator::WriteDivider(os);
1664 /* clang-format off */
1665 os << "# Unknown Build Time Dependencies.\n"
1666 << "# Tell Ninja that they may appear as side effects of build rules\n"
1667 << "# otherwise ordered by order-only dependencies.\n\n";
1668 /* clang-format on */
1670 // get the list of files that cmake itself has generated as a
1671 // product of configuration.
1673 for (const auto& lg : this->LocalGenerators) {
1674 // get the vector of files created by this makefile and convert them
1675 // to ninja paths, which are all relative in respect to the build directory
1676 for (std::string const& file : lg->GetMakefile()->GetOutputFiles()) {
1677 knownDependencies.insert(this->ConvertToNinjaPath(file));
1679 if (!this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1680 // get list files which are implicit dependencies as well and will be
1681 // phony for rebuild manifest
1682 for (std::string const& j : lg->GetMakefile()->GetListFiles()) {
1683 knownDependencies.insert(this->ConvertToNinjaPath(j));
1686 for (const auto& li : lg->GetMakefile()->GetEvaluationFiles()) {
1687 // get all the files created by generator expressions and convert them
1689 for (std::string const& evaluationFile : li->GetFiles()) {
1690 knownDependencies.insert(this->ConvertToNinjaPath(evaluationFile));
1694 knownDependencies.insert(this->CMakeCacheFile);
1696 for (auto const& ta : this->TargetAliases) {
1697 knownDependencies.insert(this->ConvertToNinjaPath(ta.first));
1700 // remove all source files we know will exist.
1701 for (auto const& i : this->AssumedSourceDependencies) {
1702 knownDependencies.insert(this->ConvertToNinjaPath(i.first));
1705 // now we difference with CombinedCustomCommandExplicitDependencies to find
1706 // the list of items we know nothing about.
1707 // We have encoded all the paths in CombinedCustomCommandExplicitDependencies
1708 // and knownDependencies so no matter if unix or windows paths they
1709 // should all match now.
1711 std::vector<std::string> unknownExplicitDepends;
1712 this->CombinedCustomCommandExplicitDependencies.erase(this->TargetAll);
1714 std::set_difference(this->CombinedCustomCommandExplicitDependencies.begin(),
1715 this->CombinedCustomCommandExplicitDependencies.end(),
1716 knownDependencies.begin(), knownDependencies.end(),
1717 std::back_inserter(unknownExplicitDepends));
1719 std::vector<std::string> warnExplicitDepends;
1720 if (!unknownExplicitDepends.empty()) {
1721 cmake* cmk = this->GetCMakeInstance();
1722 std::string const& buildRoot = cmk->GetHomeOutputDirectory();
1723 bool const inSource = (buildRoot == cmk->GetHomeDirectory());
1724 bool const warn = (!inSource && (this->PolicyCMP0058 == cmPolicies::WARN));
1725 cmNinjaBuild build("phony");
1726 build.Outputs.emplace_back("");
1727 for (std::string const& ued : unknownExplicitDepends) {
1728 // verify the file is in the build directory
1729 std::string const absDepPath =
1730 cmSystemTools::CollapseFullPath(ued, buildRoot);
1731 if (cmSystemTools::IsSubDirectory(absDepPath, buildRoot)) {
1732 // Generate phony build statement
1733 build.Outputs[0] = ued;
1734 this->WriteBuild(os, build);
1735 // Add to warning on demand
1736 if (warn && warnExplicitDepends.size() < 10) {
1737 warnExplicitDepends.push_back(ued);
1743 if (!warnExplicitDepends.empty()) {
1744 std::ostringstream w;
1745 /* clang-format off */
1746 w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0058) << "\n"
1747 "This project specifies custom command DEPENDS on files "
1748 "in the build tree that are not specified as the OUTPUT or "
1749 "BYPRODUCTS of any add_custom_command or add_custom_target:\n"
1750 " " << cmJoin(warnExplicitDepends, "\n ") <<
1752 "For compatibility with versions of CMake that did not have "
1753 "the BYPRODUCTS option, CMake is generating phony rules for "
1754 "such files to convince 'ninja' to build."
1756 "Project authors should add the missing BYPRODUCTS or OUTPUT "
1757 "options to the custom commands that produce these files."
1759 /* clang-format on */
1760 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1765 void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os)
1768 cmGlobalNinjaGenerator::WriteDivider(os);
1769 os << "# Built-in targets\n\n";
1771 this->WriteTargetRebuildManifest(os);
1772 this->WriteTargetClean(os);
1773 this->WriteTargetHelp(os);
1775 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
1776 cmMakefile::IncludeEmptyConfig)) {
1777 this->WriteTargetDefault(*this->GetConfigFileStream(config));
1780 if (!this->DefaultFileConfig.empty()) {
1781 this->WriteTargetDefault(*this->GetDefaultFileStream());
1785 void cmGlobalNinjaGenerator::WriteTargetDefault(std::ostream& os)
1787 if (!this->HasOutputPathPrefix()) {
1789 all.push_back(this->TargetAll);
1790 cmGlobalNinjaGenerator::WriteDefault(os, all,
1791 "Make the all target the default.");
1795 void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os)
1797 if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
1800 const auto& lg = this->LocalGenerators[0];
1803 cmNinjaRule rule("RERUN_CMAKE");
1805 cmStrCat(this->CMakeCmd(), " --regenerate-during-build -S",
1806 lg->ConvertToOutputFormat(lg->GetSourceDirectory(),
1807 cmOutputConverter::SHELL),
1809 lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
1810 cmOutputConverter::SHELL));
1811 rule.Description = "Re-running CMake...";
1812 rule.Comment = "Rule for re-running cmake.";
1813 rule.Generator = true;
1814 WriteRule(*this->RulesFileStream, rule);
1817 cmNinjaBuild reBuild("RERUN_CMAKE");
1818 reBuild.Comment = "Re-run CMake if any of its inputs changed.";
1819 this->AddRebuildManifestOutputs(reBuild.Outputs);
1821 for (const auto& localGen : this->LocalGenerators) {
1822 for (std::string const& fi : localGen->GetMakefile()->GetListFiles()) {
1823 reBuild.ImplicitDeps.push_back(this->ConvertToNinjaPath(fi));
1826 reBuild.ImplicitDeps.push_back(this->CMakeCacheFile);
1828 // Use 'console' pool to get non buffered output of the CMake re-run call
1829 // Available since Ninja 1.5
1830 if (this->SupportsDirectConsole()) {
1831 reBuild.Variables["pool"] = "console";
1834 cmake* cm = this->GetCMakeInstance();
1835 if (this->SupportsManifestRestat() && cm->DoWriteGlobVerifyTarget()) {
1837 cmNinjaRule rule("VERIFY_GLOBS");
1839 cmStrCat(this->CMakeCmd(), " -P ",
1840 lg->ConvertToOutputFormat(cm->GetGlobVerifyScript(),
1841 cmOutputConverter::SHELL));
1842 rule.Description = "Re-checking globbed directories...";
1843 rule.Comment = "Rule for re-checking globbed directories.";
1844 rule.Generator = true;
1845 this->WriteRule(*this->RulesFileStream, rule);
1848 cmNinjaBuild phonyBuild("phony");
1849 phonyBuild.Comment = "Phony target to force glob verification run.";
1850 phonyBuild.Outputs.push_back(
1851 cmStrCat(cm->GetGlobVerifyScript(), "_force"));
1852 this->WriteBuild(os, phonyBuild);
1854 reBuild.Variables["restat"] = "1";
1855 std::string const verifyScriptFile =
1856 this->NinjaOutputPath(cm->GetGlobVerifyScript());
1857 std::string const verifyStampFile =
1858 this->NinjaOutputPath(cm->GetGlobVerifyStamp());
1860 cmNinjaBuild vgBuild("VERIFY_GLOBS");
1862 "Re-run CMake to check if globbed directories changed.";
1863 vgBuild.Outputs.push_back(verifyStampFile);
1864 vgBuild.ImplicitDeps = phonyBuild.Outputs;
1865 vgBuild.Variables = reBuild.Variables;
1866 this->WriteBuild(os, vgBuild);
1868 reBuild.Variables.erase("restat");
1869 reBuild.ImplicitDeps.push_back(verifyScriptFile);
1870 reBuild.ExplicitDeps.push_back(verifyStampFile);
1871 } else if (!this->SupportsManifestRestat() &&
1872 cm->DoWriteGlobVerifyTarget()) {
1873 std::ostringstream msg;
1874 msg << "The detected version of Ninja:\n"
1875 << " " << this->NinjaVersion << "\n"
1876 << "is less than the version of Ninja required by CMake for adding "
1877 "restat dependencies to the build.ninja manifest regeneration "
1880 << cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat()
1882 msg << "Any pre-check scripts, such as those generated for file(GLOB "
1883 "CONFIGURE_DEPENDS), will not be run by Ninja.";
1884 this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
1888 std::sort(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end());
1889 reBuild.ImplicitDeps.erase(
1890 std::unique(reBuild.ImplicitDeps.begin(), reBuild.ImplicitDeps.end()),
1891 reBuild.ImplicitDeps.end());
1893 this->WriteBuild(os, reBuild);
1896 cmNinjaBuild build("phony");
1897 build.Comment = "A missing CMake input file is not an error.";
1898 std::set_difference(std::make_move_iterator(reBuild.ImplicitDeps.begin()),
1899 std::make_move_iterator(reBuild.ImplicitDeps.end()),
1900 this->CustomCommandOutputs.begin(),
1901 this->CustomCommandOutputs.end(),
1902 std::back_inserter(build.Outputs));
1903 this->WriteBuild(os, build);
1907 std::string cmGlobalNinjaGenerator::CMakeCmd() const
1909 const auto& lgen = this->LocalGenerators.at(0);
1910 return lgen->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
1911 cmOutputConverter::SHELL);
1914 std::string cmGlobalNinjaGenerator::NinjaCmd() const
1916 const auto& lgen = this->LocalGenerators[0];
1917 if (lgen != nullptr) {
1918 return lgen->ConvertToOutputFormat(this->NinjaCommand,
1919 cmOutputConverter::SHELL);
1924 bool cmGlobalNinjaGenerator::SupportsDirectConsole() const
1926 return this->NinjaSupportsConsolePool;
1929 bool cmGlobalNinjaGenerator::SupportsImplicitOuts() const
1931 return this->NinjaSupportsImplicitOuts;
1934 bool cmGlobalNinjaGenerator::SupportsManifestRestat() const
1936 return this->NinjaSupportsManifestRestat;
1939 bool cmGlobalNinjaGenerator::SupportsMultilineDepfile() const
1941 return this->NinjaSupportsMultilineDepfile;
1944 bool cmGlobalNinjaGenerator::WriteTargetCleanAdditional(std::ostream& os)
1946 const auto& lgr = this->LocalGenerators.at(0);
1947 std::string cleanScriptRel = "CMakeFiles/clean_additional.cmake";
1948 std::string cleanScriptAbs =
1949 cmStrCat(lgr->GetBinaryDirectory(), '/', cleanScriptRel);
1950 std::vector<std::string> configs =
1951 this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
1953 // Check if there are additional files to clean
1955 for (auto const& config : configs) {
1956 auto const it = this->Configs.find(config);
1957 if (it != this->Configs.end() &&
1958 !it->second.AdditionalCleanFiles.empty()) {
1964 // Remove cmake clean script file if it exists
1965 cmSystemTools::RemoveFile(cleanScriptAbs);
1969 // Write cmake clean script file
1971 cmGeneratedFileStream fout(cleanScriptAbs);
1975 fout << "# Additional clean files\ncmake_minimum_required(VERSION 3.16)\n";
1976 for (auto const& config : configs) {
1977 auto const it = this->Configs.find(config);
1978 if (it != this->Configs.end() &&
1979 !it->second.AdditionalCleanFiles.empty()) {
1980 fout << "\nif(\"${CONFIG}\" STREQUAL \"\" OR \"${CONFIG}\" STREQUAL \""
1981 << config << "\")\n";
1982 fout << " file(REMOVE_RECURSE\n";
1983 for (std::string const& acf : it->second.AdditionalCleanFiles) {
1985 << cmOutputConverter::EscapeForCMake(
1986 this->ConvertToNinjaPath(acf))
1990 fout << "endif()\n";
1994 // Register clean script file
1995 lgr->GetMakefile()->AddCMakeOutputFile(cleanScriptAbs);
1999 cmNinjaRule rule("CLEAN_ADDITIONAL");
2000 rule.Command = cmStrCat(
2001 this->CMakeCmd(), " -DCONFIG=$CONFIG -P ",
2002 lgr->ConvertToOutputFormat(this->NinjaOutputPath(cleanScriptRel),
2003 cmOutputConverter::SHELL));
2004 rule.Description = "Cleaning additional files...";
2005 rule.Comment = "Rule for cleaning additional files.";
2006 WriteRule(*this->RulesFileStream, rule);
2011 cmNinjaBuild build("CLEAN_ADDITIONAL");
2012 build.Comment = "Clean additional files.";
2013 build.Outputs.emplace_back();
2014 for (auto const& config : configs) {
2015 build.Outputs.front() = this->BuildAlias(
2016 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()), config);
2017 build.Variables["CONFIG"] = config;
2018 this->WriteBuild(os, build);
2020 if (this->IsMultiConfig()) {
2021 build.Outputs.front() =
2022 this->NinjaOutputPath(this->GetAdditionalCleanTargetName());
2023 build.Variables["CONFIG"] = "";
2024 this->WriteBuild(os, build);
2031 void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os)
2033 // -- Additional clean target
2034 bool additionalFiles = this->WriteTargetCleanAdditional(os);
2036 // -- Default clean target
2039 cmNinjaRule rule("CLEAN");
2040 rule.Command = cmStrCat(this->NinjaCmd(), " $FILE_ARG -t clean $TARGETS");
2041 rule.Description = "Cleaning all built files...";
2042 rule.Comment = "Rule for cleaning all built files.";
2043 WriteRule(*this->RulesFileStream, rule);
2046 auto const configs = this->Makefiles.front()->GetGeneratorConfigs(
2047 cmMakefile::IncludeEmptyConfig);
2051 cmNinjaBuild build("CLEAN");
2052 build.Comment = "Clean all the built files.";
2053 build.Outputs.emplace_back();
2055 for (auto const& config : configs) {
2056 build.Outputs.front() = this->BuildAlias(
2057 this->NinjaOutputPath(this->GetCleanTargetName()), config);
2058 if (this->IsMultiConfig()) {
2059 build.Variables["TARGETS"] =
2060 cmStrCat(this->BuildAlias(GetByproductsForCleanTargetName(), config),
2061 " ", GetByproductsForCleanTargetName());
2063 build.ExplicitDeps.clear();
2064 if (additionalFiles) {
2065 build.ExplicitDeps.push_back(this->BuildAlias(
2066 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2069 for (auto const& fileConfig : configs) {
2070 if (fileConfig != config && !this->EnableCrossConfigBuild()) {
2073 if (this->IsMultiConfig()) {
2074 build.Variables["FILE_ARG"] = cmStrCat(
2076 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
2078 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2082 if (this->EnableCrossConfigBuild()) {
2083 build.Outputs.front() = this->BuildAlias(
2084 this->NinjaOutputPath(this->GetCleanTargetName()), "all");
2085 build.ExplicitDeps.clear();
2087 if (additionalFiles) {
2088 for (auto const& config : this->CrossConfigs) {
2089 build.ExplicitDeps.push_back(this->BuildAlias(
2090 this->NinjaOutputPath(this->GetAdditionalCleanTargetName()),
2095 std::vector<std::string> byproducts;
2096 for (auto const& config : this->CrossConfigs) {
2097 byproducts.push_back(
2098 this->BuildAlias(GetByproductsForCleanTargetName(), config));
2100 byproducts.emplace_back(GetByproductsForCleanTargetName());
2101 build.Variables["TARGETS"] = cmJoin(byproducts, " ");
2103 for (auto const& fileConfig : configs) {
2104 build.Variables["FILE_ARG"] = cmStrCat(
2106 cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
2107 this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
2112 if (this->IsMultiConfig()) {
2113 cmNinjaBuild build("phony");
2114 build.Outputs.emplace_back(
2115 this->NinjaOutputPath(this->GetCleanTargetName()));
2116 build.ExplicitDeps.emplace_back();
2118 for (auto const& config : configs) {
2119 build.ExplicitDeps.front() = this->BuildAlias(
2120 this->NinjaOutputPath(this->GetCleanTargetName()), config);
2121 this->WriteBuild(*this->GetConfigFileStream(config), build);
2124 if (!this->DefaultConfigs.empty()) {
2125 build.ExplicitDeps.clear();
2126 for (auto const& config : this->DefaultConfigs) {
2127 build.ExplicitDeps.push_back(this->BuildAlias(
2128 this->NinjaOutputPath(this->GetCleanTargetName()), config));
2130 this->WriteBuild(*this->GetDefaultFileStream(), build);
2135 if (this->IsMultiConfig()) {
2136 cmNinjaBuild build("phony");
2137 build.Comment = "Clean byproducts.";
2138 build.Outputs.emplace_back(
2139 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()));
2140 build.ExplicitDeps = this->ByproductsForCleanTarget;
2141 this->WriteBuild(os, build);
2143 for (auto const& config : configs) {
2144 build.Outputs.front() = this->BuildAlias(
2145 this->ConvertToNinjaPath(GetByproductsForCleanTargetName()), config);
2146 build.ExplicitDeps = this->Configs[config].ByproductsForCleanTarget;
2147 this->WriteBuild(os, build);
2152 void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os)
2155 cmNinjaRule rule("HELP");
2156 rule.Command = cmStrCat(this->NinjaCmd(), " -t targets");
2157 rule.Description = "All primary targets available:";
2158 rule.Comment = "Rule for printing all primary targets available.";
2159 WriteRule(*this->RulesFileStream, rule);
2162 cmNinjaBuild build("HELP");
2163 build.Comment = "Print all primary targets available.";
2164 build.Outputs.push_back(this->NinjaOutputPath("help"));
2165 this->WriteBuild(os, build);
2169 void cmGlobalNinjaGenerator::InitOutputPathPrefix()
2171 this->OutputPathPrefix =
2172 this->LocalGenerators[0]->GetMakefile()->GetSafeDefinition(
2173 "CMAKE_NINJA_OUTPUT_PATH_PREFIX");
2174 EnsureTrailingSlash(this->OutputPathPrefix);
2177 std::string cmGlobalNinjaGenerator::NinjaOutputPath(
2178 std::string const& path) const
2180 if (!this->HasOutputPathPrefix() || cmSystemTools::FileIsFullPath(path)) {
2183 return cmStrCat(this->OutputPathPrefix, path);
2186 void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix(
2192 EnsureTrailingSlash(path);
2193 cmStripSuffixIfExists(path, this->OutputPathPrefix);
2196 #if !defined(CMAKE_BOOTSTRAP)
2200 We use the following approach to support Fortran. Each target already
2201 has a <target>.dir/ directory used to hold intermediate files for CMake.
2202 For each target, a FortranDependInfo.json file is generated by CMake with
2203 information about include directories, module directories, and the locations
2204 the per-target directories for target dependencies.
2206 Compilation of source files within a target is split into the following steps:
2208 1. Preprocess all sources, scan preprocessed output for module dependencies.
2209 This step is done with independent build statements for each source,
2210 and can therefore be done in parallel.
2212 rule Fortran_PREPROCESS
2214 command = gfortran -cpp $DEFINES $INCLUDES $FLAGS -E $in -o $out &&
2215 cmake -E cmake_ninja_depends \
2216 --tdi=FortranDependInfo.json --pp=$out --dep=$DEP_FILE \
2217 --obj=$OBJ_FILE --ddi=$DYNDEP_INTERMEDIATE_FILE \
2220 build src.f90-pp.f90 | src.f90.o.ddi: Fortran_PREPROCESS src.f90
2221 OBJ_FILE = src.f90.o
2222 DEP_FILE = src.f90.o.d
2223 DYNDEP_INTERMEDIATE_FILE = src.f90.o.ddi
2225 The ``cmake -E cmake_ninja_depends`` tool reads the preprocessed output
2226 and generates the ninja depfile for preprocessor dependencies. It also
2227 generates a "ddi" file (in a format private to CMake) that lists the
2228 object file that compilation will produce along with the module names
2229 it provides and/or requires. The "ddi" file is an implicit output
2230 because it should not appear in "$out" but is generated by the rule.
2232 2. Consolidate the per-source module dependencies saved in the "ddi"
2233 files from all sources to produce a ninja "dyndep" file, ``Fortran.dd``.
2236 command = cmake -E cmake_ninja_dyndep \
2237 --tdi=FortranDependInfo.json --lang=Fortran --dd=$out $in
2239 build Fortran.dd: Fortran_DYNDEP src1.f90.o.ddi src2.f90.o.ddi
2241 The ``cmake -E cmake_ninja_dyndep`` tool reads the "ddi" files from all
2242 sources in the target and the ``FortranModules.json`` files from targets
2243 on which the target depends. It computes dependency edges on compilations
2244 that require modules to those that provide the modules. This information
2245 is placed in the ``Fortran.dd`` file for ninja to load later. It also
2246 writes the expected location of modules provided by this target into
2247 ``FortranModules.json`` for use by dependent targets.
2249 3. Compile all sources after loading dynamically discovered dependencies
2250 of the compilation build statements from their ``dyndep`` bindings.
2252 rule Fortran_COMPILE
2253 command = gfortran $INCLUDES $FLAGS -c $in -o $out
2255 build src1.f90.o: Fortran_COMPILE src1.f90-pp.f90 || Fortran.dd
2258 The "dyndep" binding tells ninja to load dynamically discovered
2259 dependency information from ``Fortran.dd``. This adds information
2262 build src1.f90.o | mod1.mod: dyndep
2265 This tells ninja that ``mod1.mod`` is an implicit output of compiling
2266 the object file ``src1.f90.o``. The ``restat`` binding tells it that
2267 the timestamp of the output may not always change. Additionally:
2269 build src2.f90.o: dyndep | mod1.mod
2271 This tells ninja that ``mod1.mod`` is a dependency of compiling the
2272 object file ``src2.f90.o``. This ensures that ``src1.f90.o`` and
2273 ``mod1.mod`` will always be up to date before ``src2.f90.o`` is built
2274 (because the latter consumes the module).
2281 cmScanDepInfo ScanDep;
2282 std::vector<std::string> Includes;
2285 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2286 std::string const& arg_tdi, std::string const& arg_pp);
2289 int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
2290 std::vector<std::string>::const_iterator argEnd)
2292 std::string arg_tdi;
2294 std::string arg_dep;
2295 std::string arg_obj;
2296 std::string arg_ddi;
2297 std::string arg_lang;
2298 for (std::string const& arg : cmMakeRange(argBeg, argEnd)) {
2299 if (cmHasLiteralPrefix(arg, "--tdi=")) {
2300 arg_tdi = arg.substr(6);
2301 } else if (cmHasLiteralPrefix(arg, "--pp=")) {
2302 arg_pp = arg.substr(5);
2303 } else if (cmHasLiteralPrefix(arg, "--dep=")) {
2304 arg_dep = arg.substr(6);
2305 } else if (cmHasLiteralPrefix(arg, "--obj=")) {
2306 arg_obj = arg.substr(6);
2307 } else if (cmHasLiteralPrefix(arg, "--ddi=")) {
2308 arg_ddi = arg.substr(6);
2309 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2310 arg_lang = arg.substr(7);
2312 cmSystemTools::Error(
2313 cmStrCat("-E cmake_ninja_depends unknown argument: ", arg));
2317 if (arg_tdi.empty()) {
2318 cmSystemTools::Error("-E cmake_ninja_depends requires value for --tdi=");
2321 if (arg_pp.empty()) {
2322 cmSystemTools::Error("-E cmake_ninja_depends requires value for --pp=");
2325 if (arg_dep.empty()) {
2326 cmSystemTools::Error("-E cmake_ninja_depends requires value for --dep=");
2329 if (arg_obj.empty()) {
2330 cmSystemTools::Error("-E cmake_ninja_depends requires value for --obj=");
2333 if (arg_ddi.empty()) {
2334 cmSystemTools::Error("-E cmake_ninja_depends requires value for --ddi=");
2337 if (arg_lang.empty()) {
2338 cmSystemTools::Error("-E cmake_ninja_depends requires value for --lang=");
2342 cm::optional<cmSourceInfo> info;
2343 if (arg_lang == "Fortran") {
2344 info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_pp);
2346 cmSystemTools::Error(
2347 cmStrCat("-E cmake_ninja_depends does not understand the ", arg_lang,
2353 // The error message is already expected to have been output.
2357 info->ScanDep.PrimaryOutput = arg_obj;
2360 cmGeneratedFileStream depfile(arg_dep);
2361 depfile << cmSystemTools::ConvertToUnixOutputPath(arg_pp) << ":";
2362 for (std::string const& include : info->Includes) {
2363 depfile << " \\\n " << cmSystemTools::ConvertToUnixOutputPath(include);
2368 if (!cmScanDepFormat_P1689_Write(arg_ddi, info->ScanDep)) {
2369 cmSystemTools::Error(
2370 cmStrCat("-E cmake_ninja_depends failed to write ", arg_ddi));
2378 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
2379 std::string const& arg_tdi, std::string const& arg_pp)
2381 cm::optional<cmSourceInfo> info;
2382 cmFortranCompiler fc;
2383 std::vector<std::string> includes;
2384 std::string dir_top_bld;
2385 std::string module_dir;
2388 Json::Value const& tdi = tdio;
2390 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2391 Json::Reader reader;
2392 if (!reader.parse(tdif, tdio, false)) {
2393 cmSystemTools::Error(
2394 cmStrCat("-E cmake_ninja_depends failed to parse ", arg_tdi,
2395 reader.getFormattedErrorMessages()));
2400 dir_top_bld = tdi["dir-top-bld"].asString();
2401 if (!dir_top_bld.empty() && !cmHasLiteralSuffix(dir_top_bld, "/")) {
2405 Json::Value const& tdi_include_dirs = tdi["include-dirs"];
2406 if (tdi_include_dirs.isArray()) {
2407 for (auto const& tdi_include_dir : tdi_include_dirs) {
2408 includes.push_back(tdi_include_dir.asString());
2412 Json::Value const& tdi_module_dir = tdi["module-dir"];
2413 module_dir = tdi_module_dir.asString();
2414 if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
2418 Json::Value const& tdi_compiler_id = tdi["compiler-id"];
2419 fc.Id = tdi_compiler_id.asString();
2421 Json::Value const& tdi_submodule_sep = tdi["submodule-sep"];
2422 fc.SModSep = tdi_submodule_sep.asString();
2424 Json::Value const& tdi_submodule_ext = tdi["submodule-ext"];
2425 fc.SModExt = tdi_submodule_ext.asString();
2428 cmFortranSourceInfo finfo;
2429 std::set<std::string> defines;
2430 cmFortranParser parser(fc, includes, defines, finfo);
2431 if (!cmFortranParser_FilePush(&parser, arg_pp.c_str())) {
2432 cmSystemTools::Error(
2433 cmStrCat("-E cmake_ninja_depends failed to open ", arg_pp));
2436 if (cmFortran_yyparse(parser.Scanner) != 0) {
2437 // Failed to parse the file.
2441 info = cmSourceInfo();
2442 for (std::string const& provide : finfo.Provides) {
2443 cmSourceReqInfo src_info;
2444 src_info.LogicalName = provide;
2445 if (!module_dir.empty()) {
2446 std::string mod = cmStrCat(module_dir, provide);
2447 if (!dir_top_bld.empty() && cmHasPrefix(mod, dir_top_bld)) {
2448 mod = mod.substr(dir_top_bld.size());
2450 src_info.CompiledModulePath = std::move(mod);
2452 info->ScanDep.Provides.emplace_back(src_info);
2454 for (std::string const& require : finfo.Requires) {
2455 // Require modules not provided in the same source.
2456 if (finfo.Provides.count(require)) {
2459 cmSourceReqInfo src_info;
2460 src_info.LogicalName = require;
2461 info->ScanDep.Requires.emplace_back(src_info);
2463 for (std::string const& include : finfo.Includes) {
2464 info->Includes.push_back(include);
2470 bool cmGlobalNinjaGenerator::WriteDyndepFile(
2471 std::string const& dir_top_src, std::string const& dir_top_bld,
2472 std::string const& dir_cur_src, std::string const& dir_cur_bld,
2473 std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
2474 std::string const& module_dir,
2475 std::vector<std::string> const& linked_target_dirs,
2476 std::string const& arg_lang, std::string const& arg_modmapfmt)
2478 // Setup path conversions.
2480 cmStateSnapshot snapshot = this->GetCMakeInstance()->GetCurrentSnapshot();
2481 snapshot.GetDirectory().SetCurrentSource(dir_cur_src);
2482 snapshot.GetDirectory().SetCurrentBinary(dir_cur_bld);
2483 auto mfd = cm::make_unique<cmMakefile>(this, snapshot);
2484 auto lgd = this->CreateLocalGenerator(mfd.get());
2485 lgd->SetRelativePathTop(dir_top_src, dir_top_bld);
2486 this->Makefiles.push_back(std::move(mfd));
2487 this->LocalGenerators.push_back(std::move(lgd));
2490 std::vector<cmScanDepInfo> objects;
2491 for (std::string const& arg_ddi : arg_ddis) {
2493 if (!cmScanDepFormat_P1689_Parse(arg_ddi, &info)) {
2494 cmSystemTools::Error(
2495 cmStrCat("-E cmake_ninja_dyndep failed to parse ddi file ", arg_ddi));
2498 objects.push_back(std::move(info));
2501 // Map from module name to module file path, if known.
2502 std::map<std::string, std::string> mod_files;
2504 // Populate the module map with those provided by linked targets first.
2505 for (std::string const& linked_target_dir : linked_target_dirs) {
2506 std::string const ltmn =
2507 cmStrCat(linked_target_dir, "/", arg_lang, "Modules.json");
2509 cmsys::ifstream ltmf(ltmn.c_str(), std::ios::in | std::ios::binary);
2510 Json::Reader reader;
2511 if (ltmf && !reader.parse(ltmf, ltm, false)) {
2512 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2514 reader.getFormattedErrorMessages()));
2517 if (ltm.isObject()) {
2518 for (Json::Value::iterator i = ltm.begin(); i != ltm.end(); ++i) {
2519 mod_files[i.key().asString()] = i->asString();
2524 const char* module_ext = "";
2525 if (arg_modmapfmt == "gcc") {
2526 module_ext = ".gcm";
2529 // Extend the module map with those provided by this target.
2530 // We do this after loading the modules provided by linked targets
2531 // in case we have one of the same name that must be preferred.
2532 Json::Value tm = Json::objectValue;
2533 for (cmScanDepInfo const& object : objects) {
2534 for (auto const& p : object.Provides) {
2536 if (!p.CompiledModulePath.empty()) {
2537 // The scanner provided the path to the module file.
2538 mod = p.CompiledModulePath;
2539 if (!cmSystemTools::FileIsFullPath(mod)) {
2540 // Treat relative to work directory (top of build tree).
2541 mod = cmSystemTools::CollapseFullPath(mod, dir_top_bld);
2544 // Assume the module file path matches the logical module name.
2545 std::string safe_logical_name = p.LogicalName;
2546 cmSystemTools::ReplaceString(safe_logical_name, ":", "-");
2547 mod = cmStrCat(module_dir, safe_logical_name, module_ext);
2549 mod_files[p.LogicalName] = mod;
2550 tm[p.LogicalName] = mod;
2554 cmGeneratedFileStream ddf(arg_dd);
2555 ddf << "ninja_dyndep_version = 1.0\n";
2558 cmNinjaBuild build("dyndep");
2559 build.Outputs.emplace_back("");
2560 for (cmScanDepInfo const& object : objects) {
2561 build.Outputs[0] = this->ConvertToNinjaPath(object.PrimaryOutput);
2562 build.ImplicitOuts.clear();
2563 for (auto const& p : object.Provides) {
2564 build.ImplicitOuts.push_back(
2565 this->ConvertToNinjaPath(mod_files[p.LogicalName]));
2567 build.ImplicitDeps.clear();
2568 for (auto const& r : object.Requires) {
2569 auto mit = mod_files.find(r.LogicalName);
2570 if (mit != mod_files.end()) {
2571 build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second));
2574 build.Variables.clear();
2575 if (!object.Provides.empty()) {
2576 build.Variables.emplace("restat", "1");
2579 if (arg_modmapfmt.empty()) {
2582 std::stringstream mm;
2583 if (arg_modmapfmt == "gcc") {
2584 // Documented in GCC's documentation. The format is a series of lines
2585 // with a module name and the associated filename separated by
2586 // spaces. The first line may use `$root` as the module name to
2587 // specify a "repository root". That is used to anchor any relative
2588 // paths present in the file (CMake should never generate any).
2590 // Write the root directory to use for module paths.
2593 for (auto const& l : object.Provides) {
2594 auto m = mod_files.find(l.LogicalName);
2595 if (m != mod_files.end()) {
2596 mm << l.LogicalName << " " << this->ConvertToNinjaPath(m->second)
2600 for (auto const& r : object.Requires) {
2601 auto m = mod_files.find(r.LogicalName);
2602 if (m != mod_files.end()) {
2603 mm << r.LogicalName << " " << this->ConvertToNinjaPath(m->second)
2608 cmSystemTools::Error(
2609 cmStrCat("-E cmake_ninja_dyndep does not understand the ",
2610 arg_modmapfmt, " module map format"));
2614 // XXX(modmap): If changing this path construction, change
2615 // `cmNinjaTargetGenerator::WriteObjectBuildStatements` to generate the
2616 // corresponding file path.
2617 cmGeneratedFileStream mmf(cmStrCat(object.PrimaryOutput, ".modmap"));
2621 this->WriteBuild(ddf, build);
2625 // Store the map of modules provided by this target in a file for
2626 // use by dependents that reference this target in linked-target-dirs.
2627 std::string const target_mods_file = cmStrCat(
2628 cmSystemTools::GetFilenamePath(arg_dd), '/', arg_lang, "Modules.json");
2629 cmGeneratedFileStream tmf(target_mods_file);
2635 int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
2636 std::vector<std::string>::const_iterator argEnd)
2638 std::vector<std::string> arg_full =
2639 cmSystemTools::HandleResponseFile(argBeg, argEnd);
2642 std::string arg_lang;
2643 std::string arg_tdi;
2644 std::string arg_modmapfmt;
2645 std::vector<std::string> arg_ddis;
2646 for (std::string const& arg : arg_full) {
2647 if (cmHasLiteralPrefix(arg, "--tdi=")) {
2648 arg_tdi = arg.substr(6);
2649 } else if (cmHasLiteralPrefix(arg, "--lang=")) {
2650 arg_lang = arg.substr(7);
2651 } else if (cmHasLiteralPrefix(arg, "--dd=")) {
2652 arg_dd = arg.substr(5);
2653 } else if (cmHasLiteralPrefix(arg, "--modmapfmt=")) {
2654 arg_modmapfmt = arg.substr(12);
2655 } else if (!cmHasLiteralPrefix(arg, "--") &&
2656 cmHasLiteralSuffix(arg, ".ddi")) {
2657 arg_ddis.push_back(arg);
2659 cmSystemTools::Error(
2660 cmStrCat("-E cmake_ninja_dyndep unknown argument: ", arg));
2664 if (arg_tdi.empty()) {
2665 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --tdi=");
2668 if (arg_lang.empty()) {
2669 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --lang=");
2672 if (arg_dd.empty()) {
2673 cmSystemTools::Error("-E cmake_ninja_dyndep requires value for --dd=");
2678 Json::Value const& tdi = tdio;
2680 cmsys::ifstream tdif(arg_tdi.c_str(), std::ios::in | std::ios::binary);
2681 Json::Reader reader;
2682 if (!reader.parse(tdif, tdio, false)) {
2683 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
2685 reader.getFormattedErrorMessages()));
2690 std::string const dir_cur_bld = tdi["dir-cur-bld"].asString();
2691 std::string const dir_cur_src = tdi["dir-cur-src"].asString();
2692 std::string const dir_top_bld = tdi["dir-top-bld"].asString();
2693 std::string const dir_top_src = tdi["dir-top-src"].asString();
2694 std::string module_dir = tdi["module-dir"].asString();
2695 if (!module_dir.empty() && !cmHasLiteralSuffix(module_dir, "/")) {
2698 std::vector<std::string> linked_target_dirs;
2699 Json::Value const& tdi_linked_target_dirs = tdi["linked-target-dirs"];
2700 if (tdi_linked_target_dirs.isArray()) {
2701 for (auto const& tdi_linked_target_dir : tdi_linked_target_dirs) {
2702 linked_target_dirs.push_back(tdi_linked_target_dir.asString());
2706 cmake cm(cmake::RoleInternal, cmState::Unknown);
2707 cm.SetHomeDirectory(dir_top_src);
2708 cm.SetHomeOutputDirectory(dir_top_bld);
2709 auto ggd = cm.CreateGlobalGenerator("Ninja");
2711 !cm::static_reference_cast<cmGlobalNinjaGenerator>(ggd).WriteDyndepFile(
2712 dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld, arg_dd, arg_ddis,
2713 module_dir, linked_target_dirs, arg_lang, arg_modmapfmt)) {
2721 bool cmGlobalNinjaGenerator::EnableCrossConfigBuild() const
2723 return !this->CrossConfigs.empty();
2726 void cmGlobalNinjaGenerator::AppendDirectoryForConfig(
2727 const std::string& prefix, const std::string& config,
2728 const std::string& suffix, std::string& dir)
2730 if (!config.empty() && this->IsMultiConfig()) {
2731 dir += cmStrCat(prefix, config, suffix);
2735 std::set<std::string> cmGlobalNinjaGenerator::GetCrossConfigs(
2736 const std::string& fileConfig) const
2738 auto result = this->CrossConfigs;
2739 result.insert(fileConfig);
2743 bool cmGlobalNinjaGenerator::IsSingleConfigUtility(
2744 cmGeneratorTarget const* target) const
2746 return target->GetType() == cmStateEnums::UTILITY &&
2747 !this->PerConfigUtilityTargets.count(target->GetName());
2750 const char* cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE =
2751 "CMakeFiles/common.ninja";
2752 const char* cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION = ".ninja";
2754 cmGlobalNinjaMultiGenerator::cmGlobalNinjaMultiGenerator(cmake* cm)
2755 : cmGlobalNinjaGenerator(cm)
2757 cm->GetState()->SetIsGeneratorMultiConfig(true);
2758 cm->GetState()->SetNinjaMulti(true);
2761 void cmGlobalNinjaMultiGenerator::GetDocumentation(cmDocumentationEntry& entry)
2763 entry.Name = cmGlobalNinjaMultiGenerator::GetActualName();
2764 entry.Brief = "Generates build-<Config>.ninja files.";
2767 std::string cmGlobalNinjaMultiGenerator::ExpandCFGIntDir(
2768 const std::string& str, const std::string& config) const
2770 std::string result = str;
2771 cmSystemTools::ReplaceString(result, this->GetCMakeCFGIntDir(), config);
2775 bool cmGlobalNinjaMultiGenerator::OpenBuildFileStreams()
2777 if (!this->OpenFileStream(this->CommonFileStream,
2778 cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE)) {
2782 if (!this->OpenFileStream(this->DefaultFileStream, NINJA_BUILD_FILE)) {
2785 *this->DefaultFileStream << "# Build using rules for '"
2786 << this->DefaultFileConfig << "'.\n\n"
2788 << GetNinjaImplFilename(this->DefaultFileConfig)
2791 // Write a comment about this file.
2792 *this->CommonFileStream
2793 << "# This file contains build statements common to all "
2794 "configurations.\n\n";
2796 auto const& configs =
2797 this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
2799 configs.begin(), configs.end(), [this](std::string const& config) -> bool {
2801 if (!this->OpenFileStream(this->ImplFileStreams[config],
2802 GetNinjaImplFilename(config))) {
2806 // Write a comment about this file.
2807 *this->ImplFileStreams[config]
2808 << "# This file contains build statements specific to the \"" << config
2809 << "\"\n# configuration.\n\n";
2811 // Open config file.
2812 if (!this->OpenFileStream(this->ConfigFileStreams[config],
2813 GetNinjaConfigFilename(config))) {
2817 // Write a comment about this file.
2818 *this->ConfigFileStreams[config]
2819 << "# This file contains aliases specific to the \"" << config
2820 << "\"\n# configuration.\n\n"
2821 << "include " << GetNinjaImplFilename(config) << "\n\n";
2827 void cmGlobalNinjaMultiGenerator::CloseBuildFileStreams()
2829 if (this->CommonFileStream) {
2830 this->CommonFileStream.reset();
2832 cmSystemTools::Error("Common file stream was not open.");
2835 if (this->DefaultFileStream) {
2836 this->DefaultFileStream.reset();
2837 } // No error if it wasn't open
2839 for (auto const& config : this->Makefiles[0]->GetGeneratorConfigs(
2840 cmMakefile::IncludeEmptyConfig)) {
2841 if (this->ImplFileStreams[config]) {
2842 this->ImplFileStreams[config].reset();
2844 cmSystemTools::Error(
2845 cmStrCat("Impl file stream for \"", config, "\" was not open."));
2847 if (this->ConfigFileStreams[config]) {
2848 this->ConfigFileStreams[config].reset();
2850 cmSystemTools::Error(
2851 cmStrCat("Config file stream for \"", config, "\" was not open."));
2856 void cmGlobalNinjaMultiGenerator::AppendNinjaFileArgument(
2857 GeneratedMakeCommand& command, const std::string& config) const
2859 if (!config.empty()) {
2861 command.Add(GetNinjaConfigFilename(config));
2865 std::string cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(
2866 const std::string& config)
2868 return cmStrCat("CMakeFiles/impl-", config,
2869 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
2872 std::string cmGlobalNinjaMultiGenerator::GetNinjaConfigFilename(
2873 const std::string& config)
2875 return cmStrCat("build-", config,
2876 cmGlobalNinjaMultiGenerator::NINJA_FILE_EXTENSION);
2879 void cmGlobalNinjaMultiGenerator::AddRebuildManifestOutputs(
2880 cmNinjaDeps& outputs) const
2882 for (auto const& config : this->Makefiles.front()->GetGeneratorConfigs(
2883 cmMakefile::IncludeEmptyConfig)) {
2884 outputs.push_back(this->NinjaOutputPath(GetNinjaImplFilename(config)));
2885 outputs.push_back(this->NinjaOutputPath(GetNinjaConfigFilename(config)));
2887 if (!this->DefaultFileConfig.empty()) {
2888 outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
2892 void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs(
2893 std::vector<std::string>& configs) const
2896 this->Makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
2897 configs.insert(configs.end(), cm::cbegin(allConfigs), cm::cend(allConfigs));
2900 bool cmGlobalNinjaMultiGenerator::InspectConfigTypeVariables()
2902 std::vector<std::string> configsVec;
2904 this->Makefiles.front()->GetSafeDefinition("CMAKE_CONFIGURATION_TYPES"),
2906 if (configsVec.empty()) {
2907 configsVec.emplace_back();
2909 std::set<std::string> configs(configsVec.cbegin(), configsVec.cend());
2911 this->DefaultFileConfig =
2912 this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_BUILD_TYPE");
2913 if (this->DefaultFileConfig.empty()) {
2914 this->DefaultFileConfig = configsVec.front();
2916 if (!configs.count(this->DefaultFileConfig)) {
2917 std::ostringstream msg;
2918 msg << "The configuration specified by "
2919 << "CMAKE_DEFAULT_BUILD_TYPE (" << this->DefaultFileConfig
2920 << ") is not present in CMAKE_CONFIGURATION_TYPES";
2921 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2926 std::vector<std::string> crossConfigsVec;
2928 this->Makefiles.front()->GetSafeDefinition("CMAKE_CROSS_CONFIGS"),
2930 auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsVec);
2931 if (!crossConfigs) {
2932 std::ostringstream msg;
2933 msg << "CMAKE_CROSS_CONFIGS is not a subset of "
2934 << "CMAKE_CONFIGURATION_TYPES";
2935 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2939 this->CrossConfigs = *crossConfigs;
2941 auto defaultConfigsString =
2942 this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_CONFIGS");
2943 if (defaultConfigsString.empty()) {
2944 defaultConfigsString = this->DefaultFileConfig;
2946 if (!defaultConfigsString.empty() &&
2947 defaultConfigsString != this->DefaultFileConfig &&
2948 (this->DefaultFileConfig.empty() || this->CrossConfigs.empty())) {
2949 std::ostringstream msg;
2950 msg << "CMAKE_DEFAULT_CONFIGS cannot be used without "
2951 << "CMAKE_DEFAULT_BUILD_TYPE or CMAKE_CROSS_CONFIGS";
2952 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2957 std::vector<std::string> defaultConfigsVec;
2958 cmExpandList(defaultConfigsString, defaultConfigsVec);
2959 if (!this->DefaultFileConfig.empty()) {
2960 auto defaultConfigs =
2961 ListSubsetWithAll(this->GetCrossConfigs(this->DefaultFileConfig),
2962 this->CrossConfigs, defaultConfigsVec);
2963 if (!defaultConfigs) {
2964 std::ostringstream msg;
2965 msg << "CMAKE_DEFAULT_CONFIGS is not a subset of CMAKE_CROSS_CONFIGS";
2966 this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
2970 this->DefaultConfigs = *defaultConfigs;
2976 std::string cmGlobalNinjaMultiGenerator::GetDefaultBuildConfig() const
2981 std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTarget(
2982 cmGeneratorTarget const* target, const std::string& config) const
2984 return cmStrCat("cmake_object_order_depends_target_", target->GetName(), '_',
2985 cmSystemTools::UpperCase(config));