Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / cmLocalNinjaGenerator.cxx
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"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cstdio>
8 #include <iterator>
9 #include <memory>
10 #include <sstream>
11 #include <utility>
12
13 #include <cmext/string_view>
14
15 #include "cmsys/FStream.hxx"
16
17 #include "cmCryptoHash.h"
18 #include "cmCustomCommand.h"
19 #include "cmCustomCommandGenerator.h"
20 #include "cmGeneratedFileStream.h"
21 #include "cmGeneratorExpression.h"
22 #include "cmGeneratorTarget.h"
23 #include "cmGlobalGenerator.h"
24 #include "cmGlobalNinjaGenerator.h"
25 #include "cmLocalGenerator.h"
26 #include "cmMakefile.h"
27 #include "cmMessageType.h"
28 #include "cmNinjaTargetGenerator.h"
29 #include "cmPolicies.h"
30 #include "cmRulePlaceholderExpander.h"
31 #include "cmSourceFile.h"
32 #include "cmState.h"
33 #include "cmStateTypes.h"
34 #include "cmStringAlgorithms.h"
35 #include "cmSystemTools.h"
36 #include "cmTarget.h"
37 #include "cmValue.h"
38 #include "cmake.h"
39
40 cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg,
41                                              cmMakefile* mf)
42   : cmLocalCommonGenerator(gg, mf, WorkDir::TopBin)
43 {
44 }
45
46 // Virtual public methods.
47
48 cmRulePlaceholderExpander*
49 cmLocalNinjaGenerator::CreateRulePlaceholderExpander() const
50 {
51   cmRulePlaceholderExpander* ret =
52     this->cmLocalGenerator::CreateRulePlaceholderExpander();
53   ret->SetTargetImpLib("$TARGET_IMPLIB");
54   return ret;
55 }
56
57 cmLocalNinjaGenerator::~cmLocalNinjaGenerator() = default;
58
59 void cmLocalNinjaGenerator::Generate()
60 {
61   // Compute the path to use when referencing the current output
62   // directory from the top output directory.
63   this->HomeRelativeOutputPath =
64     this->MaybeRelativeToTopBinDir(this->GetCurrentBinaryDirectory());
65   if (this->HomeRelativeOutputPath == ".") {
66     this->HomeRelativeOutputPath.clear();
67   }
68
69   if (this->GetGlobalGenerator()->IsMultiConfig()) {
70     for (auto const& config : this->GetConfigNames()) {
71       this->WriteProcessedMakefile(this->GetImplFileStream(config));
72     }
73   }
74   this->WriteProcessedMakefile(this->GetCommonFileStream());
75 #ifdef NINJA_GEN_VERBOSE_FILES
76   this->WriteProcessedMakefile(this->GetRulesFileStream());
77 #endif
78
79   // We do that only once for the top CMakeLists.txt file.
80   if (this->IsRootMakefile()) {
81     this->WriteBuildFileTop();
82
83     this->WritePools(this->GetRulesFileStream());
84
85     const std::string& showIncludesPrefix =
86       this->GetMakefile()->GetSafeDefinition("CMAKE_CL_SHOWINCLUDES_PREFIX");
87     if (!showIncludesPrefix.empty()) {
88       cmGlobalNinjaGenerator::WriteComment(this->GetRulesFileStream(),
89                                            "localized /showIncludes string");
90       this->GetRulesFileStream() << "msvc_deps_prefix = ";
91       // 'cl /showIncludes' encodes output in the console output code page.
92       // It may differ from the encoding used for file paths in 'build.ninja'.
93       // Ninja matches the showIncludes prefix using its raw byte sequence.
94       this->GetRulesFileStream().WriteAltEncoding(
95         showIncludesPrefix, cmGeneratedFileStream::Encoding::ConsoleOutput);
96       this->GetRulesFileStream() << "\n\n";
97     }
98   }
99
100   for (const auto& target : this->GetGeneratorTargets()) {
101     if (!target->IsInBuildSystem()) {
102       continue;
103     }
104     auto tg = cmNinjaTargetGenerator::New(target.get());
105     if (tg) {
106       if (target->Target->IsPerConfig()) {
107         for (auto const& config : this->GetConfigNames()) {
108           tg->Generate(config);
109           if (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
110               this->GetGlobalGenerator()->IsMultiConfig()) {
111             cmNinjaBuild phonyAlias("phony");
112             this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
113               target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact);
114             this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
115               target.get(), phonyAlias.ExplicitDeps, config,
116               DependOnTargetArtifact);
117             this->GetGlobalNinjaGenerator()->WriteBuild(
118               *this->GetGlobalNinjaGenerator()->GetConfigFileStream(config),
119               phonyAlias);
120           }
121         }
122         if (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
123             this->GetGlobalGenerator()->IsMultiConfig()) {
124           if (!this->GetGlobalNinjaGenerator()->GetDefaultConfigs().empty()) {
125             cmNinjaBuild phonyAlias("phony");
126             this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
127               target.get(), phonyAlias.Outputs, "", DependOnTargetArtifact);
128             for (auto const& config :
129                  this->GetGlobalNinjaGenerator()->GetDefaultConfigs()) {
130               this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
131                 target.get(), phonyAlias.ExplicitDeps, config,
132                 DependOnTargetArtifact);
133             }
134             this->GetGlobalNinjaGenerator()->WriteBuild(
135               *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
136               phonyAlias);
137           }
138           cmNinjaBuild phonyAlias("phony");
139           this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
140             target.get(), phonyAlias.Outputs, "all", DependOnTargetArtifact);
141           for (auto const& config : this->GetConfigNames()) {
142             this->GetGlobalNinjaGenerator()->AppendTargetOutputs(
143               target.get(), phonyAlias.ExplicitDeps, config,
144               DependOnTargetArtifact);
145           }
146           this->GetGlobalNinjaGenerator()->WriteBuild(
147             *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
148             phonyAlias);
149         }
150       } else {
151         tg->Generate("");
152       }
153     }
154   }
155
156   for (auto const& config : this->GetConfigNames()) {
157     this->WriteCustomCommandBuildStatements(config);
158     this->AdditionalCleanFiles(config);
159   }
160 }
161
162 // TODO: Picked up from cmLocalUnixMakefileGenerator3.  Refactor it.
163 std::string cmLocalNinjaGenerator::GetTargetDirectory(
164   cmGeneratorTarget const* target) const
165 {
166   std::string dir = cmStrCat("CMakeFiles/", target->GetName());
167 #if defined(__VMS)
168   dir += "_dir";
169 #else
170   dir += ".dir";
171 #endif
172   return dir;
173 }
174
175 // Non-virtual public methods.
176
177 const cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
178   const
179 {
180   return static_cast<const cmGlobalNinjaGenerator*>(
181     this->GetGlobalGenerator());
182 }
183
184 cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
185 {
186   return static_cast<cmGlobalNinjaGenerator*>(this->GetGlobalGenerator());
187 }
188
189 // Virtual protected methods.
190
191 std::string cmLocalNinjaGenerator::ConvertToIncludeReference(
192   std::string const& path, cmOutputConverter::OutputFormat format)
193 {
194   return this->ConvertToOutputFormat(path, format);
195 }
196
197 // Private methods.
198
199 cmGeneratedFileStream& cmLocalNinjaGenerator::GetImplFileStream(
200   const std::string& config) const
201 {
202   return *this->GetGlobalNinjaGenerator()->GetImplFileStream(config);
203 }
204
205 cmGeneratedFileStream& cmLocalNinjaGenerator::GetCommonFileStream() const
206 {
207   return *this->GetGlobalNinjaGenerator()->GetCommonFileStream();
208 }
209
210 cmGeneratedFileStream& cmLocalNinjaGenerator::GetRulesFileStream() const
211 {
212   return *this->GetGlobalNinjaGenerator()->GetRulesFileStream();
213 }
214
215 const cmake* cmLocalNinjaGenerator::GetCMakeInstance() const
216 {
217   return this->GetGlobalGenerator()->GetCMakeInstance();
218 }
219
220 cmake* cmLocalNinjaGenerator::GetCMakeInstance()
221 {
222   return this->GetGlobalGenerator()->GetCMakeInstance();
223 }
224
225 void cmLocalNinjaGenerator::WriteBuildFileTop()
226 {
227   this->WriteProjectHeader(this->GetCommonFileStream());
228
229   if (this->GetGlobalGenerator()->IsMultiConfig()) {
230     for (auto const& config : this->GetConfigNames()) {
231       auto& stream = this->GetImplFileStream(config);
232       this->WriteProjectHeader(stream);
233       this->WriteNinjaRequiredVersion(stream);
234       this->WriteNinjaConfigurationVariable(stream, config);
235       this->WriteNinjaFilesInclusionConfig(stream);
236     }
237   } else {
238     this->WriteNinjaRequiredVersion(this->GetCommonFileStream());
239     this->WriteNinjaConfigurationVariable(this->GetCommonFileStream(),
240                                           this->GetConfigNames().front());
241   }
242   this->WriteNinjaFilesInclusionCommon(this->GetCommonFileStream());
243   this->WriteNinjaWorkDir(this->GetCommonFileStream());
244
245   // For the rule file.
246   this->WriteProjectHeader(this->GetRulesFileStream());
247 }
248
249 void cmLocalNinjaGenerator::WriteProjectHeader(std::ostream& os)
250 {
251   cmGlobalNinjaGenerator::WriteDivider(os);
252   os << "# Project: " << this->GetProjectName() << '\n'
253      << "# Configurations: " << cmJoin(this->GetConfigNames(), ", ") << '\n';
254   cmGlobalNinjaGenerator::WriteDivider(os);
255 }
256
257 void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os)
258 {
259   // Default required version
260   std::string requiredVersion = cmGlobalNinjaGenerator::RequiredNinjaVersion();
261
262   // Ninja generator uses the 'console' pool if available (>= 1.5)
263   if (this->GetGlobalNinjaGenerator()->SupportsDirectConsole()) {
264     requiredVersion =
265       cmGlobalNinjaGenerator::RequiredNinjaVersionForConsolePool();
266   }
267
268   // The Ninja generator writes rules which require support for restat
269   // when rebuilding build.ninja manifest (>= 1.8)
270   if (this->GetGlobalNinjaGenerator()->SupportsManifestRestat() &&
271       this->GetCMakeInstance()->DoWriteGlobVerifyTarget() &&
272       !this->GetGlobalNinjaGenerator()->GlobalSettingIsOn(
273         "CMAKE_SUPPRESS_REGENERATION")) {
274     requiredVersion =
275       cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat();
276   }
277
278   cmGlobalNinjaGenerator::WriteComment(
279     os, "Minimal version of Ninja required by this file");
280   os << "ninja_required_version = " << requiredVersion << "\n\n";
281 }
282
283 void cmLocalNinjaGenerator::WriteNinjaConfigurationVariable(
284   std::ostream& os, const std::string& config)
285 {
286   cmGlobalNinjaGenerator::WriteVariable(
287     os, "CONFIGURATION", config,
288     "Set configuration variable for custom commands.");
289 }
290
291 void cmLocalNinjaGenerator::WritePools(std::ostream& os)
292 {
293   cmGlobalNinjaGenerator::WriteDivider(os);
294
295   cmValue jobpools =
296     this->GetCMakeInstance()->GetState()->GetGlobalProperty("JOB_POOLS");
297   if (!jobpools) {
298     jobpools = this->GetMakefile()->GetDefinition("CMAKE_JOB_POOLS");
299   }
300   if (jobpools) {
301     cmGlobalNinjaGenerator::WriteComment(
302       os, "Pools defined by global property JOB_POOLS");
303     std::vector<std::string> pools = cmExpandedList(*jobpools);
304     for (std::string const& pool : pools) {
305       const std::string::size_type eq = pool.find('=');
306       unsigned int jobs;
307       if (eq != std::string::npos &&
308           sscanf(pool.c_str() + eq, "=%u", &jobs) == 1) {
309         os << "pool " << pool.substr(0, eq) << "\n  depth = " << jobs
310            << "\n\n";
311       } else {
312         cmSystemTools::Error("Invalid pool defined by property 'JOB_POOLS': " +
313                              pool);
314       }
315     }
316   }
317 }
318
319 void cmLocalNinjaGenerator::WriteNinjaFilesInclusionConfig(std::ostream& os)
320 {
321   cmGlobalNinjaGenerator::WriteDivider(os);
322   os << "# Include auxiliary files.\n\n";
323   cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
324   std::string const ninjaCommonFile =
325     ng->NinjaOutputPath(cmGlobalNinjaMultiGenerator::NINJA_COMMON_FILE);
326   std::string const commonFilePath = ng->EncodePath(ninjaCommonFile);
327   cmGlobalNinjaGenerator::WriteInclude(os, commonFilePath,
328                                        "Include common file.");
329   os << "\n";
330 }
331
332 void cmLocalNinjaGenerator::WriteNinjaFilesInclusionCommon(std::ostream& os)
333 {
334   cmGlobalNinjaGenerator::WriteDivider(os);
335   os << "# Include auxiliary files.\n\n";
336   cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
337   std::string const ninjaRulesFile =
338     ng->NinjaOutputPath(cmGlobalNinjaGenerator::NINJA_RULES_FILE);
339   std::string const rulesFilePath = ng->EncodePath(ninjaRulesFile);
340   cmGlobalNinjaGenerator::WriteInclude(os, rulesFilePath,
341                                        "Include rules file.");
342   os << "\n";
343 }
344
345 void cmLocalNinjaGenerator::WriteNinjaWorkDir(std::ostream& os)
346 {
347   cmGlobalNinjaGenerator::WriteDivider(os);
348   cmGlobalNinjaGenerator::WriteComment(
349     os, "Logical path to working directory; prefix for absolute paths.");
350   cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
351   std::string ninja_workdir = this->GetBinaryDirectory();
352   ng->StripNinjaOutputPathPrefixAsSuffix(ninja_workdir); // Also appends '/'.
353   os << "cmake_ninja_workdir = " << ng->EncodePath(ninja_workdir) << "\n";
354 }
355
356 void cmLocalNinjaGenerator::WriteProcessedMakefile(std::ostream& os)
357 {
358   cmGlobalNinjaGenerator::WriteDivider(os);
359   os << "# Write statements declared in CMakeLists.txt:\n"
360      << "# " << this->Makefile->GetSafeDefinition("CMAKE_CURRENT_LIST_FILE")
361      << '\n';
362   if (this->IsRootMakefile()) {
363     os << "# Which is the root file.\n";
364   }
365   cmGlobalNinjaGenerator::WriteDivider(os);
366   os << '\n';
367 }
368
369 void cmLocalNinjaGenerator::AppendTargetOutputs(cmGeneratorTarget* target,
370                                                 cmNinjaDeps& outputs,
371                                                 const std::string& config)
372 {
373   this->GetGlobalNinjaGenerator()->AppendTargetOutputs(target, outputs, config,
374                                                        DependOnTargetArtifact);
375 }
376
377 void cmLocalNinjaGenerator::AppendTargetDepends(cmGeneratorTarget* target,
378                                                 cmNinjaDeps& outputs,
379                                                 const std::string& config,
380                                                 const std::string& fileConfig,
381                                                 cmNinjaTargetDepends depends)
382 {
383   this->GetGlobalNinjaGenerator()->AppendTargetDepends(target, outputs, config,
384                                                        fileConfig, depends);
385 }
386
387 void cmLocalNinjaGenerator::AppendCustomCommandDeps(
388   cmCustomCommandGenerator const& ccg, cmNinjaDeps& ninjaDeps,
389   const std::string& config)
390 {
391   for (std::string const& i : ccg.GetDepends()) {
392     std::string dep;
393     if (this->GetRealDependency(i, config, dep)) {
394       ninjaDeps.push_back(
395         this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep));
396     }
397   }
398 }
399
400 std::string cmLocalNinjaGenerator::WriteCommandScript(
401   std::vector<std::string> const& cmdLines, std::string const& outputConfig,
402   std::string const& commandConfig, std::string const& customStep,
403   cmGeneratorTarget const* target) const
404 {
405   std::string scriptPath;
406   if (target) {
407     scriptPath = target->GetSupportDirectory();
408   } else {
409     scriptPath = cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles");
410   }
411   scriptPath += this->GetGlobalNinjaGenerator()->ConfigDirectory(outputConfig);
412   cmSystemTools::MakeDirectory(scriptPath);
413   scriptPath += '/';
414   scriptPath += customStep;
415   if (this->GlobalGenerator->IsMultiConfig()) {
416     scriptPath += cmStrCat('-', commandConfig);
417   }
418 #ifdef _WIN32
419   scriptPath += ".bat";
420 #else
421   scriptPath += ".sh";
422 #endif
423
424   cmsys::ofstream script(scriptPath.c_str());
425
426 #ifdef _WIN32
427   script << "@echo off\n";
428   int line = 1;
429 #else
430   script << "set -e\n\n";
431 #endif
432
433   for (auto const& i : cmdLines) {
434     std::string cmd = i;
435     // The command line was built assuming it would be written to
436     // the build.ninja file, so it uses '$$' for '$'.  Remove this
437     // for the raw shell script.
438     cmSystemTools::ReplaceString(cmd, "$$", "$");
439 #ifdef _WIN32
440     script << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)"
441            << '\n';
442 #else
443     script << cmd << '\n';
444 #endif
445   }
446
447 #ifdef _WIN32
448   script << "goto :EOF\n\n"
449             ":ABORT\n"
450             "set ERROR_CODE=%ERRORLEVEL%\n"
451             "echo Batch file failed at line %FAIL_LINE% "
452             "with errorcode %ERRORLEVEL%\n"
453             "exit /b %ERROR_CODE%";
454 #endif
455
456   return scriptPath;
457 }
458
459 std::string cmLocalNinjaGenerator::BuildCommandLine(
460   std::vector<std::string> const& cmdLines, std::string const& outputConfig,
461   std::string const& commandConfig, std::string const& customStep,
462   cmGeneratorTarget const* target) const
463 {
464   // If we have no commands but we need to build a command anyway, use noop.
465   // This happens when building a POST_BUILD value for link targets that
466   // don't use POST_BUILD.
467   if (cmdLines.empty()) {
468     return cmGlobalNinjaGenerator::SHELL_NOOP;
469   }
470
471   // If this is a custom step then we will have no '$VAR' ninja placeholders.
472   // This means we can deal with long command sequences by writing to a script.
473   // Do this if the command lines are on the scale of the OS limit.
474   if (!customStep.empty()) {
475     size_t cmdLinesTotal = 0;
476     for (std::string const& cmd : cmdLines) {
477       cmdLinesTotal += cmd.length() + 6;
478     }
479     if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) {
480       std::string const scriptPath = this->WriteCommandScript(
481         cmdLines, outputConfig, commandConfig, customStep, target);
482       std::string cmd
483 #ifndef _WIN32
484         = "/bin/sh "
485 #endif
486         ;
487       cmd += this->ConvertToOutputFormat(
488         this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath),
489         cmOutputConverter::SHELL);
490
491       // Add an unused argument based on script content so that Ninja
492       // knows when the command lines change.
493       cmd += " ";
494       cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
495       cmd += hash.HashFile(scriptPath).substr(0, 16);
496       return cmd;
497     }
498   }
499
500   std::ostringstream cmd;
501   for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li)
502 #ifdef _WIN32
503   {
504     if (li != cmdLines.begin()) {
505       cmd << " && ";
506     } else if (cmdLines.size() > 1) {
507       cmd << "cmd.exe /C \"";
508     }
509     // Put current cmdLine in brackets if it contains "||" because it has
510     // higher precedence than "&&" in cmd.exe
511     if (li->find("||") != std::string::npos) {
512       cmd << "( " << *li << " )";
513     } else {
514       cmd << *li;
515     }
516   }
517   if (cmdLines.size() > 1) {
518     cmd << "\"";
519   }
520 #else
521   {
522     if (li != cmdLines.begin()) {
523       cmd << " && ";
524     }
525     cmd << *li;
526   }
527 #endif
528   return cmd.str();
529 }
530
531 void cmLocalNinjaGenerator::AppendCustomCommandLines(
532   cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines)
533 {
534   auto* gg = this->GetGlobalNinjaGenerator();
535
536   if (ccg.GetNumberOfCommands() > 0) {
537     std::string wd = ccg.GetWorkingDirectory();
538     if (wd.empty()) {
539       wd = this->GetCurrentBinaryDirectory();
540     }
541
542     std::ostringstream cdCmd;
543 #ifdef _WIN32
544     std::string cdStr = "cd /D ";
545 #else
546     std::string cdStr = "cd ";
547 #endif
548     cdCmd << cdStr
549           << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
550     cmdLines.push_back(cdCmd.str());
551   }
552
553   std::string launcher = this->MakeCustomLauncher(ccg);
554
555   for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) {
556     std::string c = ccg.GetCommand(i);
557     if (c.empty()) {
558       continue;
559     }
560     cmdLines.push_back(launcher +
561                        this->ConvertToOutputFormat(
562                          c,
563                          gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI
564                                              : cmOutputConverter::SHELL));
565
566     std::string& cmd = cmdLines.back();
567     ccg.AppendArguments(i, cmd);
568   }
569 }
570
571 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
572   cmCustomCommand const* cc, const std::set<cmGeneratorTarget*>& targets,
573   const std::string& fileConfig)
574 {
575   cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
576   if (gg->SeenCustomCommand(cc, fileConfig)) {
577     return;
578   }
579
580   auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig);
581   for (cmCustomCommandGenerator const& ccg : ccgs) {
582     if (ccg.GetOutputs().empty() && ccg.GetByproducts().empty()) {
583       // Generator expressions evaluate to no output for this config.
584       continue;
585     }
586
587     cmNinjaDeps orderOnlyDeps;
588
589     // A custom command may appear on multiple targets.  However, some build
590     // systems exist where the target dependencies on some of the targets are
591     // overspecified, leading to a dependency cycle.  If we assume all target
592     // dependencies are a superset of the true target dependencies for this
593     // custom command, we can take the set intersection of all target
594     // dependencies to obtain a correct dependency list.
595     //
596     // FIXME: This won't work in certain obscure scenarios involving indirect
597     // dependencies.
598     auto j = targets.begin();
599     assert(j != targets.end());
600     this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
601       *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
602     std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
603     ++j;
604
605     for (; j != targets.end(); ++j) {
606       std::vector<std::string> jDeps;
607       std::vector<std::string> depsIntersection;
608       this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
609         *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
610       std::sort(jDeps.begin(), jDeps.end());
611       std::set_intersection(orderOnlyDeps.begin(), orderOnlyDeps.end(),
612                             jDeps.begin(), jDeps.end(),
613                             std::back_inserter(depsIntersection));
614       orderOnlyDeps = depsIntersection;
615     }
616
617     const std::vector<std::string>& outputs = ccg.GetOutputs();
618     const std::vector<std::string>& byproducts = ccg.GetByproducts();
619
620     bool symbolic = false;
621     for (std::string const& output : outputs) {
622       if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
623         if (sf->GetPropertyAsBool("SYMBOLIC")) {
624           symbolic = true;
625           break;
626         }
627       }
628     }
629
630     cmGlobalNinjaGenerator::CCOutputs ccOutputs(gg);
631     ccOutputs.Add(outputs);
632     ccOutputs.Add(byproducts);
633
634     std::string mainOutput = ccOutputs.ExplicitOuts[0];
635
636     cmNinjaDeps ninjaDeps;
637     this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig);
638
639     std::vector<std::string> cmdLines;
640     this->AppendCustomCommandLines(ccg, cmdLines);
641
642     if (cmdLines.empty()) {
643       cmNinjaBuild build("phony");
644       build.Comment = cmStrCat("Phony custom command for ", mainOutput);
645       build.Outputs = std::move(ccOutputs.ExplicitOuts);
646       build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts);
647       build.ExplicitDeps = std::move(ninjaDeps);
648       build.OrderOnlyDeps = orderOnlyDeps;
649       gg->WriteBuild(this->GetImplFileStream(fileConfig), build);
650     } else {
651       std::string customStep = cmSystemTools::GetFilenameName(mainOutput);
652       if (this->GlobalGenerator->IsMultiConfig()) {
653         customStep += '-';
654         customStep += fileConfig;
655         customStep += '-';
656         customStep += ccg.GetOutputConfig();
657       }
658       // Hash full path to make unique.
659       customStep += '-';
660       cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
661       customStep += hash.HashString(mainOutput).substr(0, 7);
662
663       std::string depfile = ccg.GetDepfile();
664       if (!depfile.empty()) {
665         switch (cc->GetCMP0116Status()) {
666           case cmPolicies::WARN:
667             if (this->GetCurrentBinaryDirectory() !=
668                   this->GetBinaryDirectory() ||
669                 this->Makefile->PolicyOptionalWarningEnabled(
670                   "CMAKE_POLICY_WARNING_CMP0116")) {
671               this->GetCMakeInstance()->IssueMessage(
672                 MessageType::AUTHOR_WARNING,
673                 cmPolicies::GetPolicyWarning(cmPolicies::CMP0116),
674                 cc->GetBacktrace());
675             }
676             CM_FALLTHROUGH;
677           case cmPolicies::OLD:
678             break;
679           case cmPolicies::REQUIRED_IF_USED:
680           case cmPolicies::REQUIRED_ALWAYS:
681           case cmPolicies::NEW:
682             depfile = ccg.GetInternalDepfile();
683             break;
684         }
685       }
686
687       std::string comment = cmStrCat("Custom command for ", mainOutput);
688       gg->WriteCustomCommandBuild(
689         this->BuildCommandLine(cmdLines, ccg.GetOutputConfig(), fileConfig,
690                                customStep),
691         this->ConstructComment(ccg), comment, depfile, cc->GetJobPool(),
692         cc->GetUsesTerminal(),
693         /*restat*/ !symbolic || !byproducts.empty(), fileConfig,
694         std::move(ccOutputs), std::move(ninjaDeps), std::move(orderOnlyDeps));
695     }
696   }
697 }
698
699 bool cmLocalNinjaGenerator::HasUniqueByproducts(
700   std::vector<std::string> const& byproducts, cmListFileBacktrace const& bt)
701 {
702   std::vector<std::string> configs =
703     this->GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
704   cmGeneratorExpression ge(bt);
705   for (std::string const& p : byproducts) {
706     if (cmGeneratorExpression::Find(p) == std::string::npos) {
707       return false;
708     }
709     std::set<std::string> seen;
710     std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p);
711     for (std::string const& config : configs) {
712       for (std::string const& b :
713            this->ExpandCustomCommandOutputPaths(*cge, config)) {
714         if (!seen.insert(b).second) {
715           return false;
716         }
717       }
718     }
719   }
720   return true;
721 }
722
723 namespace {
724 bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs)
725 {
726   std::set<std::string> allOutputs;
727   std::set<std::string> allByproducts;
728   for (cmCustomCommandGenerator const& ccg : ccgs) {
729     for (std::string const& output : ccg.GetOutputs()) {
730       if (!allOutputs.insert(output).second) {
731         return false;
732       }
733     }
734     for (std::string const& byproduct : ccg.GetByproducts()) {
735       if (!allByproducts.insert(byproduct).second) {
736         return false;
737       }
738     }
739   }
740   return true;
741 }
742 }
743
744 std::string cmLocalNinjaGenerator::CreateUtilityOutput(
745   std::string const& targetName, std::vector<std::string> const& byproducts,
746   cmListFileBacktrace const& bt)
747 {
748   // In Ninja Multi-Config, we can only produce cross-config utility
749   // commands if all byproducts are per-config.
750   if (!this->GetGlobalGenerator()->IsMultiConfig() ||
751       !this->HasUniqueByproducts(byproducts, bt)) {
752     return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts,
753                                                        bt);
754   }
755
756   std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(),
757                                     "/CMakeFiles/", targetName, '-');
758   // The output is not actually created so mark it symbolic.
759   for (std::string const& config :
760        this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig)) {
761     std::string const force = cmStrCat(base, config);
762     if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) {
763       sf->SetProperty("SYMBOLIC", "1");
764     } else {
765       cmSystemTools::Error("Could not get source file entry for " + force);
766     }
767   }
768   this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName);
769   return cmStrCat(base, "$<CONFIG>"_s);
770 }
771
772 std::vector<cmCustomCommandGenerator>
773 cmLocalNinjaGenerator::MakeCustomCommandGenerators(
774   cmCustomCommand const& cc, std::string const& fileConfig)
775 {
776   cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
777
778   bool transformDepfile = false;
779   switch (cc.GetCMP0116Status()) {
780     case cmPolicies::WARN:
781       CM_FALLTHROUGH;
782     case cmPolicies::OLD:
783       break;
784     case cmPolicies::REQUIRED_IF_USED:
785     case cmPolicies::REQUIRED_ALWAYS:
786     case cmPolicies::NEW:
787       transformDepfile = true;
788       break;
789   }
790
791   // Start with the build graph's configuration.
792   std::vector<cmCustomCommandGenerator> ccgs;
793   ccgs.emplace_back(cc, fileConfig, this, transformDepfile);
794
795   // Consider adding cross configurations.
796   if (!gg->EnableCrossConfigBuild()) {
797     return ccgs;
798   }
799
800   // Outputs and byproducts must be expressed using generator expressions.
801   for (std::string const& output : cc.GetOutputs()) {
802     if (cmGeneratorExpression::Find(output) == std::string::npos) {
803       return ccgs;
804     }
805   }
806   for (std::string const& byproduct : cc.GetByproducts()) {
807     if (cmGeneratorExpression::Find(byproduct) == std::string::npos) {
808       return ccgs;
809     }
810   }
811
812   // Tentatively add the other cross configurations.
813   for (std::string const& config : gg->GetCrossConfigs(fileConfig)) {
814     if (fileConfig != config) {
815       ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config);
816     }
817   }
818
819   // If outputs and byproducts are not unique to each configuration,
820   // drop the cross configurations.
821   if (!HasUniqueOutputs(ccgs)) {
822     ccgs.erase(ccgs.begin() + 1, ccgs.end());
823   }
824
825   return ccgs;
826 }
827
828 void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
829                                                    cmGeneratorTarget* target)
830 {
831   CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>());
832   std::pair<CustomCommandTargetMap::iterator, bool> ins =
833     this->CustomCommandTargets.insert(v);
834   if (ins.second) {
835     this->CustomCommands.push_back(cc);
836   }
837   ins.first->second.insert(target);
838 }
839
840 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements(
841   const std::string& fileConfig)
842 {
843   for (cmCustomCommand const* customCommand : this->CustomCommands) {
844     auto i = this->CustomCommandTargets.find(customCommand);
845     assert(i != this->CustomCommandTargets.end());
846
847     this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig);
848   }
849 }
850
851 std::string cmLocalNinjaGenerator::MakeCustomLauncher(
852   cmCustomCommandGenerator const& ccg)
853 {
854   cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
855
856   if (!cmNonempty(property_value)) {
857     return std::string();
858   }
859
860   // Expand rule variables referenced in the given launcher command.
861   cmRulePlaceholderExpander::RuleVariables vars;
862
863   std::string output;
864   const std::vector<std::string>& outputs = ccg.GetOutputs();
865   if (!outputs.empty()) {
866     output = outputs[0];
867     if (ccg.GetWorkingDirectory().empty()) {
868       output = this->MaybeRelativeToCurBinDir(output);
869     }
870     output = this->ConvertToOutputFormat(output, cmOutputConverter::SHELL);
871   }
872   vars.Output = output.c_str();
873
874   std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
875     this->CreateRulePlaceholderExpander());
876
877   std::string launcher = *property_value;
878   rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
879   if (!launcher.empty()) {
880     launcher += " ";
881   }
882
883   return launcher;
884 }
885
886 void cmLocalNinjaGenerator::AdditionalCleanFiles(const std::string& config)
887 {
888   if (cmValue prop_value =
889         this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
890     std::vector<std::string> cleanFiles;
891     {
892       cmExpandList(cmGeneratorExpression::Evaluate(*prop_value, this, config),
893                    cleanFiles);
894     }
895     std::string const& binaryDir = this->GetCurrentBinaryDirectory();
896     cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
897     for (std::string const& cleanFile : cleanFiles) {
898       // Support relative paths
899       gg->AddAdditionalCleanFile(
900         cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);
901     }
902   }
903 }