CMake Nightly Date Stamp
[kiteware-cmake.git] / Source / cmLocalNinjaGenerator.cxx
blobbc3da6ecc6d92ce15059530b448d0e3aa0e2a9c3
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 "cmLocalNinjaGenerator.h"
5 #include <algorithm>
6 #include <cassert>
7 #include <cstdio>
8 #include <memory>
9 #include <sstream>
10 #include <utility>
12 #include <cm/unordered_set>
13 #include <cmext/string_view>
15 #include "cmsys/FStream.hxx"
17 #include "cm_codecvt_Encoding.hxx"
19 #include "cmCryptoHash.h"
20 #include "cmCustomCommand.h"
21 #include "cmCustomCommandGenerator.h"
22 #include "cmGeneratedFileStream.h"
23 #include "cmGeneratorExpression.h"
24 #include "cmGeneratorTarget.h"
25 #include "cmGlobalGenerator.h"
26 #include "cmGlobalNinjaGenerator.h"
27 #include "cmList.h"
28 #include "cmLocalGenerator.h"
29 #include "cmMakefile.h"
30 #include "cmMessageType.h"
31 #include "cmNinjaTargetGenerator.h"
32 #include "cmNinjaTypes.h"
33 #include "cmPolicies.h"
34 #include "cmRulePlaceholderExpander.h"
35 #include "cmSourceFile.h"
36 #include "cmState.h"
37 #include "cmStateTypes.h"
38 #include "cmStringAlgorithms.h"
39 #include "cmSystemTools.h"
40 #include "cmTarget.h"
41 #include "cmValue.h"
42 #include "cmake.h"
44 cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg,
45 cmMakefile* mf)
46 : cmLocalCommonGenerator(gg, mf)
50 // Virtual public methods.
52 std::unique_ptr<cmRulePlaceholderExpander>
53 cmLocalNinjaGenerator::CreateRulePlaceholderExpander() const
55 auto ret = this->cmLocalGenerator::CreateRulePlaceholderExpander();
56 ret->SetTargetImpLib("$TARGET_IMPLIB");
57 return std::unique_ptr<cmRulePlaceholderExpander>(std::move(ret));
60 cmLocalNinjaGenerator::~cmLocalNinjaGenerator() = default;
62 void cmLocalNinjaGenerator::Generate()
64 // Compute the path to use when referencing the current output
65 // directory from the top output directory.
66 this->HomeRelativeOutputPath =
67 this->MaybeRelativeToTopBinDir(this->GetCurrentBinaryDirectory());
68 if (this->HomeRelativeOutputPath == ".") {
69 this->HomeRelativeOutputPath.clear();
72 if (this->GetGlobalGenerator()->IsMultiConfig()) {
73 for (auto const& config : this->GetConfigNames()) {
74 this->WriteProcessedMakefile(this->GetImplFileStream(config));
77 this->WriteProcessedMakefile(this->GetCommonFileStream());
78 #ifdef NINJA_GEN_VERBOSE_FILES
79 this->WriteProcessedMakefile(this->GetRulesFileStream());
80 #endif
82 // We do that only once for the top CMakeLists.txt file.
83 if (this->IsRootMakefile()) {
84 this->WriteBuildFileTop();
86 this->WritePools(this->GetRulesFileStream());
88 const std::string& showIncludesPrefix =
89 this->GetMakefile()->GetSafeDefinition("CMAKE_CL_SHOWINCLUDES_PREFIX");
90 if (!showIncludesPrefix.empty()) {
91 cmGlobalNinjaGenerator::WriteComment(this->GetRulesFileStream(),
92 "localized /showIncludes string");
93 this->GetRulesFileStream() << "msvc_deps_prefix = ";
94 // 'cl /showIncludes' encodes output in the console output code page.
95 // It may differ from the encoding used for file paths in 'build.ninja'.
96 // Ninja matches the showIncludes prefix using its raw byte sequence.
97 this->GetRulesFileStream().WriteAltEncoding(
98 showIncludesPrefix, cmGeneratedFileStream::Encoding::ConsoleOutput);
99 this->GetRulesFileStream() << "\n\n";
103 for (const auto& target : this->GetGeneratorTargets()) {
104 if (!target->IsInBuildSystem()) {
105 continue;
107 auto tg = cmNinjaTargetGenerator::New(target.get());
108 if (tg) {
109 if (target->Target->IsPerConfig()) {
110 for (auto const& config : this->GetConfigNames()) {
111 tg->Generate(config);
112 if (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
113 this->GetGlobalGenerator()->IsMultiConfig()) {
114 cmNinjaBuild phonyAlias("phony");
115 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
116 target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact);
117 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
118 target.get(), phonyAlias.ExplicitDeps, config,
119 DependOnTargetArtifact);
120 this->GetGlobalNinjaGenerator()->WriteBuild(
121 *this->GetGlobalNinjaGenerator()->GetConfigFileStream(config),
122 phonyAlias);
125 if (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
126 this->GetGlobalGenerator()->IsMultiConfig()) {
127 if (!this->GetGlobalNinjaGenerator()->GetDefaultConfigs().empty()) {
128 cmNinjaBuild phonyAlias("phony");
129 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
130 target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact);
131 for (auto const& config :
132 this->GetGlobalNinjaGenerator()->GetDefaultConfigs()) {
133 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
134 target.get(), phonyAlias.ExplicitDeps, config,
135 DependOnTargetArtifact);
137 this->GetGlobalNinjaGenerator()->WriteBuild(
138 *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
139 phonyAlias);
141 cmNinjaBuild phonyAlias("phony");
142 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
143 target.get(), phonyAlias.Outputs, "all", DependOnTargetArtifact);
144 for (auto const& config : this->GetConfigNames()) {
145 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
146 target.get(), phonyAlias.ExplicitDeps, config,
147 DependOnTargetArtifact);
149 this->GetGlobalNinjaGenerator()->WriteBuild(
150 *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
151 phonyAlias);
153 } else {
154 tg->Generate("");
159 for (auto const& config : this->GetConfigNames()) {
160 this->WriteCustomCommandBuildStatements(config);
161 this->AdditionalCleanFiles(config);
165 // TODO: Picked up from cmLocalUnixMakefileGenerator3. Refactor it.
166 std::string cmLocalNinjaGenerator::GetTargetDirectory(
167 cmGeneratorTarget const* target) const
169 std::string dir = cmStrCat("CMakeFiles/", target->GetName());
170 #if defined(__VMS)
171 dir += "_dir";
172 #else
173 dir += ".dir";
174 #endif
175 return dir;
178 // Non-virtual public methods.
180 const cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
181 const
183 return static_cast<const cmGlobalNinjaGenerator*>(
184 this->GetGlobalGenerator());
187 cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
189 return static_cast<cmGlobalNinjaGenerator*>(this->GetGlobalGenerator());
192 std::string const& cmLocalNinjaGenerator::GetWorkingDirectory() const
194 return this->GetState()->GetBinaryDirectory();
197 std::string cmLocalNinjaGenerator::MaybeRelativeToWorkDir(
198 std::string const& path) const
200 return this->GetGlobalNinjaGenerator()->NinjaOutputPath(
201 this->MaybeRelativeToTopBinDir(path));
204 std::string cmLocalNinjaGenerator::GetLinkDependencyFile(
205 cmGeneratorTarget* target, std::string const& config) const
207 return cmStrCat(target->GetSupportDirectory(),
208 this->GetGlobalNinjaGenerator()->ConfigDirectory(config),
209 "/link.d");
212 // Virtual protected methods.
214 std::string cmLocalNinjaGenerator::ConvertToIncludeReference(
215 std::string const& path, cmOutputConverter::OutputFormat format)
217 return this->ConvertToOutputFormat(path, format);
220 // Private methods.
222 cmGeneratedFileStream& cmLocalNinjaGenerator::GetImplFileStream(
223 const std::string& config) const
225 return *this->GetGlobalNinjaGenerator()->GetImplFileStream(config);
228 cmGeneratedFileStream& cmLocalNinjaGenerator::GetCommonFileStream() const
230 return *this->GetGlobalNinjaGenerator()->GetCommonFileStream();
233 cmGeneratedFileStream& cmLocalNinjaGenerator::GetRulesFileStream() const
235 return *this->GetGlobalNinjaGenerator()->GetRulesFileStream();
238 const cmake* cmLocalNinjaGenerator::GetCMakeInstance() const
240 return this->GetGlobalGenerator()->GetCMakeInstance();
243 cmake* cmLocalNinjaGenerator::GetCMakeInstance()
245 return this->GetGlobalGenerator()->GetCMakeInstance();
248 void cmLocalNinjaGenerator::WriteBuildFileTop()
250 this->WriteProjectHeader(this->GetCommonFileStream());
252 if (this->GetGlobalGenerator()->IsMultiConfig()) {
253 for (auto const& config : this->GetConfigNames()) {
254 auto& stream = this->GetImplFileStream(config);
255 this->WriteProjectHeader(stream);
256 this->WriteNinjaRequiredVersion(stream);
257 this->WriteNinjaConfigurationVariable(stream, config);
258 this->WriteNinjaFilesInclusionConfig(stream);
260 } else {
261 this->WriteNinjaRequiredVersion(this->GetCommonFileStream());
262 this->WriteNinjaConfigurationVariable(this->GetCommonFileStream(),
263 this->GetConfigNames().front());
265 this->WriteNinjaFilesInclusionCommon(this->GetCommonFileStream());
266 this->WriteNinjaWorkDir(this->GetCommonFileStream());
268 // For the rule file.
269 this->WriteProjectHeader(this->GetRulesFileStream());
272 void cmLocalNinjaGenerator::WriteProjectHeader(std::ostream& os)
274 cmGlobalNinjaGenerator::WriteDivider(os);
275 os << "# Project: " << this->GetProjectName() << '\n'
276 << "# Configurations: " << cmJoin(this->GetConfigNames(), ", ") << '\n';
277 cmGlobalNinjaGenerator::WriteDivider(os);
280 void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os)
282 // Default required version
283 std::string requiredVersion = cmGlobalNinjaGenerator::RequiredNinjaVersion();
285 // Ninja generator uses the 'console' pool if available (>= 1.5)
286 if (this->GetGlobalNinjaGenerator()->SupportsDirectConsole()) {
287 requiredVersion =
288 cmGlobalNinjaGenerator::RequiredNinjaVersionForConsolePool();
291 // The Ninja generator writes rules which require support for restat
292 // when rebuilding build.ninja manifest (>= 1.8)
293 if (this->GetGlobalNinjaGenerator()->SupportsManifestRestat() &&
294 this->GetCMakeInstance()->DoWriteGlobVerifyTarget() &&
295 !this->GetGlobalNinjaGenerator()->GlobalSettingIsOn(
296 "CMAKE_SUPPRESS_REGENERATION")) {
297 requiredVersion =
298 cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat();
301 cmGlobalNinjaGenerator::WriteComment(
302 os, "Minimal version of Ninja required by this file");
303 os << "ninja_required_version = " << requiredVersion << "\n\n";
306 void cmLocalNinjaGenerator::WriteNinjaConfigurationVariable(
307 std::ostream& os, const std::string& config)
309 cmGlobalNinjaGenerator::WriteVariable(
310 os, "CONFIGURATION", config,
311 "Set configuration variable for custom commands.");
314 void cmLocalNinjaGenerator::WritePools(std::ostream& os)
316 cmGlobalNinjaGenerator::WriteDivider(os);
318 cmValue jobpools =
319 this->GetCMakeInstance()->GetState()->GetGlobalProperty("JOB_POOLS");
320 if (!jobpools) {
321 jobpools = this->GetMakefile()->GetDefinition("CMAKE_JOB_POOLS");
323 if (jobpools) {
324 cmGlobalNinjaGenerator::WriteComment(
325 os, "Pools defined by global property JOB_POOLS");
326 cmList pools{ *jobpools };
327 for (std::string const& pool : pools) {
328 const std::string::size_type eq = pool.find('=');
329 unsigned int jobs;
330 if (eq != std::string::npos &&
331 sscanf(pool.c_str() + eq, "=%u", &jobs) == 1) {
332 os << "pool " << pool.substr(0, eq) << "\n depth = " << jobs
333 << "\n\n";
334 } else {
335 cmSystemTools::Error("Invalid pool defined by property 'JOB_POOLS': " +
336 pool);
342 void cmLocalNinjaGenerator::WriteNinjaFilesInclusionConfig(std::ostream& os)
344 cmGlobalNinjaGenerator::WriteDivider(os);
345 os << "# Include auxiliary files.\n\n";
346 cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
347 std::string const ninjaCommonFile =
348 ng->NinjaOutputPath(cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE);
349 std::string const commonFilePath = ng->EncodePath(ninjaCommonFile);
350 cmGlobalNinjaGenerator::WriteInclude(os, commonFilePath,
351 "Include common file.");
352 os << "\n";
355 void cmLocalNinjaGenerator::WriteNinjaFilesInclusionCommon(std::ostream& os)
357 cmGlobalNinjaGenerator::WriteDivider(os);
358 os << "# Include auxiliary files.\n\n";
359 cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
360 std::string const ninjaRulesFile =
361 ng->NinjaOutputPath(cmGlobalNinjaGenerator::NINJA_RULES_FILE);
362 std::string const rulesFilePath = ng->EncodePath(ninjaRulesFile);
363 cmGlobalNinjaGenerator::WriteInclude(os, rulesFilePath,
364 "Include rules file.");
365 os << "\n";
368 void cmLocalNinjaGenerator::WriteNinjaWorkDir(std::ostream& os)
370 cmGlobalNinjaGenerator::WriteDivider(os);
371 cmGlobalNinjaGenerator::WriteComment(
372 os, "Logical path to working directory; prefix for absolute paths.");
373 cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
374 std::string ninja_workdir = this->GetBinaryDirectory();
375 ng->StripNinjaOutputPathPrefixAsSuffix(ninja_workdir); // Also appends '/'.
376 os << "cmake_ninja_workdir = " << ng->EncodePath(ninja_workdir) << "\n";
379 void cmLocalNinjaGenerator::WriteProcessedMakefile(std::ostream& os)
381 cmGlobalNinjaGenerator::WriteDivider(os);
382 os << "# Write statements declared in CMakeLists.txt:\n"
383 << "# " << this->Makefile->GetSafeDefinition("CMAKE_CURRENT_LIST_FILE")
384 << '\n';
385 if (this->IsRootMakefile()) {
386 os << "# Which is the root file.\n";
388 cmGlobalNinjaGenerator::WriteDivider(os);
389 os << '\n';
392 void cmLocalNinjaGenerator::AppendTargetOutputs(cmGeneratorTarget* target,
393 cmNinjaDeps& outputs,
394 const std::string& config)
396 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(target, outputs, config,
397 DependOnTargetArtifact);
400 void cmLocalNinjaGenerator::AppendTargetDepends(cmGeneratorTarget* target,
401 cmNinjaDeps& outputs,
402 const std::string& config,
403 const std::string& fileConfig,
404 cmNinjaTargetDepends depends)
406 this->GetGlobalNinjaGenerator()->AppendTargetDepends(target, outputs, config,
407 fileConfig, depends);
410 void cmLocalNinjaGenerator::AppendCustomCommandDeps(
411 cmCustomCommandGenerator const& ccg, cmNinjaDeps& ninjaDeps,
412 const std::string& config)
414 for (std::string const& i : ccg.GetDepends()) {
415 std::string dep;
416 if (this->GetRealDependency(i, config, dep)) {
417 ninjaDeps.push_back(
418 this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep));
423 std::string cmLocalNinjaGenerator::WriteCommandScript(
424 std::vector<std::string> const& cmdLines, std::string const& outputConfig,
425 std::string const& commandConfig, std::string const& customStep,
426 cmGeneratorTarget const* target) const
428 std::string scriptPath;
429 if (target) {
430 scriptPath = target->GetSupportDirectory();
431 } else {
432 scriptPath = cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles");
434 scriptPath += this->GetGlobalNinjaGenerator()->ConfigDirectory(outputConfig);
435 cmSystemTools::MakeDirectory(scriptPath);
436 scriptPath += '/';
437 scriptPath += customStep;
438 if (this->GlobalGenerator->IsMultiConfig()) {
439 scriptPath += cmStrCat('-', commandConfig);
441 #ifdef _WIN32
442 scriptPath += ".bat";
443 #else
444 scriptPath += ".sh";
445 #endif
447 cmsys::ofstream script(scriptPath.c_str());
449 #ifdef _WIN32
450 script << "@echo off\n";
451 int line = 1;
452 #else
453 script << "set -e\n\n";
454 #endif
456 for (auto const& i : cmdLines) {
457 std::string cmd = i;
458 // The command line was built assuming it would be written to
459 // the build.ninja file, so it uses '$$' for '$'. Remove this
460 // for the raw shell script.
461 cmSystemTools::ReplaceString(cmd, "$$", "$");
462 #ifdef _WIN32
463 script << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)"
464 << '\n';
465 #else
466 script << cmd << '\n';
467 #endif
470 #ifdef _WIN32
471 script << "goto :EOF\n\n"
472 ":ABORT\n"
473 "set ERROR_CODE=%ERRORLEVEL%\n"
474 "echo Batch file failed at line %FAIL_LINE% "
475 "with errorcode %ERRORLEVEL%\n"
476 "exit /b %ERROR_CODE%";
477 #endif
479 return scriptPath;
482 #ifdef _WIN32
483 namespace {
484 bool RuleNeedsCMD(std::string const& cmd)
486 std::vector<std::string> args;
487 cmSystemTools::ParseWindowsCommandLine(cmd.c_str(), args);
488 auto it = std::find_if(args.cbegin(), args.cend(),
489 [](std::string const& arg) -> bool {
490 // FIXME: Detect more windows shell operators.
491 return cmHasLiteralPrefix(arg, ">");
493 return it != args.cend();
496 #endif
498 std::string cmLocalNinjaGenerator::BuildCommandLine(
499 std::vector<std::string> const& cmdLines, std::string const& outputConfig,
500 std::string const& commandConfig, std::string const& customStep,
501 cmGeneratorTarget const* target) const
503 // If we have no commands but we need to build a command anyway, use noop.
504 // This happens when building a POST_BUILD value for link targets that
505 // don't use POST_BUILD.
506 if (cmdLines.empty()) {
507 return cmGlobalNinjaGenerator::SHELL_NOOP;
510 // If this is a custom step then we will have no '$VAR' ninja placeholders.
511 // This means we can deal with long command sequences by writing to a script.
512 // Do this if the command lines are on the scale of the OS limit.
513 if (!customStep.empty()) {
514 size_t cmdLinesTotal = 0;
515 for (std::string const& cmd : cmdLines) {
516 cmdLinesTotal += cmd.length() + 6;
518 if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) {
519 std::string const scriptPath = this->WriteCommandScript(
520 cmdLines, outputConfig, commandConfig, customStep, target);
521 std::string cmd
522 #ifndef _WIN32
523 = "/bin/sh "
524 #endif
526 cmd += this->ConvertToOutputFormat(
527 this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath),
528 cmOutputConverter::SHELL);
530 // Add an unused argument based on script content so that Ninja
531 // knows when the command lines change.
532 cmd += " ";
533 cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
534 cmd += hash.HashFile(scriptPath).substr(0, 16);
535 return cmd;
539 std::ostringstream cmd;
540 #ifdef _WIN32
541 cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
542 bool const needCMD =
543 cmdLines.size() > 1 || (customStep.empty() && RuleNeedsCMD(cmdLines[0]));
544 for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) {
545 if (li != cmdLines.begin()) {
546 cmd << " && ";
547 } else if (needCMD) {
548 cmd << gg->GetComspec() << " /C \"";
550 // Put current cmdLine in brackets if it contains "||" because it has
551 // higher precedence than "&&" in cmd.exe
552 if (li->find("||") != std::string::npos) {
553 cmd << "( " << *li << " )";
554 } else {
555 cmd << *li;
558 if (needCMD) {
559 cmd << "\"";
561 #else
562 for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) {
563 if (li != cmdLines.begin()) {
564 cmd << " && ";
566 cmd << *li;
568 #endif
569 return cmd.str();
572 void cmLocalNinjaGenerator::AppendCustomCommandLines(
573 cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines)
575 auto* gg = this->GetGlobalNinjaGenerator();
577 if (ccg.GetNumberOfCommands() > 0) {
578 std::string wd = ccg.GetWorkingDirectory();
579 if (wd.empty()) {
580 wd = this->GetCurrentBinaryDirectory();
583 std::ostringstream cdCmd;
584 #ifdef _WIN32
585 std::string cdStr = "cd /D ";
586 #else
587 std::string cdStr = "cd ";
588 #endif
589 cdCmd << cdStr
590 << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
591 cmdLines.push_back(cdCmd.str());
594 std::string launcher = this->MakeCustomLauncher(ccg);
596 for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) {
597 std::string c = ccg.GetCommand(i);
598 if (c.empty()) {
599 continue;
601 cmdLines.push_back(launcher +
602 this->ConvertToOutputFormat(
604 gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI
605 : cmOutputConverter::SHELL));
607 std::string& cmd = cmdLines.back();
608 ccg.AppendArguments(i, cmd);
612 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
613 cmCustomCommand const* cc, const std::set<cmGeneratorTarget*>& targets,
614 const std::string& fileConfig)
616 cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
617 if (gg->SeenCustomCommand(cc, fileConfig)) {
618 return;
621 auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig);
622 for (cmCustomCommandGenerator const& ccg : ccgs) {
623 if (ccg.GetOutputs().empty() && ccg.GetByproducts().empty()) {
624 // Generator expressions evaluate to no output for this config.
625 continue;
628 std::unordered_set<std::string> orderOnlyDeps;
630 if (!cc->GetDependsExplicitOnly()) {
631 // A custom command may appear on multiple targets. However, some build
632 // systems exist where the target dependencies on some of the targets are
633 // overspecified, leading to a dependency cycle. If we assume all target
634 // dependencies are a superset of the true target dependencies for this
635 // custom command, we can take the set intersection of all target
636 // dependencies to obtain a correct dependency list.
638 // FIXME: This won't work in certain obscure scenarios involving indirect
639 // dependencies.
640 auto j = targets.begin();
641 assert(j != targets.end());
642 this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
643 *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
644 ++j;
646 for (; j != targets.end(); ++j) {
647 std::unordered_set<std::string> jDeps;
648 this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
649 *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
650 cm::erase_if(orderOnlyDeps, [&jDeps](std::string const& dep) {
651 return jDeps.find(dep) == jDeps.end();
656 const std::vector<std::string>& outputs = ccg.GetOutputs();
657 const std::vector<std::string>& byproducts = ccg.GetByproducts();
659 bool symbolic = false;
660 for (std::string const& output : outputs) {
661 if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
662 if (sf->GetPropertyAsBool("SYMBOLIC")) {
663 symbolic = true;
664 break;
669 cmGlobalNinjaGenerator::CCOutputs ccOutputs(gg);
670 ccOutputs.Add(outputs);
671 ccOutputs.Add(byproducts);
673 std::string mainOutput = ccOutputs.ExplicitOuts[0];
675 cmNinjaDeps ninjaDeps;
676 this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig);
678 std::vector<std::string> cmdLines;
679 this->AppendCustomCommandLines(ccg, cmdLines);
681 cmNinjaDeps sortedOrderOnlyDeps(orderOnlyDeps.begin(),
682 orderOnlyDeps.end());
683 std::sort(sortedOrderOnlyDeps.begin(), sortedOrderOnlyDeps.end());
685 if (cmdLines.empty()) {
686 cmNinjaBuild build("phony");
687 build.Comment = cmStrCat("Phony custom command for ", mainOutput);
688 build.Outputs = std::move(ccOutputs.ExplicitOuts);
689 build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts);
690 build.ExplicitDeps = std::move(ninjaDeps);
691 build.OrderOnlyDeps = std::move(sortedOrderOnlyDeps);
692 gg->WriteBuild(this->GetImplFileStream(fileConfig), build);
693 } else {
694 std::string customStep = cmSystemTools::GetFilenameName(mainOutput);
695 if (this->GlobalGenerator->IsMultiConfig()) {
696 customStep += '-';
697 customStep += fileConfig;
698 customStep += '-';
699 customStep += ccg.GetOutputConfig();
701 // Hash full path to make unique.
702 customStep += '-';
703 cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
704 customStep += hash.HashString(mainOutput).substr(0, 7);
706 std::string depfile = ccg.GetDepfile();
707 if (!depfile.empty()) {
708 switch (cc->GetCMP0116Status()) {
709 case cmPolicies::WARN:
710 if (this->GetCurrentBinaryDirectory() !=
711 this->GetBinaryDirectory() ||
712 this->Makefile->PolicyOptionalWarningEnabled(
713 "CMAKE_POLICY_WARNING_CMP0116")) {
714 this->GetCMakeInstance()->IssueMessage(
715 MessageType::AUTHOR_WARNING,
716 cmPolicies::GetPolicyWarning(cmPolicies::CMP0116),
717 cc->GetBacktrace());
719 CM_FALLTHROUGH;
720 case cmPolicies::OLD:
721 break;
722 case cmPolicies::REQUIRED_IF_USED:
723 case cmPolicies::REQUIRED_ALWAYS:
724 case cmPolicies::NEW:
725 depfile = ccg.GetInternalDepfile();
726 break;
730 std::string comment = cmStrCat("Custom command for ", mainOutput);
731 gg->WriteCustomCommandBuild(
732 this->BuildCommandLine(cmdLines, ccg.GetOutputConfig(), fileConfig,
733 customStep),
734 this->ConstructComment(ccg), comment, depfile, cc->GetJobPool(),
735 cc->GetUsesTerminal(),
736 /*restat*/ !symbolic || !byproducts.empty(), fileConfig,
737 std::move(ccOutputs), std::move(ninjaDeps),
738 std::move(sortedOrderOnlyDeps));
743 bool cmLocalNinjaGenerator::HasUniqueByproducts(
744 std::vector<std::string> const& byproducts, cmListFileBacktrace const& bt)
746 std::vector<std::string> configs =
747 this->GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
748 cmGeneratorExpression ge(*this->GetCMakeInstance(), bt);
749 for (std::string const& p : byproducts) {
750 if (cmGeneratorExpression::Find(p) == std::string::npos) {
751 return false;
753 std::set<std::string> seen;
754 std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
755 for (std::string const& config : configs) {
756 for (std::string const& b :
757 this->ExpandCustomCommandOutputPaths(*cge, config)) {
758 if (!seen.insert(b).second) {
759 return false;
764 return true;
767 namespace {
768 bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs)
770 std::set<std::string> allOutputs;
771 std::set<std::string> allByproducts;
772 for (cmCustomCommandGenerator const& ccg : ccgs) {
773 for (std::string const& output : ccg.GetOutputs()) {
774 if (!allOutputs.insert(output).second) {
775 return false;
778 for (std::string const& byproduct : ccg.GetByproducts()) {
779 if (!allByproducts.insert(byproduct).second) {
780 return false;
784 return true;
788 std::string cmLocalNinjaGenerator::CreateUtilityOutput(
789 std::string const& targetName, std::vector<std::string> const& byproducts,
790 cmListFileBacktrace const& bt)
792 // In Ninja Multi-Config, we can only produce cross-config utility
793 // commands if all byproducts are per-config.
794 if (!this->GetGlobalGenerator()->IsMultiConfig() ||
795 !this->HasUniqueByproducts(byproducts, bt)) {
796 return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts,
797 bt);
800 std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(),
801 "/CMakeFiles/", targetName, '-');
802 // The output is not actually created so mark it symbolic.
803 for (std::string const& config :
804 this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig)) {
805 std::string const force = cmStrCat(base, config);
806 if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) {
807 sf->SetProperty("SYMBOLIC", "1");
808 } else {
809 cmSystemTools::Error("Could not get source file entry for " + force);
812 this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName);
813 return cmStrCat(base, "$<CONFIG>"_s);
816 std::vector<cmCustomCommandGenerator>
817 cmLocalNinjaGenerator::MakeCustomCommandGenerators(
818 cmCustomCommand const& cc, std::string const& fileConfig)
820 cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
822 bool transformDepfile = false;
823 switch (cc.GetCMP0116Status()) {
824 case cmPolicies::WARN:
825 CM_FALLTHROUGH;
826 case cmPolicies::OLD:
827 break;
828 case cmPolicies::REQUIRED_IF_USED:
829 case cmPolicies::REQUIRED_ALWAYS:
830 case cmPolicies::NEW:
831 transformDepfile = true;
832 break;
835 // Start with the build graph's configuration.
836 std::vector<cmCustomCommandGenerator> ccgs;
837 ccgs.emplace_back(cc, fileConfig, this, transformDepfile);
839 // Consider adding cross configurations.
840 if (!gg->EnableCrossConfigBuild()) {
841 return ccgs;
844 // Outputs and byproducts must be expressed using generator expressions.
845 for (std::string const& output : cc.GetOutputs()) {
846 if (cmGeneratorExpression::Find(output) == std::string::npos) {
847 return ccgs;
850 for (std::string const& byproduct : cc.GetByproducts()) {
851 if (cmGeneratorExpression::Find(byproduct) == std::string::npos) {
852 return ccgs;
856 // Tentatively add the other cross configurations.
857 for (std::string const& config : gg->GetCrossConfigs(fileConfig)) {
858 if (fileConfig != config) {
859 ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config);
863 // If outputs and byproducts are not unique to each configuration,
864 // drop the cross configurations.
865 if (!HasUniqueOutputs(ccgs)) {
866 ccgs.erase(ccgs.begin() + 1, ccgs.end());
869 return ccgs;
872 void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
873 cmGeneratorTarget* target)
875 CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>());
876 std::pair<CustomCommandTargetMap::iterator, bool> ins =
877 this->CustomCommandTargets.insert(v);
878 if (ins.second) {
879 this->CustomCommands.push_back(cc);
881 ins.first->second.insert(target);
884 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements(
885 const std::string& fileConfig)
887 for (cmCustomCommand const* customCommand : this->CustomCommands) {
888 auto i = this->CustomCommandTargets.find(customCommand);
889 assert(i != this->CustomCommandTargets.end());
891 this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig);
895 std::string cmLocalNinjaGenerator::MakeCustomLauncher(
896 cmCustomCommandGenerator const& ccg)
898 cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
900 if (!cmNonempty(property_value)) {
901 return std::string();
904 // Expand rule variables referenced in the given launcher command.
905 cmRulePlaceholderExpander::RuleVariables vars;
907 std::string output;
908 const std::vector<std::string>& outputs = ccg.GetOutputs();
909 if (!outputs.empty()) {
910 output = outputs[0];
911 if (ccg.GetWorkingDirectory().empty()) {
912 output = this->MaybeRelativeToCurBinDir(output);
914 output = this->ConvertToOutputFormat(output, cmOutputConverter::SHELL);
916 vars.Output = output.c_str();
918 auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander();
920 std::string launcher = *property_value;
921 rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
922 if (!launcher.empty()) {
923 launcher += " ";
926 return launcher;
929 void cmLocalNinjaGenerator::AdditionalCleanFiles(const std::string& config)
931 if (cmValue prop_value =
932 this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
933 cmList cleanFiles{ cmGeneratorExpression::Evaluate(*prop_value, this,
934 config) };
935 std::string const& binaryDir = this->GetCurrentBinaryDirectory();
936 cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
937 for (auto const& cleanFile : cleanFiles) {
938 // Support relative paths
939 gg->AddAdditionalCleanFile(
940 cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);