/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 "cmGeneratedFileStream.h" |
23 | | #include "cmGeneratorExpression.h" |
24 | | #include "cmGeneratorTarget.h" |
25 | | #include "cmGlobalGenerator.h" |
26 | | #include "cmGlobalNinjaGenerator.h" |
27 | | #include "cmList.h" |
28 | | #include "cmListFileCache.h" |
29 | | #include "cmLocalGenerator.h" |
30 | | #include "cmMakefile.h" |
31 | | #include "cmMessageType.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 | ninjaDeps.push_back( |
419 | 0 | this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep)); |
420 | 0 | } |
421 | 0 | } |
422 | 0 | } |
423 | | |
424 | | std::string cmLocalNinjaGenerator::WriteCommandScript( |
425 | | std::vector<std::string> const& cmdLines, std::string const& outputConfig, |
426 | | std::string const& commandConfig, std::string const& customStep, |
427 | | cmGeneratorTarget const* target) const |
428 | 0 | { |
429 | 0 | std::string scriptPath; |
430 | 0 | if (target) { |
431 | 0 | scriptPath = target->GetSupportDirectory(); |
432 | 0 | } else { |
433 | 0 | scriptPath = cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles"); |
434 | 0 | } |
435 | 0 | scriptPath += this->GetGlobalNinjaGenerator()->ConfigDirectory(outputConfig); |
436 | 0 | cmSystemTools::MakeDirectory(scriptPath); |
437 | 0 | scriptPath += '/'; |
438 | 0 | scriptPath += customStep; |
439 | 0 | if (this->GlobalGenerator->IsMultiConfig()) { |
440 | 0 | scriptPath += cmStrCat('-', commandConfig); |
441 | 0 | } |
442 | | #ifdef _WIN32 |
443 | | scriptPath += ".bat"; |
444 | | #else |
445 | 0 | scriptPath += ".sh"; |
446 | 0 | #endif |
447 | |
|
448 | 0 | cmsys::ofstream script(scriptPath.c_str()); |
449 | |
|
450 | | #ifdef _WIN32 |
451 | | script << "@echo off\n"; |
452 | | int line = 1; |
453 | | #else |
454 | 0 | script << "set -e\n\n"; |
455 | 0 | #endif |
456 | |
|
457 | 0 | for (auto const& i : cmdLines) { |
458 | 0 | std::string cmd = i; |
459 | | // The command line was built assuming it would be written to |
460 | | // the build.ninja file, so it uses '$$' for '$'. Remove this |
461 | | // for the raw shell script. |
462 | 0 | cmSystemTools::ReplaceString(cmd, "$$", "$"); |
463 | | #ifdef _WIN32 |
464 | | script << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)" |
465 | | << '\n'; |
466 | | #else |
467 | 0 | script << cmd << '\n'; |
468 | 0 | #endif |
469 | 0 | } |
470 | |
|
471 | | #ifdef _WIN32 |
472 | | script << "goto :EOF\n\n" |
473 | | ":ABORT\n" |
474 | | "set ERROR_CODE=%ERRORLEVEL%\n" |
475 | | "echo Batch file failed at line %FAIL_LINE% " |
476 | | "with errorcode %ERRORLEVEL%\n" |
477 | | "exit /b %ERROR_CODE%"; |
478 | | #endif |
479 | |
|
480 | 0 | return scriptPath; |
481 | 0 | } |
482 | | |
483 | | #ifdef _WIN32 |
484 | | namespace { |
485 | | bool RuleNeedsCMD(std::string const& cmd) |
486 | | { |
487 | | std::vector<std::string> args; |
488 | | cmSystemTools::ParseWindowsCommandLine(cmd.c_str(), args); |
489 | | auto it = std::find_if(args.cbegin(), args.cend(), |
490 | | [](std::string const& arg) -> bool { |
491 | | // FIXME: Detect more windows shell operators. |
492 | | return cmHasPrefix(arg, '>'); |
493 | | }); |
494 | | return it != args.cend(); |
495 | | } |
496 | | } |
497 | | #endif |
498 | | |
499 | | std::string cmLocalNinjaGenerator::BuildCommandLine( |
500 | | std::vector<std::string> const& cmdLines, std::string const& outputConfig, |
501 | | std::string const& commandConfig, std::string const& customStep, |
502 | | cmGeneratorTarget const* target) const |
503 | 0 | { |
504 | | // If we have no commands but we need to build a command anyway, use noop. |
505 | | // This happens when building a POST_BUILD value for link targets that |
506 | | // don't use POST_BUILD. |
507 | 0 | if (cmdLines.empty()) { |
508 | 0 | return cmGlobalNinjaGenerator::SHELL_NOOP; |
509 | 0 | } |
510 | | |
511 | | // If this is a custom step then we will have no '$VAR' ninja placeholders. |
512 | | // This means we can deal with long command sequences by writing to a script. |
513 | | // Do this if the command lines are on the scale of the OS limit. |
514 | 0 | if (!customStep.empty()) { |
515 | 0 | size_t cmdLinesTotal = 0; |
516 | 0 | for (std::string const& cmd : cmdLines) { |
517 | 0 | cmdLinesTotal += cmd.length() + 6; |
518 | 0 | } |
519 | 0 | if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) { |
520 | 0 | std::string const scriptPath = this->WriteCommandScript( |
521 | 0 | cmdLines, outputConfig, commandConfig, customStep, target); |
522 | 0 | std::string cmd |
523 | 0 | #ifndef _WIN32 |
524 | 0 | = "/bin/sh " |
525 | 0 | #endif |
526 | 0 | ; |
527 | 0 | cmd += this->ConvertToOutputFormat( |
528 | 0 | this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath), |
529 | 0 | cmOutputConverter::SHELL); |
530 | | |
531 | | // Add an unused argument based on script content so that Ninja |
532 | | // knows when the command lines change. |
533 | 0 | cmd += " "; |
534 | 0 | cmCryptoHash hash(cmCryptoHash::AlgoSHA256); |
535 | 0 | cmd += hash.HashFile(scriptPath).substr(0, 16); |
536 | 0 | return cmd; |
537 | 0 | } |
538 | 0 | } |
539 | | |
540 | 0 | std::ostringstream cmd; |
541 | | #ifdef _WIN32 |
542 | | cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator(); |
543 | | bool const needCMD = |
544 | | cmdLines.size() > 1 || (customStep.empty() && RuleNeedsCMD(cmdLines[0])); |
545 | | for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) { |
546 | | if (li != cmdLines.begin()) { |
547 | | cmd << " && "; |
548 | | } else if (needCMD) { |
549 | | cmd << gg->GetComspec() << " /C \""; |
550 | | } |
551 | | // Put current cmdLine in brackets if it contains "||" because it has |
552 | | // higher precedence than "&&" in cmd.exe |
553 | | if (li->find("||") != std::string::npos) { |
554 | | cmd << "( " << *li << " )"; |
555 | | } else { |
556 | | cmd << *li; |
557 | | } |
558 | | } |
559 | | if (needCMD) { |
560 | | cmd << "\""; |
561 | | } |
562 | | #else |
563 | 0 | for (auto li = cmdLines.begin(); li != cmdLines.end(); ++li) { |
564 | 0 | if (li != cmdLines.begin()) { |
565 | 0 | cmd << " && "; |
566 | 0 | } |
567 | 0 | cmd << *li; |
568 | 0 | } |
569 | 0 | #endif |
570 | 0 | return cmd.str(); |
571 | 0 | } |
572 | | |
573 | | void cmLocalNinjaGenerator::AppendCustomCommandLines( |
574 | | cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines) |
575 | 0 | { |
576 | 0 | auto* gg = this->GetGlobalNinjaGenerator(); |
577 | |
|
578 | 0 | if (ccg.GetNumberOfCommands() > 0) { |
579 | 0 | std::string wd = ccg.GetWorkingDirectory(); |
580 | 0 | if (wd.empty()) { |
581 | 0 | wd = this->GetCurrentBinaryDirectory(); |
582 | 0 | } |
583 | |
|
584 | 0 | std::ostringstream cdCmd; |
585 | | #ifdef _WIN32 |
586 | | std::string cdStr = "cd /D "; |
587 | | #else |
588 | 0 | std::string cdStr = "cd "; |
589 | 0 | #endif |
590 | 0 | cdCmd << cdStr |
591 | 0 | << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL); |
592 | 0 | cmdLines.push_back(cdCmd.str()); |
593 | 0 | } |
594 | |
|
595 | 0 | std::string launcher = this->MakeCustomLauncher(ccg); |
596 | |
|
597 | 0 | for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) { |
598 | 0 | std::string c = ccg.GetCommand(i); |
599 | 0 | if (c.empty()) { |
600 | 0 | continue; |
601 | 0 | } |
602 | 0 | cmdLines.push_back(launcher + |
603 | 0 | this->ConvertToOutputFormat( |
604 | 0 | c, |
605 | 0 | gg->IsMultiConfig() ? cmOutputConverter::NINJAMULTI |
606 | 0 | : cmOutputConverter::SHELL)); |
607 | |
|
608 | 0 | std::string& cmd = cmdLines.back(); |
609 | 0 | ccg.AppendArguments(i, cmd); |
610 | 0 | } |
611 | 0 | } |
612 | | |
613 | | void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement( |
614 | | cmCustomCommand const* cc, std::set<cmGeneratorTarget*> const& targets, |
615 | | std::string const& fileConfig) |
616 | 0 | { |
617 | 0 | cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator(); |
618 | 0 | if (gg->SeenCustomCommand(cc, fileConfig)) { |
619 | 0 | return; |
620 | 0 | } |
621 | | |
622 | 0 | auto ccgs = this->MakeCustomCommandGenerators(*cc, fileConfig); |
623 | 0 | for (cmCustomCommandGenerator const& ccg : ccgs) { |
624 | 0 | if (ccg.GetOutputs().empty() && ccg.GetByproducts().empty()) { |
625 | | // Generator expressions evaluate to no output for this config. |
626 | 0 | continue; |
627 | 0 | } |
628 | | |
629 | 0 | std::unordered_set<std::string> orderOnlyDeps; |
630 | |
|
631 | 0 | if (!cc->GetDependsExplicitOnly()) { |
632 | | // A custom command may appear on multiple targets. However, some build |
633 | | // systems exist where the target dependencies on some of the targets are |
634 | | // overspecified, leading to a dependency cycle. If we assume all target |
635 | | // dependencies are a superset of the true target dependencies for this |
636 | | // custom command, we can take the set intersection of all target |
637 | | // dependencies to obtain a correct dependency list. |
638 | | // |
639 | | // FIXME: This won't work in certain obscure scenarios involving indirect |
640 | | // dependencies. |
641 | 0 | auto j = targets.begin(); |
642 | 0 | assert(j != targets.end()); |
643 | 0 | this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure( |
644 | 0 | *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1); |
645 | 0 | ++j; |
646 | |
|
647 | 0 | for (; j != targets.end(); ++j) { |
648 | 0 | std::unordered_set<std::string> jDeps; |
649 | 0 | this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure( |
650 | 0 | *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1); |
651 | 0 | cm::erase_if(orderOnlyDeps, [&jDeps](std::string const& dep) { |
652 | 0 | return jDeps.find(dep) == jDeps.end(); |
653 | 0 | }); |
654 | 0 | } |
655 | 0 | } |
656 | |
|
657 | 0 | std::vector<std::string> const& outputs = ccg.GetOutputs(); |
658 | 0 | std::vector<std::string> const& byproducts = ccg.GetByproducts(); |
659 | |
|
660 | 0 | bool symbolic = false; |
661 | 0 | for (std::string const& output : outputs) { |
662 | 0 | if (cmSourceFile* sf = this->Makefile->GetSource(output)) { |
663 | 0 | if (sf->GetPropertyAsBool("SYMBOLIC")) { |
664 | 0 | symbolic = true; |
665 | 0 | break; |
666 | 0 | } |
667 | 0 | } |
668 | 0 | } |
669 | |
|
670 | 0 | cmGlobalNinjaGenerator::CCOutputs ccOutputs(gg); |
671 | 0 | ccOutputs.Add(outputs); |
672 | 0 | ccOutputs.Add(byproducts); |
673 | |
|
674 | 0 | std::string mainOutput = ccOutputs.ExplicitOuts[0]; |
675 | |
|
676 | 0 | cmNinjaDeps ninjaDeps; |
677 | 0 | this->AppendCustomCommandDeps(ccg, ninjaDeps, fileConfig); |
678 | |
|
679 | 0 | std::vector<std::string> cmdLines; |
680 | 0 | this->AppendCustomCommandLines(ccg, cmdLines); |
681 | |
|
682 | 0 | cmNinjaDeps sortedOrderOnlyDeps(orderOnlyDeps.begin(), |
683 | 0 | orderOnlyDeps.end()); |
684 | 0 | std::sort(sortedOrderOnlyDeps.begin(), sortedOrderOnlyDeps.end()); |
685 | |
|
686 | 0 | if (cmdLines.empty()) { |
687 | 0 | cmNinjaBuild build("phony"); |
688 | 0 | build.Comment = cmStrCat("Phony custom command for ", mainOutput); |
689 | 0 | build.Outputs = std::move(ccOutputs.ExplicitOuts); |
690 | 0 | build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts); |
691 | 0 | build.ExplicitDeps = std::move(ninjaDeps); |
692 | 0 | build.OrderOnlyDeps = std::move(sortedOrderOnlyDeps); |
693 | 0 | gg->WriteBuild(this->GetImplFileStream(fileConfig), build); |
694 | 0 | } else { |
695 | 0 | std::string customStep = cmSystemTools::GetFilenameName(mainOutput); |
696 | 0 | if (this->GlobalGenerator->IsMultiConfig()) { |
697 | 0 | customStep += '-'; |
698 | 0 | customStep += fileConfig; |
699 | 0 | customStep += '-'; |
700 | 0 | customStep += ccg.GetOutputConfig(); |
701 | 0 | } |
702 | | // Hash full path to make unique. |
703 | 0 | customStep += '-'; |
704 | 0 | cmCryptoHash hash(cmCryptoHash::AlgoSHA256); |
705 | 0 | customStep += hash.HashString(mainOutput).substr(0, 7); |
706 | |
|
707 | 0 | std::string depfile = ccg.GetDepfile(); |
708 | 0 | if (!depfile.empty()) { |
709 | 0 | switch (cc->GetCMP0116Status()) { |
710 | 0 | case cmPolicies::WARN: |
711 | 0 | if (this->GetCurrentBinaryDirectory() != |
712 | 0 | this->GetBinaryDirectory() || |
713 | 0 | this->Makefile->PolicyOptionalWarningEnabled( |
714 | 0 | "CMAKE_POLICY_WARNING_CMP0116")) { |
715 | 0 | this->GetCMakeInstance()->IssueMessage( |
716 | 0 | MessageType::AUTHOR_WARNING, |
717 | 0 | cmPolicies::GetPolicyWarning(cmPolicies::CMP0116), |
718 | 0 | cc->GetBacktrace()); |
719 | 0 | } |
720 | 0 | CM_FALLTHROUGH; |
721 | 0 | case cmPolicies::OLD: |
722 | 0 | break; |
723 | 0 | case cmPolicies::NEW: |
724 | 0 | depfile = ccg.GetInternalDepfile(); |
725 | 0 | break; |
726 | 0 | } |
727 | 0 | } |
728 | | |
729 | 0 | std::string comment = cmStrCat("Custom command for ", mainOutput); |
730 | 0 | gg->WriteCustomCommandBuild( |
731 | 0 | this->BuildCommandLine(cmdLines, ccg.GetOutputConfig(), fileConfig, |
732 | 0 | customStep), |
733 | 0 | this->ConstructComment(ccg), comment, depfile, cc->GetJobPool(), |
734 | 0 | cc->GetUsesTerminal(), |
735 | 0 | /*restat*/ !symbolic || !byproducts.empty(), fileConfig, |
736 | 0 | std::move(ccOutputs), std::move(ninjaDeps), |
737 | 0 | std::move(sortedOrderOnlyDeps)); |
738 | 0 | } |
739 | 0 | } |
740 | 0 | } |
741 | | |
742 | | bool cmLocalNinjaGenerator::HasUniqueByproducts( |
743 | | std::vector<std::string> const& byproducts, cmListFileBacktrace const& bt) |
744 | 0 | { |
745 | 0 | cmGeneratorExpression ge(*this->GetCMakeInstance(), bt); |
746 | 0 | for (std::string const& p : byproducts) { |
747 | 0 | if (cmGeneratorExpression::Find(p) == std::string::npos) { |
748 | 0 | return false; |
749 | 0 | } |
750 | 0 | std::set<std::string> seen; |
751 | 0 | std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(p); |
752 | 0 | for (std::string const& config : this->GetConfigNames()) { |
753 | 0 | for (std::string const& b : |
754 | 0 | this->ExpandCustomCommandOutputPaths(*cge, config)) { |
755 | 0 | if (!seen.insert(b).second) { |
756 | 0 | return false; |
757 | 0 | } |
758 | 0 | } |
759 | 0 | } |
760 | 0 | } |
761 | 0 | return true; |
762 | 0 | } |
763 | | |
764 | | namespace { |
765 | | bool HasUniqueOutputs(std::vector<cmCustomCommandGenerator> const& ccgs) |
766 | 0 | { |
767 | 0 | std::set<std::string> allOutputs; |
768 | 0 | std::set<std::string> allByproducts; |
769 | 0 | for (cmCustomCommandGenerator const& ccg : ccgs) { |
770 | 0 | for (std::string const& output : ccg.GetOutputs()) { |
771 | 0 | if (!allOutputs.insert(output).second) { |
772 | 0 | return false; |
773 | 0 | } |
774 | 0 | } |
775 | 0 | for (std::string const& byproduct : ccg.GetByproducts()) { |
776 | 0 | if (!allByproducts.insert(byproduct).second) { |
777 | 0 | return false; |
778 | 0 | } |
779 | 0 | } |
780 | 0 | } |
781 | 0 | return true; |
782 | 0 | } |
783 | | } |
784 | | |
785 | | std::string cmLocalNinjaGenerator::CreateUtilityOutput( |
786 | | std::string const& targetName, std::vector<std::string> const& byproducts, |
787 | | cmListFileBacktrace const& bt) |
788 | 0 | { |
789 | | // In Ninja Multi-Config, we can only produce cross-config utility |
790 | | // commands if all byproducts are per-config. |
791 | 0 | if (!this->GetGlobalGenerator()->IsMultiConfig() || |
792 | 0 | !this->HasUniqueByproducts(byproducts, bt)) { |
793 | 0 | return this->cmLocalGenerator::CreateUtilityOutput(targetName, byproducts, |
794 | 0 | bt); |
795 | 0 | } |
796 | | |
797 | 0 | std::string const base = cmStrCat(this->GetCurrentBinaryDirectory(), |
798 | 0 | "/CMakeFiles/", targetName, '-'); |
799 | | // The output is not actually created so mark it symbolic. |
800 | 0 | for (std::string const& config : this->GetConfigNames()) { |
801 | 0 | std::string const force = cmStrCat(base, config); |
802 | 0 | if (cmSourceFile* sf = this->Makefile->GetOrCreateGeneratedSource(force)) { |
803 | 0 | sf->SetProperty("SYMBOLIC", "1"); |
804 | 0 | } else { |
805 | 0 | cmSystemTools::Error("Could not get source file entry for " + force); |
806 | 0 | } |
807 | 0 | } |
808 | 0 | this->GetGlobalNinjaGenerator()->AddPerConfigUtilityTarget(targetName); |
809 | 0 | return cmStrCat(base, "$<CONFIG>"_s); |
810 | 0 | } |
811 | | |
812 | | std::vector<cmCustomCommandGenerator> |
813 | | cmLocalNinjaGenerator::MakeCustomCommandGenerators( |
814 | | cmCustomCommand const& cc, std::string const& fileConfig) |
815 | 0 | { |
816 | 0 | cmGlobalNinjaGenerator const* gg = this->GetGlobalNinjaGenerator(); |
817 | |
|
818 | 0 | bool transformDepfile = false; |
819 | 0 | switch (cc.GetCMP0116Status()) { |
820 | 0 | case cmPolicies::WARN: |
821 | 0 | CM_FALLTHROUGH; |
822 | 0 | case cmPolicies::OLD: |
823 | 0 | break; |
824 | 0 | case cmPolicies::NEW: |
825 | 0 | transformDepfile = true; |
826 | 0 | break; |
827 | 0 | } |
828 | | |
829 | | // Start with the build graph's configuration. |
830 | 0 | std::vector<cmCustomCommandGenerator> ccgs; |
831 | 0 | ccgs.emplace_back(cc, fileConfig, this, transformDepfile); |
832 | | |
833 | | // Consider adding cross configurations. |
834 | 0 | if (!gg->EnableCrossConfigBuild()) { |
835 | 0 | return ccgs; |
836 | 0 | } |
837 | | |
838 | | // Outputs and byproducts must be expressed using generator expressions. |
839 | 0 | for (std::string const& output : cc.GetOutputs()) { |
840 | 0 | if (cmGeneratorExpression::Find(output) == std::string::npos) { |
841 | 0 | return ccgs; |
842 | 0 | } |
843 | 0 | } |
844 | 0 | for (std::string const& byproduct : cc.GetByproducts()) { |
845 | 0 | if (cmGeneratorExpression::Find(byproduct) == std::string::npos) { |
846 | 0 | return ccgs; |
847 | 0 | } |
848 | 0 | } |
849 | | |
850 | | // Tentatively add the other cross configurations. |
851 | 0 | for (std::string const& config : gg->GetCrossConfigs(fileConfig)) { |
852 | 0 | if (fileConfig != config) { |
853 | 0 | ccgs.emplace_back(cc, fileConfig, this, transformDepfile, config); |
854 | 0 | } |
855 | 0 | } |
856 | | |
857 | | // If outputs and byproducts are not unique to each configuration, |
858 | | // drop the cross configurations. |
859 | 0 | if (!HasUniqueOutputs(ccgs)) { |
860 | 0 | ccgs.erase(ccgs.begin() + 1, ccgs.end()); |
861 | 0 | } |
862 | |
|
863 | 0 | return ccgs; |
864 | 0 | } |
865 | | |
866 | | void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc, |
867 | | cmGeneratorTarget* target) |
868 | 0 | { |
869 | 0 | CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>()); |
870 | 0 | std::pair<CustomCommandTargetMap::iterator, bool> ins = |
871 | 0 | this->CustomCommandTargets.insert(v); |
872 | 0 | if (ins.second) { |
873 | 0 | this->CustomCommands.push_back(cc); |
874 | 0 | } |
875 | 0 | ins.first->second.insert(target); |
876 | 0 | } |
877 | | |
878 | | void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements( |
879 | | std::string const& fileConfig) |
880 | 0 | { |
881 | 0 | for (cmCustomCommand const* customCommand : this->CustomCommands) { |
882 | 0 | auto i = this->CustomCommandTargets.find(customCommand); |
883 | 0 | assert(i != this->CustomCommandTargets.end()); |
884 | |
|
885 | 0 | this->WriteCustomCommandBuildStatement(i->first, i->second, fileConfig); |
886 | 0 | } |
887 | 0 | } |
888 | | |
889 | | std::string cmLocalNinjaGenerator::MakeCustomLauncher( |
890 | | cmCustomCommandGenerator const& ccg) |
891 | 0 | { |
892 | 0 | cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM"); |
893 | |
|
894 | 0 | if (!cmNonempty(property_value)) { |
895 | 0 | return std::string(); |
896 | 0 | } |
897 | | |
898 | | // Expand rule variables referenced in the given launcher command. |
899 | 0 | cmRulePlaceholderExpander::RuleVariables vars; |
900 | |
|
901 | 0 | std::string output; |
902 | 0 | std::vector<std::string> const& outputs = ccg.GetOutputs(); |
903 | 0 | for (size_t i = 0; i < outputs.size(); ++i) { |
904 | 0 | output = cmStrCat(output, |
905 | 0 | this->ConvertToOutputFormat( |
906 | 0 | ccg.GetWorkingDirectory().empty() |
907 | 0 | ? this->MaybeRelativeToCurBinDir(outputs[i]) |
908 | 0 | : outputs[i], |
909 | 0 | cmOutputConverter::SHELL)); |
910 | 0 | if (i != outputs.size() - 1) { |
911 | 0 | output = cmStrCat(output, ','); |
912 | 0 | } |
913 | 0 | } |
914 | 0 | vars.Output = output.c_str(); |
915 | 0 | vars.Role = ccg.GetCC().GetRole().c_str(); |
916 | 0 | vars.CMTargetName = ccg.GetCC().GetTarget().c_str(); |
917 | 0 | vars.Config = ccg.GetOutputConfig().c_str(); |
918 | |
|
919 | 0 | auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander(); |
920 | |
|
921 | 0 | std::string launcher = *property_value; |
922 | 0 | rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars); |
923 | 0 | if (!launcher.empty()) { |
924 | 0 | launcher += " "; |
925 | 0 | } |
926 | |
|
927 | 0 | return launcher; |
928 | 0 | } |
929 | | |
930 | | void cmLocalNinjaGenerator::AdditionalCleanFiles(std::string const& config) |
931 | 0 | { |
932 | 0 | if (cmValue prop_value = |
933 | 0 | this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) { |
934 | 0 | cmList cleanFiles{ cmGeneratorExpression::Evaluate(*prop_value, this, |
935 | 0 | config) }; |
936 | 0 | std::string const& binaryDir = this->GetCurrentBinaryDirectory(); |
937 | 0 | cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator(); |
938 | 0 | for (auto const& cleanFile : cleanFiles) { |
939 | | // Support relative paths |
940 | 0 | gg->AddAdditionalCleanFile( |
941 | 0 | cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config); |
942 | 0 | } |
943 | 0 | } |
944 | 0 | } |