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