1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/ninja_target_writer.h"
6
7 #include <sstream>
8
9 #include "base/files/file_util.h"
10 #include "base/strings/string_util.h"
11 #include "gn/c_substitution_type.h"
12 #include "gn/config_values_extractors.h"
13 #include "gn/err.h"
14 #include "gn/escape.h"
15 #include "gn/filesystem_utils.h"
16 #include "gn/general_tool.h"
17 #include "gn/ninja_action_target_writer.h"
18 #include "gn/ninja_binary_target_writer.h"
19 #include "gn/ninja_bundle_data_target_writer.h"
20 #include "gn/ninja_copy_target_writer.h"
21 #include "gn/ninja_create_bundle_target_writer.h"
22 #include "gn/ninja_generated_file_target_writer.h"
23 #include "gn/ninja_group_target_writer.h"
24 #include "gn/ninja_target_command_util.h"
25 #include "gn/ninja_utils.h"
26 #include "gn/output_file.h"
27 #include "gn/rust_substitution_type.h"
28 #include "gn/scheduler.h"
29 #include "gn/string_output_buffer.h"
30 #include "gn/string_utils.h"
31 #include "gn/substitution_writer.h"
32 #include "gn/target.h"
33 #include "gn/trace.h"
34
NinjaTargetWriter(const Target * target,std::ostream & out)35 NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out)
36 : settings_(target->settings()),
37 target_(target),
38 out_(out),
39 path_output_(settings_->build_settings()->build_dir(),
40 settings_->build_settings()->root_path_utf8(),
41 ESCAPE_NINJA) {}
42
SetResolvedTargetData(ResolvedTargetData * resolved)43 void NinjaTargetWriter::SetResolvedTargetData(ResolvedTargetData* resolved) {
44 if (resolved) {
45 resolved_owned_.reset();
46 resolved_ptr_ = resolved;
47 }
48 }
49
SetNinjaOutputs(std::vector<OutputFile> * ninja_outputs)50 void NinjaTargetWriter::SetNinjaOutputs(
51 std::vector<OutputFile>* ninja_outputs) {
52 ninja_outputs_ = ninja_outputs;
53 }
54
GetResolvedTargetData()55 ResolvedTargetData* NinjaTargetWriter::GetResolvedTargetData() {
56 return const_cast<ResolvedTargetData*>(&resolved());
57 }
58
resolved() const59 const ResolvedTargetData& NinjaTargetWriter::resolved() const {
60 if (!resolved_ptr_) {
61 resolved_owned_ = std::make_unique<ResolvedTargetData>();
62 resolved_ptr_ = resolved_owned_.get();
63 }
64 return *resolved_ptr_;
65 }
66
67 NinjaTargetWriter::~NinjaTargetWriter() = default;
68
WriteOutput(const OutputFile & output) const69 void NinjaTargetWriter::WriteOutput(const OutputFile& output) const {
70 path_output_.WriteFile(out_, output);
71 if (ninja_outputs_)
72 ninja_outputs_->push_back(output);
73 }
74
WriteOutput(OutputFile && output) const75 void NinjaTargetWriter::WriteOutput(OutputFile&& output) const {
76 path_output_.WriteFile(out_, output);
77 if (ninja_outputs_)
78 ninja_outputs_->push_back(std::move(output));
79 }
80
WriteOutputs(const std::vector<OutputFile> & outputs) const81 void NinjaTargetWriter::WriteOutputs(
82 const std::vector<OutputFile>& outputs) const {
83 path_output_.WriteFiles(out_, outputs);
84 if (ninja_outputs_)
85 ninja_outputs_->insert(ninja_outputs_->end(), outputs.begin(),
86 outputs.end());
87 }
88
WriteOutputs(std::vector<OutputFile> && outputs) const89 void NinjaTargetWriter::WriteOutputs(std::vector<OutputFile>&& outputs) const {
90 path_output_.WriteFiles(out_, outputs);
91 if (ninja_outputs_) {
92 for (auto& output : outputs)
93 ninja_outputs_->push_back(std::move(output));
94 }
95 }
96
97 // static
RunAndWriteFile(const Target * target,ResolvedTargetData * resolved,std::vector<OutputFile> * ninja_outputs)98 std::string NinjaTargetWriter::RunAndWriteFile(
99 const Target* target,
100 ResolvedTargetData* resolved,
101 std::vector<OutputFile>* ninja_outputs) {
102 const Settings* settings = target->settings();
103
104 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE_NINJA,
105 target->label().GetUserVisibleName(false));
106 trace.SetToolchain(settings->toolchain_label());
107
108 if (g_scheduler->verbose_logging())
109 g_scheduler->Log("Computing", target->label().GetUserVisibleName(true));
110
111 // It's ridiculously faster to write to a string and then write that to
112 // disk in one operation than to use an fstream here.
113 StringOutputBuffer storage;
114 std::ostream rules(&storage);
115
116 // Call out to the correct sub-type of writer. Binary targets need to be
117 // written to separate files for compiler flag scoping, but other target
118 // types can have their rules coalesced.
119 //
120 // In ninja, if a rule uses a variable (like $include_dirs) it will use
121 // the value set by indenting it under the build line or it takes the value
122 // from the end of the invoking scope (otherwise the current file). It does
123 // not copy the value from what it was when the build line was encountered.
124 // To avoid writing lots of duplicate rules for defines and cflags, etc. on
125 // each source file build line, we use separate .ninja files with the shared
126 // variables set at the top.
127 //
128 // Groups and actions don't use this type of flag, they make unique rules
129 // or write variables scoped under each build line. As a result, they don't
130 // need the separate files.
131 bool needs_file_write = false;
132 if (target->output_type() == Target::BUNDLE_DATA) {
133 NinjaBundleDataTargetWriter writer(target, rules);
134 writer.SetResolvedTargetData(resolved);
135 writer.SetNinjaOutputs(ninja_outputs);
136 writer.Run();
137 } else if (target->output_type() == Target::CREATE_BUNDLE) {
138 NinjaCreateBundleTargetWriter writer(target, rules);
139 writer.SetResolvedTargetData(resolved);
140 writer.SetNinjaOutputs(ninja_outputs);
141 writer.Run();
142 } else if (target->output_type() == Target::COPY_FILES) {
143 NinjaCopyTargetWriter writer(target, rules);
144 writer.SetResolvedTargetData(resolved);
145 writer.SetNinjaOutputs(ninja_outputs);
146 writer.Run();
147 } else if (target->output_type() == Target::ACTION ||
148 target->output_type() == Target::ACTION_FOREACH) {
149 NinjaActionTargetWriter writer(target, rules);
150 writer.SetResolvedTargetData(resolved);
151 writer.SetNinjaOutputs(ninja_outputs);
152 writer.Run();
153 } else if (target->output_type() == Target::GROUP) {
154 NinjaGroupTargetWriter writer(target, rules);
155 writer.SetResolvedTargetData(resolved);
156 writer.SetNinjaOutputs(ninja_outputs);
157 writer.Run();
158 } else if (target->output_type() == Target::GENERATED_FILE) {
159 NinjaGeneratedFileTargetWriter writer(target, rules);
160 writer.SetResolvedTargetData(resolved);
161 writer.SetNinjaOutputs(ninja_outputs);
162 writer.Run();
163 } else if (target->IsBinary()) {
164 needs_file_write = true;
165 NinjaBinaryTargetWriter writer(target, rules);
166 writer.SetResolvedTargetData(resolved);
167 writer.SetNinjaOutputs(ninja_outputs);
168 writer.Run();
169 } else {
170 CHECK(0) << "Output type of target not handled.";
171 }
172
173 if (needs_file_write) {
174 // Write the ninja file.
175 SourceFile ninja_file = GetNinjaFileForTarget(target);
176 base::FilePath full_ninja_file =
177 settings->build_settings()->GetFullPath(ninja_file);
178 storage.WriteToFileIfChanged(full_ninja_file, nullptr);
179
180 EscapeOptions options;
181 options.mode = ESCAPE_NINJA;
182
183 // Return the subninja command to load the rules file.
184 std::string result = "subninja ";
185 result.append(EscapeString(
186 OutputFile(target->settings()->build_settings(), ninja_file).value(),
187 options, nullptr));
188 result.push_back('\n');
189 return result;
190 }
191
192 // No separate file required, just return the rules.
193 return storage.str();
194 }
195
WriteEscapedSubstitution(const Substitution * type)196 void NinjaTargetWriter::WriteEscapedSubstitution(const Substitution* type) {
197 EscapeOptions opts;
198 opts.mode = ESCAPE_NINJA;
199
200 out_ << type->ninja_name << " = ";
201 EscapeStringToStream(
202 out_, SubstitutionWriter::GetTargetSubstitution(target_, type), opts);
203 out_ << std::endl;
204 }
205
WriteSharedVars(const SubstitutionBits & bits)206 void NinjaTargetWriter::WriteSharedVars(const SubstitutionBits& bits) {
207 bool written_anything = false;
208
209 // Target label.
210 if (bits.used.count(&SubstitutionLabel)) {
211 WriteEscapedSubstitution(&SubstitutionLabel);
212 written_anything = true;
213 }
214
215 // Target label name.
216 if (bits.used.count(&SubstitutionLabelName)) {
217 WriteEscapedSubstitution(&SubstitutionLabelName);
218 written_anything = true;
219 }
220
221 // Target label name without toolchain.
222 if (bits.used.count(&SubstitutionLabelNoToolchain)) {
223 WriteEscapedSubstitution(&SubstitutionLabelNoToolchain);
224 written_anything = true;
225 }
226
227 // Root gen dir.
228 if (bits.used.count(&SubstitutionRootGenDir)) {
229 WriteEscapedSubstitution(&SubstitutionRootGenDir);
230 written_anything = true;
231 }
232
233 // Root out dir.
234 if (bits.used.count(&SubstitutionRootOutDir)) {
235 WriteEscapedSubstitution(&SubstitutionRootOutDir);
236 written_anything = true;
237 }
238
239 // Target gen dir.
240 if (bits.used.count(&SubstitutionTargetGenDir)) {
241 WriteEscapedSubstitution(&SubstitutionTargetGenDir);
242 written_anything = true;
243 }
244
245 // Target out dir.
246 if (bits.used.count(&SubstitutionTargetOutDir)) {
247 WriteEscapedSubstitution(&SubstitutionTargetOutDir);
248 written_anything = true;
249 }
250
251 // Target output name.
252 if (bits.used.count(&SubstitutionTargetOutputName)) {
253 WriteEscapedSubstitution(&SubstitutionTargetOutputName);
254 written_anything = true;
255 }
256
257 // If we wrote any vars, separate them from the rest of the file that follows
258 // with a blank line.
259 if (written_anything)
260 out_ << std::endl;
261 }
262
WriteCCompilerVars(const SubstitutionBits & bits,bool indent,bool respect_source_used)263 void NinjaTargetWriter::WriteCCompilerVars(const SubstitutionBits& bits,
264 bool indent,
265 bool respect_source_used) {
266 // Defines.
267 if (bits.used.count(&CSubstitutionDefines)) {
268 if (indent)
269 out_ << " ";
270 out_ << CSubstitutionDefines.ninja_name << " =";
271 RecursiveTargetConfigToStream<std::string>(kRecursiveWriterSkipDuplicates,
272 target_, &ConfigValues::defines,
273 DefineWriter(), out_);
274 out_ << std::endl;
275 }
276
277 // Framework search path.
278 if (bits.used.count(&CSubstitutionFrameworkDirs)) {
279 const Tool* tool = target_->toolchain()->GetTool(CTool::kCToolLink);
280
281 if (indent)
282 out_ << " ";
283 out_ << CSubstitutionFrameworkDirs.ninja_name << " =";
284 PathOutput framework_dirs_output(
285 path_output_.current_dir(),
286 settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
287 RecursiveTargetConfigToStream<SourceDir>(
288 kRecursiveWriterSkipDuplicates, target_, &ConfigValues::framework_dirs,
289 FrameworkDirsWriter(framework_dirs_output,
290 tool->framework_dir_switch()),
291 out_);
292 out_ << std::endl;
293 }
294
295 // Include directories.
296 if (bits.used.count(&CSubstitutionIncludeDirs)) {
297 if (indent)
298 out_ << " ";
299 out_ << CSubstitutionIncludeDirs.ninja_name << " =";
300 PathOutput include_path_output(
301 path_output_.current_dir(),
302 settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
303 RecursiveTargetConfigToStream<SourceDir>(
304 kRecursiveWriterSkipDuplicates, target_, &ConfigValues::include_dirs,
305 IncludeWriter(include_path_output), out_);
306 out_ << std::endl;
307 }
308
309 bool has_precompiled_headers =
310 target_->config_values().has_precompiled_headers();
311
312 EscapeOptions opts;
313 opts.mode = ESCAPE_NINJA_COMMAND;
314 if (respect_source_used
315 ? target_->source_types_used().Get(SourceFile::SOURCE_S)
316 : bits.used.count(&CSubstitutionAsmFlags)) {
317 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
318 &CSubstitutionAsmFlags, false, Tool::kToolNone,
319 &ConfigValues::asmflags, opts, path_output_, out_, true,
320 indent);
321 }
322 if (respect_source_used
323 ? (target_->source_types_used().Get(SourceFile::SOURCE_C) ||
324 target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
325 target_->source_types_used().Get(SourceFile::SOURCE_M) ||
326 target_->source_types_used().Get(SourceFile::SOURCE_MM) ||
327 target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP))
328 : bits.used.count(&CSubstitutionCFlags)) {
329 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlags,
330 false, Tool::kToolNone, &ConfigValues::cflags, opts,
331 path_output_, out_, true, indent);
332 }
333 if (respect_source_used
334 ? target_->source_types_used().Get(SourceFile::SOURCE_C)
335 : bits.used.count(&CSubstitutionCFlagsC)) {
336 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlagsC,
337 has_precompiled_headers, CTool::kCToolCc,
338 &ConfigValues::cflags_c, opts, path_output_, out_, true,
339 indent);
340 }
341 if (respect_source_used
342 ? (target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
343 target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP))
344 : bits.used.count(&CSubstitutionCFlagsCc)) {
345 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
346 &CSubstitutionCFlagsCc, has_precompiled_headers,
347 CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
348 out_, true, indent);
349 }
350 if (respect_source_used
351 ? target_->source_types_used().Get(SourceFile::SOURCE_M)
352 : bits.used.count(&CSubstitutionCFlagsObjC)) {
353 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
354 &CSubstitutionCFlagsObjC, has_precompiled_headers,
355 CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
356 path_output_, out_, true, indent);
357 }
358 if (respect_source_used
359 ? target_->source_types_used().Get(SourceFile::SOURCE_MM)
360 : bits.used.count(&CSubstitutionCFlagsObjCc)) {
361 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
362 &CSubstitutionCFlagsObjCc, has_precompiled_headers,
363 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
364 path_output_, out_, true, indent);
365 }
366 if (target_->source_types_used().SwiftSourceUsed() || !respect_source_used) {
367 if (bits.used.count(&CSubstitutionSwiftModuleName)) {
368 if (indent)
369 out_ << " ";
370 out_ << CSubstitutionSwiftModuleName.ninja_name << " = ";
371 EscapeStringToStream(out_, target_->swift_values().module_name(), opts);
372 out_ << std::endl;
373 }
374
375 if (bits.used.count(&CSubstitutionSwiftBridgeHeader)) {
376 if (indent)
377 out_ << " ";
378 out_ << CSubstitutionSwiftBridgeHeader.ninja_name << " = ";
379 if (!target_->swift_values().bridge_header().is_null()) {
380 path_output_.WriteFile(out_, target_->swift_values().bridge_header());
381 } else {
382 out_ << R"("")";
383 }
384 out_ << std::endl;
385 }
386
387 if (bits.used.count(&CSubstitutionSwiftModuleDirs)) {
388 // Uniquify the list of swiftmodule dirs (in case multiple swiftmodules
389 // are generated in the same directory).
390 UniqueVector<SourceDir> swiftmodule_dirs;
391 for (const Target* dep : resolved().GetSwiftModuleDependencies(target_))
392 swiftmodule_dirs.push_back(dep->swift_values().module_output_dir());
393
394 if (indent)
395 out_ << " ";
396 out_ << CSubstitutionSwiftModuleDirs.ninja_name << " =";
397 PathOutput swiftmodule_path_output(
398 path_output_.current_dir(),
399 settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
400 IncludeWriter swiftmodule_path_writer(swiftmodule_path_output);
401 for (const SourceDir& swiftmodule_dir : swiftmodule_dirs) {
402 swiftmodule_path_writer(swiftmodule_dir, out_);
403 }
404 out_ << std::endl;
405 }
406
407 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
408 &CSubstitutionSwiftFlags, false, CTool::kCToolSwift,
409 &ConfigValues::swiftflags, opts, path_output_, out_, true,
410 indent);
411 }
412 }
413
WriteRustCompilerVars(const SubstitutionBits & bits,bool indent,bool always_write)414 void NinjaTargetWriter::WriteRustCompilerVars(const SubstitutionBits& bits,
415 bool indent,
416 bool always_write) {
417 EscapeOptions opts;
418 opts.mode = ESCAPE_NINJA_COMMAND;
419
420 if (bits.used.count(&kRustSubstitutionRustFlags) || always_write) {
421 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
422 &kRustSubstitutionRustFlags, false, Tool::kToolNone,
423 &ConfigValues::rustflags, opts, path_output_, out_, true,
424 indent);
425 }
426
427 if (bits.used.count(&kRustSubstitutionRustEnv) || always_write) {
428 WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
429 &kRustSubstitutionRustEnv, false, Tool::kToolNone,
430 &ConfigValues::rustenv, opts, path_output_, out_, true,
431 indent);
432 }
433 }
434
WriteInputDepsStampAndGetDep(const std::vector<const Target * > & additional_hard_deps,size_t num_stamp_uses) const435 std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampAndGetDep(
436 const std::vector<const Target*>& additional_hard_deps,
437 size_t num_stamp_uses) const {
438 CHECK(target_->toolchain()) << "Toolchain not set on target "
439 << target_->label().GetUserVisibleName(true);
440
441 // ----------
442 // Collect all input files that are input deps of this target. Knowing the
443 // number before writing allows us to either skip writing the input deps
444 // stamp or optimize it. Use pointers to avoid copies here.
445 std::vector<const SourceFile*> input_deps_sources;
446 input_deps_sources.reserve(32);
447
448 // Actions get implicit dependencies on the script itself.
449 if (target_->output_type() == Target::ACTION ||
450 target_->output_type() == Target::ACTION_FOREACH)
451 input_deps_sources.push_back(&target_->action_values().script());
452
453 // Input files are only considered for non-binary targets which use an
454 // implicit dependency instead. The implicit dependency in this case is
455 // handled separately by the binary target writer.
456 if (!target_->IsBinary()) {
457 for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
458 for (const auto& input : iter.cur().inputs())
459 input_deps_sources.push_back(&input);
460 }
461 }
462
463 // For an action (where we run a script only once) the sources are the same
464 // as the inputs. For action_foreach, the sources will be operated on
465 // separately so don't handle them here.
466 if (target_->output_type() == Target::ACTION) {
467 for (const auto& source : target_->sources())
468 input_deps_sources.push_back(&source);
469 }
470
471 // ----------
472 // Collect all target input dependencies of this target as was done for the
473 // files above.
474 std::vector<const Target*> input_deps_targets;
475 input_deps_targets.reserve(32);
476
477 // Hard dependencies that are direct or indirect dependencies.
478 // These are large (up to 100s), hence why we check other
479 const TargetSet& hard_deps = resolved().GetHardDeps(target_);
480 for (const Target* target : hard_deps) {
481 // BUNDLE_DATA should normally be treated as a data-only dependency
482 // (see Target::IsDataOnly()). Only the CREATE_BUNDLE target, that actually
483 // consumes this data, needs to have the BUNDLE_DATA as an input dependency.
484 if (target->output_type() != Target::BUNDLE_DATA ||
485 target_->output_type() == Target::CREATE_BUNDLE)
486 input_deps_targets.push_back(target);
487 }
488
489 // Additional hard dependencies passed in. These are usually empty or small,
490 // and we don't want to duplicate the explicit hard deps of the target.
491 for (const Target* target : additional_hard_deps) {
492 if (!hard_deps.contains(target))
493 input_deps_targets.push_back(target);
494 }
495
496 // Toolchain dependencies. These must be resolved before doing anything.
497 // This just writes all toolchain deps for simplicity. If we find that
498 // toolchains often have more than one dependency, we could consider writing
499 // a toolchain-specific stamp file and only include the stamp here.
500 // Note that these are usually empty/small.
501 const LabelTargetVector& toolchain_deps = target_->toolchain()->deps();
502 for (const auto& toolchain_dep : toolchain_deps) {
503 // This could theoretically duplicate dependencies already in the list,
504 // but it shouldn't happen in practice, is inconvenient to check for,
505 // and only results in harmless redundant dependencies listed.
506 input_deps_targets.push_back(toolchain_dep.ptr);
507 }
508
509 // ---------
510 // Write the outputs.
511
512 if (input_deps_sources.size() + input_deps_targets.size() == 0)
513 return std::vector<OutputFile>(); // No input dependencies.
514
515 // If we're only generating one input dependency, return it directly instead
516 // of writing a stamp file for it.
517 if (input_deps_sources.size() == 1 && input_deps_targets.size() == 0)
518 return std::vector<OutputFile>{
519 OutputFile(settings_->build_settings(), *input_deps_sources[0])};
520 if (input_deps_sources.size() == 0 && input_deps_targets.size() == 1) {
521 const OutputFile& dep = input_deps_targets[0]->dependency_output_file();
522 DCHECK(!dep.value().empty());
523 return std::vector<OutputFile>{dep};
524 }
525
526 std::vector<OutputFile> outs;
527 // File input deps.
528 for (const SourceFile* source : input_deps_sources)
529 outs.push_back(OutputFile(settings_->build_settings(), *source));
530 // Target input deps. Sort by label so the output is deterministic (otherwise
531 // some of the targets will have gone through std::sets which will have
532 // sorted them by pointer).
533 std::sort(
534 input_deps_targets.begin(), input_deps_targets.end(),
535 [](const Target* a, const Target* b) { return a->label() < b->label(); });
536 for (auto* dep : input_deps_targets) {
537 DCHECK(!dep->dependency_output_file().value().empty());
538 outs.push_back(dep->dependency_output_file());
539 }
540
541 // If there are multiple inputs, but the stamp file would be referenced only
542 // once, don't write it but depend on the inputs directly.
543 if (num_stamp_uses == 1u)
544 return outs;
545
546 // Make a stamp file.
547 OutputFile input_stamp_file =
548 GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
549 input_stamp_file.value().append(target_->label().name());
550 input_stamp_file.value().append(".inputdeps.stamp");
551
552 out_ << "build ";
553 WriteOutput(input_stamp_file);
554
555 out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
556 << GeneralTool::kGeneralToolStamp;
557 path_output_.WriteFiles(out_, outs);
558
559 out_ << "\n";
560 return std::vector<OutputFile>{input_stamp_file};
561 }
562
WriteStampForTarget(const std::vector<OutputFile> & files,const std::vector<OutputFile> & order_only_deps)563 void NinjaTargetWriter::WriteStampForTarget(
564 const std::vector<OutputFile>& files,
565 const std::vector<OutputFile>& order_only_deps) {
566 const OutputFile& stamp_file = target_->dependency_output_file();
567
568 // First validate that the target's dependency is a stamp file. Otherwise,
569 // we shouldn't have gotten here!
570 CHECK(base::EndsWithCaseInsensitiveASCII(stamp_file.value(), ".stamp"))
571 << "Output should end in \".stamp\" for stamp file output. Instead got: "
572 << "\"" << stamp_file.value() << "\"";
573
574 out_ << "build ";
575 WriteOutput(std::move(stamp_file));
576
577 out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
578 << GeneralTool::kGeneralToolStamp;
579 path_output_.WriteFiles(out_, files);
580
581 if (!order_only_deps.empty()) {
582 out_ << " ||";
583 path_output_.WriteFiles(out_, order_only_deps);
584 }
585 out_ << std::endl;
586 }
587