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