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