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