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"
13 #include <cmext/string_view>
15 #include "cmsys/FStream.hxx"
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"
33 #include "cmStateTypes.h"
34 #include "cmStringAlgorithms.h"
35 #include "cmSystemTools.h"
40 cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg,
42 : cmLocalCommonGenerator(gg, mf, WorkDir::TopBin)
46 // Virtual public methods.
48 cmRulePlaceholderExpander*
49 cmLocalNinjaGenerator::CreateRulePlaceholderExpander() const
51 cmRulePlaceholderExpander* ret =
52 this->cmLocalGenerator::CreateRulePlaceholderExpander();
53 ret->SetTargetImpLib("$TARGET_IMPLIB");
57 cmLocalNinjaGenerator::~cmLocalNinjaGenerator() = default;
59 void cmLocalNinjaGenerator::Generate()
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();
69 if (this->GetGlobalGenerator()->IsMultiConfig()) {
70 for (auto const& config : this->GetConfigNames()) {
71 this->WriteProcessedMakefile(this->GetImplFileStream(config));
74 this->WriteProcessedMakefile(this->GetCommonFileStream());
75 #ifdef NINJA_GEN_VERBOSE_FILES
76 this->WriteProcessedMakefile(this->GetRulesFileStream());
79 // We do that only once for the top CMakeLists.txt file.
80 if (this->IsRootMakefile()) {
81 this->WriteBuildFileTop();
83 this->WritePools(this->GetRulesFileStream());
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";
100 for (const auto& target : this->GetGeneratorTargets()) {
101 if (!target->IsInBuildSystem()) {
104 auto tg = cmNinjaTargetGenerator::New(target.get());
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),
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);
134 this->GetGlobalNinjaGenerator()->WriteBuild(
135 *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
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);
146 this->GetGlobalNinjaGenerator()->WriteBuild(
147 *this->GetGlobalNinjaGenerator()->GetDefaultFileStream(),
156 for (auto const& config : this->GetConfigNames()) {
157 this->WriteCustomCommandBuildStatements(config);
158 this->AdditionalCleanFiles(config);
162 // TODO: Picked up from cmLocalUnixMakefileGenerator3. Refactor it.
163 std::string cmLocalNinjaGenerator::GetTargetDirectory(
164 cmGeneratorTarget const* target) const
166 std::string dir = cmStrCat("CMakeFiles/", target->GetName());
175 // Non-virtual public methods.
177 const cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
180 return static_cast<const cmGlobalNinjaGenerator*>(
181 this->GetGlobalGenerator());
184 cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
186 return static_cast<cmGlobalNinjaGenerator*>(this->GetGlobalGenerator());
189 // Virtual protected methods.
191 std::string cmLocalNinjaGenerator::ConvertToIncludeReference(
192 std::string const& path, cmOutputConverter::OutputFormat format)
194 return this->ConvertToOutputFormat(path, format);
199 cmGeneratedFileStream& cmLocalNinjaGenerator::GetImplFileStream(
200 const std::string& config) const
202 return *this->GetGlobalNinjaGenerator()->GetImplFileStream(config);
205 cmGeneratedFileStream& cmLocalNinjaGenerator::GetCommonFileStream() const
207 return *this->GetGlobalNinjaGenerator()->GetCommonFileStream();
210 cmGeneratedFileStream& cmLocalNinjaGenerator::GetRulesFileStream() const
212 return *this->GetGlobalNinjaGenerator()->GetRulesFileStream();
215 const cmake* cmLocalNinjaGenerator::GetCMakeInstance() const
217 return this->GetGlobalGenerator()->GetCMakeInstance();
220 cmake* cmLocalNinjaGenerator::GetCMakeInstance()
222 return this->GetGlobalGenerator()->GetCMakeInstance();
225 void cmLocalNinjaGenerator::WriteBuildFileTop()
227 this->WriteProjectHeader(this->GetCommonFileStream());
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);
238 this->WriteNinjaRequiredVersion(this->GetCommonFileStream());
239 this->WriteNinjaConfigurationVariable(this->GetCommonFileStream(),
240 this->GetConfigNames().front());
242 this->WriteNinjaFilesInclusionCommon(this->GetCommonFileStream());
243 this->WriteNinjaWorkDir(this->GetCommonFileStream());
245 // For the rule file.
246 this->WriteProjectHeader(this->GetRulesFileStream());
249 void cmLocalNinjaGenerator::WriteProjectHeader(std::ostream& os)
251 cmGlobalNinjaGenerator::WriteDivider(os);
252 os << "# Project: " << this->GetProjectName() << '\n'
253 << "# Configurations: " << cmJoin(this->GetConfigNames(), ", ") << '\n';
254 cmGlobalNinjaGenerator::WriteDivider(os);
257 void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os)
259 // Default required version
260 std::string requiredVersion = cmGlobalNinjaGenerator::RequiredNinjaVersion();
262 // Ninja generator uses the 'console' pool if available (>= 1.5)
263 if (this->GetGlobalNinjaGenerator()->SupportsDirectConsole()) {
265 cmGlobalNinjaGenerator::RequiredNinjaVersionForConsolePool();
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")) {
275 cmGlobalNinjaGenerator::RequiredNinjaVersionForManifestRestat();
278 cmGlobalNinjaGenerator::WriteComment(
279 os, "Minimal version of Ninja required by this file");
280 os << "ninja_required_version = " << requiredVersion << "\n\n";
283 void cmLocalNinjaGenerator::WriteNinjaConfigurationVariable(
284 std::ostream& os, const std::string& config)
286 cmGlobalNinjaGenerator::WriteVariable(
287 os, "CONFIGURATION", config,
288 "Set configuration variable for custom commands.");
291 void cmLocalNinjaGenerator::WritePools(std::ostream& os)
293 cmGlobalNinjaGenerator::WriteDivider(os);
296 this->GetCMakeInstance()->GetState()->GetGlobalProperty("JOB_POOLS");
298 jobpools = this->GetMakefile()->GetDefinition("CMAKE_JOB_POOLS");
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('=');
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
312 cmSystemTools::Error("Invalid pool defined by property 'JOB_POOLS': " +
319 void cmLocalNinjaGenerator::WriteNinjaFilesInclusionConfig(std::ostream& os)
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.");
332 void cmLocalNinjaGenerator::WriteNinjaFilesInclusionCommon(std::ostream& os)
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.");
345 void cmLocalNinjaGenerator::WriteNinjaWorkDir(std::ostream& os)
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";
356 void cmLocalNinjaGenerator::WriteProcessedMakefile(std::ostream& os)
358 cmGlobalNinjaGenerator::WriteDivider(os);
359 os << "# Write statements declared in CMakeLists.txt:\n"
360 << "# " << this->Makefile->GetSafeDefinition("CMAKE_CURRENT_LIST_FILE")
362 if (this->IsRootMakefile()) {
363 os << "# Which is the root file.\n";
365 cmGlobalNinjaGenerator::WriteDivider(os);
369 void cmLocalNinjaGenerator::AppendTargetOutputs(cmGeneratorTarget* target,
370 cmNinjaDeps& outputs,
371 const std::string& config)
373 this->GetGlobalNinjaGenerator()->AppendTargetOutputs(target, outputs, config,
374 DependOnTargetArtifact);
377 void cmLocalNinjaGenerator::AppendTargetDepends(cmGeneratorTarget* target,
378 cmNinjaDeps& outputs,
379 const std::string& config,
380 const std::string& fileConfig,
381 cmNinjaTargetDepends depends)
383 this->GetGlobalNinjaGenerator()->AppendTargetDepends(target, outputs, config,
384 fileConfig, depends);
387 void cmLocalNinjaGenerator::AppendCustomCommandDeps(
388 cmCustomCommandGenerator const& ccg, cmNinjaDeps& ninjaDeps,
389 const std::string& config)
391 for (std::string const& i : ccg.GetDepends()) {
393 if (this->GetRealDependency(i, config, dep)) {
395 this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep));
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
405 std::string scriptPath;
407 scriptPath = target->GetSupportDirectory();
409 scriptPath = cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles");
411 scriptPath += this->GetGlobalNinjaGenerator()->ConfigDirectory(outputConfig);
412 cmSystemTools::MakeDirectory(scriptPath);
414 scriptPath += customStep;
415 if (this->GlobalGenerator->IsMultiConfig()) {
416 scriptPath += cmStrCat('-', commandConfig);
419 scriptPath += ".bat";
424 cmsys::ofstream script(scriptPath.c_str());
427 script << "@echo off\n";
430 script << "set -e\n\n";
433 for (auto const& i : cmdLines) {
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, "$$", "$");
440 script << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)"
443 script << cmd << '\n';
448 script << "goto :EOF\n\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%";
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
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;
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;
479 if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) {
480 std::string const scriptPath = this->WriteCommandScript(
481 cmdLines, outputConfig, commandConfig, customStep, target);
487 cmd += this->ConvertToOutputFormat(
488 this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath),
489 cmOutputConverter::SHELL);
491 // Add an unused argument based on script content so that Ninja
492 // knows when the command lines change.
494 cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
495 cmd += hash.HashFile(scriptPath).substr(0, 16);
500 std::ostringstream cmd;
501 for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li)
504 if (li != cmdLines.begin()) {
506 } else if (cmdLines.size() > 1) {
507 cmd << "cmd.exe /C \"";
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 << " )";
517 if (cmdLines.size() > 1) {
522 if (li != cmdLines.begin()) {
531 void cmLocalNinjaGenerator::AppendCustomCommandLines(
532 cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines)
534 auto* gg = this->GetGlobalNinjaGenerator();
536 if (ccg.GetNumberOfCommands() > 0) {
537 std::string wd = ccg.GetWorkingDirectory();
539 wd = this->GetCurrentBinaryDirectory();
542 std::ostringstream cdCmd;
544 std::string cdStr = "cd /D ";
546 std::string cdStr = "cd ";
549 << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
550 cmdLines.push_back(cdCmd.str());
553 std::string launcher = this->MakeCustomLauncher(ccg);
555 for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) {
556 std::string c = ccg.GetCommand(i);
560 cmdLines.push_back(launcher +
561 this->ConvertToOutputFormat(
563 gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI
564 : cmOutputConverter::SHELL));
566 std::string& cmd = cmdLines.back();
567 ccg.AppendArguments(i, cmd);
571 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
572 cmCustomCommand const* cc, const std::set<cmGeneratorTarget*>& targets,
573 const std::string& fileConfig)
575 cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
576 if (gg->SeenCustomCommand(cc, fileConfig)) {
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.
587 cmNinjaDeps orderOnlyDeps;
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.
596 // FIXME: This won't work in certain obscure scenarios involving indirect
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());
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;
617 const std::vector<std::string>& outputs = ccg.GetOutputs();
618 const std::vector<std::string>& byproducts = ccg.GetByproducts();
620 bool symbolic = false;
621 for (std::string const& output : outputs) {
622 if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
623 if (sf->GetPropertyAsBool("SYMBOLIC")) {
630 cmGlobalNinjaGenerator::CCOutputs ccOutputs(gg);
631 ccOutputs.Add(outputs);
632 ccOutputs.Add(byproducts);
634 std::string mainOutput = ccOutputs.ExplicitOuts[0];
636 cmNinjaDeps ninjaDeps;
637 this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig);
639 std::vector<std::string> cmdLines;
640 this->AppendCustomCommandLines(ccg, cmdLines);
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);
651 std::string customStep = cmSystemTools::GetFilenameName(mainOutput);
652 if (this->GlobalGenerator->IsMultiConfig()) {
654 customStep += fileConfig;
656 customStep += ccg.GetOutputConfig();
658 // Hash full path to make unique.
660 cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
661 customStep += hash.HashString(mainOutput).substr(0, 7);
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),
677 case cmPolicies::OLD:
679 case cmPolicies::REQUIRED_IF_USED:
680 case cmPolicies::REQUIRED_ALWAYS:
681 case cmPolicies::NEW:
682 depfile = ccg.GetInternalDepfile();
687 std::string comment = cmStrCat("Custom command for ", mainOutput);
688 gg->WriteCustomCommandBuild(
689 this->BuildCommandLine(cmdLines, ccg.GetOutputConfig(), fileConfig,
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));
699 bool cmLocalNinjaGenerator::HasUniqueByproducts(
700 std::vector<std::string> const& byproducts, cmListFileBacktrace const& bt)
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) {
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) {
724 bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs)
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) {
734 for (std::string const& byproduct : ccg.GetByproducts()) {
735 if (!allByproducts.insert(byproduct).second) {
744 std::string cmLocalNinjaGenerator::CreateUtilityOutput(
745 std::string const& targetName, std::vector<std::string> const& byproducts,
746 cmListFileBacktrace const& bt)
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,
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");
765 cmSystemTools::Error("Could not get source file entry for " + force);
768 this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName);
769 return cmStrCat(base, "$<CONFIG>"_s);
772 std::vector<cmCustomCommandGenerator>
773 cmLocalNinjaGenerator::MakeCustomCommandGenerators(
774 cmCustomCommand const& cc, std::string const& fileConfig)
776 cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator();
778 bool transformDepfile = false;
779 switch (cc.GetCMP0116Status()) {
780 case cmPolicies::WARN:
782 case cmPolicies::OLD:
784 case cmPolicies::REQUIRED_IF_USED:
785 case cmPolicies::REQUIRED_ALWAYS:
786 case cmPolicies::NEW:
787 transformDepfile = true;
791 // Start with the build graph's configuration.
792 std::vector<cmCustomCommandGenerator> ccgs;
793 ccgs.emplace_back(cc, fileConfig, this, transformDepfile);
795 // Consider adding cross configurations.
796 if (!gg->EnableCrossConfigBuild()) {
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) {
806 for (std::string const& byproduct : cc.GetByproducts()) {
807 if (cmGeneratorExpression::Find(byproduct) == std::string::npos) {
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);
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());
828 void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
829 cmGeneratorTarget* target)
831 CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>());
832 std::pair<CustomCommandTargetMap::iterator, bool> ins =
833 this->CustomCommandTargets.insert(v);
835 this->CustomCommands.push_back(cc);
837 ins.first->second.insert(target);
840 void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements(
841 const std::string& fileConfig)
843 for (cmCustomCommand const* customCommand : this->CustomCommands) {
844 auto i = this->CustomCommandTargets.find(customCommand);
845 assert(i != this->CustomCommandTargets.end());
847 this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig);
851 std::string cmLocalNinjaGenerator::MakeCustomLauncher(
852 cmCustomCommandGenerator const& ccg)
854 cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
856 if (!cmNonempty(property_value)) {
857 return std::string();
860 // Expand rule variables referenced in the given launcher command.
861 cmRulePlaceholderExpander::RuleVariables vars;
864 const std::vector<std::string>& outputs = ccg.GetOutputs();
865 if (!outputs.empty()) {
867 if (ccg.GetWorkingDirectory().empty()) {
868 output = this->MaybeRelativeToCurBinDir(output);
870 output = this->ConvertToOutputFormat(output, cmOutputConverter::SHELL);
872 vars.Output = output.c_str();
874 std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
875 this->CreateRulePlaceholderExpander());
877 std::string launcher = *property_value;
878 rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
879 if (!launcher.empty()) {
886 void cmLocalNinjaGenerator::AdditionalCleanFiles(const std::string& config)
888 if (cmValue prop_value =
889 this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
890 std::vector<std::string> cleanFiles;
892 cmExpandList(cmGeneratorExpression::Evaluate(*prop_value, this, config),
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);