Add NinjaOutputsWriter class Implement a new class that can be used to generate a JSON file containing an object mapping GN label strings to list of corresponding Ninja output paths (including stamp files). This will be used to implement a new `gn gen` option. The output can be used by GN clients to quickly convert between GN labels and Ninja targets. This is done through the following changes: - NinjaTargetWriter gets new methods to write output files to the build plan, and record the corresponding OutputFile values (without Ninja escaping) separately if needed. - All NinjaTargetWriter sub-classes are updated to call WriteOutput() or WriteOutputs() when adding Ninja output paths to the generated .ninja_files, instead of calling path_outputs_.WriteFile() directly. Change-Id: Id6b1cc667e7bec56d37ededb75475fbc480eccaf Reviewed-on: https://gn-review.googlesource.com/c/gn/+/16761 Reviewed-by: Dirk Pranke <dpranke@google.com> Commit-Queue: David Turner <digit@google.com>
diff --git a/build/gen.py b/build/gen.py index 41d1571..e7037bb 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -706,6 +706,7 @@ 'src/gn/ninja_create_bundle_target_writer.cc', 'src/gn/ninja_generated_file_target_writer.cc', 'src/gn/ninja_group_target_writer.cc', + 'src/gn/ninja_outputs_writer.cc', 'src/gn/ninja_rust_binary_target_writer.cc', 'src/gn/ninja_target_command_util.cc', 'src/gn/ninja_target_writer.cc', @@ -831,6 +832,7 @@ 'src/gn/ninja_create_bundle_target_writer_unittest.cc', 'src/gn/ninja_generated_file_target_writer_unittest.cc', 'src/gn/ninja_group_target_writer_unittest.cc', + 'src/gn/ninja_outputs_writer_unittest.cc', 'src/gn/ninja_rust_binary_target_writer_unittest.cc', 'src/gn/ninja_target_command_util_unittest.cc', 'src/gn/ninja_target_writer_unittest.cc',
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc index d6d3aa8..06da244 100644 --- a/src/gn/ninja_action_target_writer.cc +++ b/src/gn/ninja_action_target_writer.cc
@@ -87,7 +87,7 @@ out_ << "build"; SubstitutionWriter::GetListAsOutputFiles( settings_, target_->action_values().outputs(), &output_files); - path_output_.WriteFiles(out_, output_files); + WriteOutputs(output_files); out_ << ": " << custom_rule_name; if (!input_deps.empty()) { @@ -275,7 +275,7 @@ for (size_t i = first_output_index; i < output_files->size(); i++) { out_ << " "; - path_output_.WriteFile(out_, (*output_files)[i]); + WriteOutput((*output_files)[i]); } }
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc index 3cfa584..62e01eb 100644 --- a/src/gn/ninja_binary_target_writer.cc +++ b/src/gn/ninja_binary_target_writer.cc
@@ -44,12 +44,14 @@ if (target_->source_types_used().RustSourceUsed()) { NinjaRustBinaryTargetWriter writer(target_, out_); writer.SetResolvedTargetData(GetResolvedTargetData()); + writer.SetNinjaOutputs(ninja_outputs_); writer.Run(); return; } NinjaCBinaryTargetWriter writer(target_, out_); writer.SetResolvedTargetData(GetResolvedTargetData()); + writer.SetNinjaOutputs(ninja_outputs_); writer.Run(); } @@ -91,7 +93,8 @@ stamp_file.value().append(".inputs.stamp"); out_ << "build "; - path_output_.WriteFile(out_, stamp_file); + WriteOutput(stamp_file); + out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << GeneralTool::kGeneralToolStamp; @@ -263,7 +266,7 @@ const std::vector<OutputFile>& outputs, bool can_write_source_info) { out_ << "build"; - path_output_.WriteFiles(out_, outputs); + WriteOutputs(outputs); out_ << ": " << rule_prefix_ << tool_name; path_output_.WriteFiles(out_, sources);
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc index f97c222..eada97d 100644 --- a/src/gn/ninja_c_binary_target_writer.cc +++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -612,7 +612,7 @@ target_, tool_, tool_->outputs(), &output_files); out_ << "build"; - path_output_.WriteFiles(out_, output_files); + WriteOutputs(output_files); out_ << ": " << rule_prefix_ << Tool::GetToolTypeForTargetFinalOutput(target_);
diff --git a/src/gn/ninja_copy_target_writer.cc b/src/gn/ninja_copy_target_writer.cc index 157b612..eca56d0 100644 --- a/src/gn/ninja_copy_target_writer.cc +++ b/src/gn/ninja_copy_target_writer.cc
@@ -113,7 +113,8 @@ output_files->push_back(output_file); out_ << "build "; - path_output_.WriteFile(out_, output_file); + WriteOutput(std::move(output_file)); + out_ << ": " << tool_name << " "; path_output_.WriteFile(out_, input_file); if (!input_deps.empty() || !data_outs.empty()) {
diff --git a/src/gn/ninja_create_bundle_target_writer.cc b/src/gn/ninja_create_bundle_target_writer.cc index 23936ab..efa950e 100644 --- a/src/gn/ninja_create_bundle_target_writer.cc +++ b/src/gn/ninja_create_bundle_target_writer.cc
@@ -97,10 +97,11 @@ // targets to treat the entire bundle as a single unit, even though it is // a directory, so that it can be depended upon as a discrete build edge. out_ << "build "; - path_output_.WriteFile( - out_, + + WriteOutput( OutputFile(settings_->build_settings(), target_->bundle_data().GetBundleRootDirOutput(settings_))); + out_ << ": phony " << target_->dependency_output_file().value(); out_ << std::endl; } @@ -162,7 +163,7 @@ output_files->push_back(expanded_output_file); out_ << "build "; - path_output_.WriteFile(out_, expanded_output_file); + WriteOutput(std::move(expanded_output_file)); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << GeneralTool::kGeneralToolCopyBundleData << " "; path_output_.WriteFile(out_, source_file); @@ -206,7 +207,7 @@ DCHECK(!target_->bundle_data().partial_info_plist().is_null()); out_ << "build "; - path_output_.WriteFile(out_, partial_info_plist); + WriteOutput(partial_info_plist); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << GeneralTool::kGeneralToolStamp; if (!order_only_deps.empty()) { @@ -222,14 +223,14 @@ DCHECK(!input_dep.value().empty()); out_ << "build "; - path_output_.WriteFile(out_, compiled_catalog); + WriteOutput(std::move(compiled_catalog)); if (partial_info_plist != OutputFile()) { // If "partial_info_plist" is non-empty, then add it to list of implicit // outputs of the asset catalog compilation, so that target can use it // without getting the ninja error "'foo', needed by 'bar', missing and // no known rule to make it". out_ << " | "; - path_output_.WriteFile(out_, partial_info_plist); + WriteOutput(partial_info_plist); } out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) @@ -289,7 +290,7 @@ xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp"); out_ << "build "; - path_output_.WriteFile(out_, xcassets_input_stamp_file); + WriteOutput(xcassets_input_stamp_file); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << GeneralTool::kGeneralToolStamp; @@ -317,7 +318,7 @@ SubstitutionWriter::GetListAsOutputFiles( settings_, target_->bundle_data().code_signing_outputs(), &code_signing_output_files); - path_output_.WriteFiles(out_, code_signing_output_files); + WriteOutputs(code_signing_output_files); // Since the code signature step depends on all the files from the bundle, // the create_bundle stamp can just depends on the output of the signature @@ -355,7 +356,7 @@ code_signing_input_stamp_file.value().append(".codesigning.inputdeps.stamp"); out_ << "build "; - path_output_.WriteFile(out_, code_signing_input_stamp_file); + WriteOutput(code_signing_input_stamp_file); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << GeneralTool::kGeneralToolStamp;
diff --git a/src/gn/ninja_outputs_writer.cc b/src/gn/ninja_outputs_writer.cc new file mode 100644 index 0000000..4f9309f --- /dev/null +++ b/src/gn/ninja_outputs_writer.cc
@@ -0,0 +1,157 @@ +// Copyright (c) 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gn/ninja_outputs_writer.h" + +#include <algorithm> +#include <memory> + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/json/string_escape.h" +#include "gn/builder.h" +#include "gn/commands.h" +#include "gn/filesystem_utils.h" +#include "gn/invoke_python.h" +#include "gn/settings.h" +#include "gn/string_output_buffer.h" + +// NOTE: Intentional macro definition allows compile-time string concatenation. +// (see usage below). +#if defined(OS_WINDOWS) +#define LINE_ENDING "\r\n" +#else +#define LINE_ENDING "\n" +#endif + +namespace { + +using MapType = NinjaOutputsWriter::MapType; + +// Sort the targets according to their human visible labels first. +struct TargetLabelPair { + TargetLabelPair(const Target* target, const Label& default_toolchain_label) + : target(target), + label(std::make_unique<std::string>( + target->label().GetUserVisibleName(default_toolchain_label))) {} + + const Target* target; + std::unique_ptr<std::string> label; + + bool operator<(const TargetLabelPair& other) const { + return *label < *other.label; + } + + using List = std::vector<TargetLabelPair>; + + // Create list of TargetLabelPairs sorted by their target labels. + static List CreateSortedList(const MapType& outputs_map, + const Label& default_toolchain_label) { + List result; + result.reserve(outputs_map.size()); + + for (const auto& output_pair : outputs_map) + result.emplace_back(output_pair.first, default_toolchain_label); + + std::sort(result.begin(), result.end()); + return result; + } +}; + +} // namespace + +// static +StringOutputBuffer NinjaOutputsWriter::GenerateJSON( + const MapType& outputs_map) { + Label default_toolchain_label; + if (!outputs_map.empty()) { + default_toolchain_label = + outputs_map.begin()->first->settings()->default_toolchain_label(); + } + + auto sorted_pairs = + TargetLabelPair::CreateSortedList(outputs_map, default_toolchain_label); + + StringOutputBuffer out; + out.Append('{'); + + auto escape = [](std::string_view str) -> std::string { + std::string result; + base::EscapeJSONString(str, true, &result); + return result; + }; + + bool first_label = true; + for (const auto& pair : sorted_pairs) { + const Target* target = pair.target; + const std::string& label = *pair.label; + + auto it = outputs_map.find(target); + CHECK(it != outputs_map.end()); + + if (!first_label) + out.Append(','); + first_label = false; + + out.Append("\n "); + out.Append(escape(label)); + out.Append(": ["); + bool first_path = true; + for (const auto& output : it->second) { + if (!first_path) + out.Append(','); + first_path = false; + out.Append("\n "); + out.Append(escape(output.value())); + } + out.Append("\n ]"); + } + + out.Append("\n}"); + return out; +} + +bool NinjaOutputsWriter::RunAndWriteFiles( + const MapType& outputs_map, + const BuildSettings* build_settings, + const std::string& file_name, + const std::string& exec_script, + const std::string& exec_script_extra_args, + bool quiet, + Err* err) { + SourceFile output_file = build_settings->build_dir().ResolveRelativeFile( + Value(nullptr, file_name), err); + if (output_file.is_null()) { + return false; + } + + StringOutputBuffer outputs = GenerateJSON(outputs_map); + + base::FilePath output_path = build_settings->GetFullPath(output_file); + if (!outputs.ContentsEqual(output_path)) { + if (!outputs.WriteToFile(output_path, err)) { + return false; + } + + if (!exec_script.empty()) { + SourceFile script_file; + if (exec_script[0] != '/') { + // Relative path, assume the base is in build_dir. + script_file = build_settings->build_dir().ResolveRelativeFile( + Value(nullptr, exec_script), err); + if (script_file.is_null()) { + return false; + } + } else { + script_file = SourceFile(exec_script); + } + base::FilePath script_path = build_settings->GetFullPath(script_file); + return internal::InvokePython(build_settings, script_path, + exec_script_extra_args, output_path, quiet, + err); + } + } + + return true; +}
diff --git a/src/gn/ninja_outputs_writer.h b/src/gn/ninja_outputs_writer.h new file mode 100644 index 0000000..c81a3ed --- /dev/null +++ b/src/gn/ninja_outputs_writer.h
@@ -0,0 +1,41 @@ +// Copyright (c) 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_GN_NINJA_OUTPUTS_WRITER_H_ +#define TOOLS_GN_NINJA_OUTPUTS_WRITER_H_ + +#include <string> +#include <unordered_map> +#include <vector> + +#include "gn/err.h" +#include "gn/output_file.h" +#include "gn/string_output_buffer.h" +#include "gn/target.h" + +class Builder; +class BuildSettings; +class StringOutputBuffer; + +// Generates the --ninja-outputs-file content +class NinjaOutputsWriter { + public: + // A map from targets to list of corresponding Ninja output paths. + using MapType = std::unordered_map<const Target*, std::vector<OutputFile>>; + + static bool RunAndWriteFiles(const MapType& outputs_map, + const BuildSettings* build_setting, + const std::string& file_name, + const std::string& exec_script, + const std::string& exec_script_extra_args, + bool quiet, + Err* err); + + private: + FRIEND_TEST_ALL_PREFIXES(NinjaOutputsWriterTest, OutputsFile); + + static StringOutputBuffer GenerateJSON(const MapType& outputs_map); +}; + +#endif
diff --git a/src/gn/ninja_outputs_writer_unittest.cc b/src/gn/ninja_outputs_writer_unittest.cc new file mode 100644 index 0000000..52354c6 --- /dev/null +++ b/src/gn/ninja_outputs_writer_unittest.cc
@@ -0,0 +1,159 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gn/ninja_outputs_writer.h" + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "gn/builder_record.h" +#include "gn/filesystem_utils.h" +#include "gn/ninja_target_writer.h" +#include "gn/setup.h" +#include "gn/switches.h" +#include "gn/test_with_scheduler.h" +#include "util/test/test.h" + +using NinjaOutputsWriterTest = TestWithScheduler; +using NinjaOutputsMap = NinjaOutputsWriter::MapType; + +static void WriteFile(const base::FilePath& file, const std::string& data) { + CHECK_EQ(static_cast<int>(data.size()), // Way smaller than INT_MAX. + base::WriteFile(file, data.data(), data.size())); +} + +// Collects Ninja outputs for each target. Used by multiple background threads. +struct TargetWriteInfo { + std::mutex lock; + NinjaOutputsMap ninja_outputs_map; +}; + +// Called on worker thread to write the ninja file. +void BackgroundDoWrite(TargetWriteInfo* write_info, const Target* target) { + std::vector<OutputFile> target_ninja_outputs; + std::string rule = NinjaTargetWriter::RunAndWriteFile(target, nullptr, + &target_ninja_outputs); + + DCHECK(!rule.empty()); + + std::lock_guard<std::mutex> lock(write_info->lock); + write_info->ninja_outputs_map.emplace(target, + std::move(target_ninja_outputs)); +} + +static void ItemResolvedAndGeneratedCallback(TargetWriteInfo* write_info, + const BuilderRecord* record) { + const Item* item = record->item(); + const Target* target = item->AsTarget(); + if (target) { + g_scheduler->ScheduleWork( + [write_info, target]() { BackgroundDoWrite(write_info, target); }); + } +} + +TEST_F(NinjaOutputsWriterTest, OutputsFile) { + base::CommandLine cmdline(base::CommandLine::NO_PROGRAM); + + const char kDotfileContents[] = R"( +buildconfig = "//BUILDCONFIG.gn" +)"; + + const char kBuildConfigContents[] = R"( +set_default_toolchain("//toolchain:default") +)"; + + const char kToolchainBuildContents[] = R"##( +toolchain("default") { + tool("stamp") { + command = "stamp" + } +} + +toolchain("secondary") { + tool("stamp") { + command = "stamp2" + } +} +)##"; + + const char kBuildGnContents[] = R"##( +group("foo") { + deps = [ ":bar", ":zoo(//toolchain:secondary)" ] +} + +action("bar") { + script = "//:run_bar_script.py" + outputs = [ "$root_build_dir/bar.output" ] + args = [] +} + +group("zoo") { +} +)##"; + + // Create a temp directory containing the build. + base::ScopedTempDir in_temp_dir; + ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir()); + base::FilePath in_path = in_temp_dir.GetPath(); + + WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILD.gn")), kBuildGnContents); + WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), + kBuildConfigContents); + WriteFile(in_path.Append(FILE_PATH_LITERAL(".gn")), kDotfileContents); + + EXPECT_TRUE( + base::CreateDirectory(in_path.Append(FILE_PATH_LITERAL("toolchain")))); + + WriteFile(in_path.Append(FILE_PATH_LITERAL("toolchain/BUILD.gn")), + kToolchainBuildContents); + + cmdline.AppendSwitch(switches::kRoot, FilePathToUTF8(in_path)); + + base::FilePath outputs_json_path(FILE_PATH_LITERAL("ninja_outputs.json")); + cmdline.AppendSwitch("--ninja-outputs-file", + FilePathToUTF8(outputs_json_path)); + + // Create another temp dir for writing the generated files to. + base::ScopedTempDir build_temp_dir; + ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir()); + + // Run setup + Setup setup; + EXPECT_TRUE( + setup.DoSetup(FilePathToUTF8(build_temp_dir.GetPath()), true, cmdline)); + + TargetWriteInfo write_info; + + setup.builder().set_resolved_and_generated_callback( + [&write_info](const BuilderRecord* record) { + ItemResolvedAndGeneratedCallback(&write_info, record); + }); + + // Do the actual load. + ASSERT_TRUE(setup.Run()); + + StringOutputBuffer out = + NinjaOutputsWriter::GenerateJSON(write_info.ninja_outputs_map); + + // Verify that the generated file is here. + std::string generated = out.str(); + std::string expected = R"##({ + "//:bar": [ + "bar.output", + "obj/bar.stamp" + ], + "//:foo": [ + "obj/foo.stamp" + ], + "//:zoo": [ + "obj/zoo.stamp" + ], + "//:zoo(//toolchain:secondary)": [ + "secondary/obj/zoo.stamp" + ] +})##"; + + EXPECT_EQ(generated, expected); +}
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc index 6ecbaa7..b8b156f 100644 --- a/src/gn/ninja_target_writer.cc +++ b/src/gn/ninja_target_writer.cc
@@ -47,6 +47,11 @@ } } +void NinjaTargetWriter::SetNinjaOutputs( + std::vector<OutputFile>* ninja_outputs) { + ninja_outputs_ = ninja_outputs; +} + ResolvedTargetData* NinjaTargetWriter::GetResolvedTargetData() { return const_cast<ResolvedTargetData*>(&resolved()); } @@ -61,9 +66,39 @@ NinjaTargetWriter::~NinjaTargetWriter() = default; +void NinjaTargetWriter::WriteOutput(const OutputFile& output) const { + path_output_.WriteFile(out_, output); + if (ninja_outputs_) + ninja_outputs_->push_back(output); +} + +void NinjaTargetWriter::WriteOutput(OutputFile&& output) const { + path_output_.WriteFile(out_, output); + if (ninja_outputs_) + ninja_outputs_->push_back(std::move(output)); +} + +void NinjaTargetWriter::WriteOutputs( + const std::vector<OutputFile>& outputs) const { + path_output_.WriteFiles(out_, outputs); + if (ninja_outputs_) + ninja_outputs_->insert(ninja_outputs_->end(), outputs.begin(), + outputs.end()); +} + +void NinjaTargetWriter::WriteOutputs(std::vector<OutputFile>&& outputs) const { + path_output_.WriteFiles(out_, outputs); + if (ninja_outputs_) { + for (auto& output : outputs) + ninja_outputs_->push_back(std::move(output)); + } +} + // static -std::string NinjaTargetWriter::RunAndWriteFile(const Target* target, - ResolvedTargetData* resolved) { +std::string NinjaTargetWriter::RunAndWriteFile( + const Target* target, + ResolvedTargetData* resolved, + std::vector<OutputFile>* ninja_outputs) { const Settings* settings = target->settings(); ScopedTrace trace(TraceItem::TRACE_FILE_WRITE_NINJA, @@ -97,32 +132,39 @@ if (target->output_type() == Target::BUNDLE_DATA) { NinjaBundleDataTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); + writer.SetNinjaOutputs(ninja_outputs); writer.Run(); } else if (target->output_type() == Target::CREATE_BUNDLE) { NinjaCreateBundleTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); + writer.SetNinjaOutputs(ninja_outputs); writer.Run(); } else if (target->output_type() == Target::COPY_FILES) { NinjaCopyTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); + writer.SetNinjaOutputs(ninja_outputs); writer.Run(); } else if (target->output_type() == Target::ACTION || target->output_type() == Target::ACTION_FOREACH) { NinjaActionTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); + writer.SetNinjaOutputs(ninja_outputs); writer.Run(); } else if (target->output_type() == Target::GROUP) { NinjaGroupTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); + writer.SetNinjaOutputs(ninja_outputs); writer.Run(); } else if (target->output_type() == Target::GENERATED_FILE) { NinjaGeneratedFileTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); + writer.SetNinjaOutputs(ninja_outputs); writer.Run(); } else if (target->IsBinary()) { needs_file_write = true; NinjaBinaryTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); + writer.SetNinjaOutputs(ninja_outputs); writer.Run(); } else { CHECK(0) << "Output type of target not handled."; @@ -508,7 +550,8 @@ input_stamp_file.value().append(".inputdeps.stamp"); out_ << "build "; - path_output_.WriteFile(out_, input_stamp_file); + WriteOutput(input_stamp_file); + out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << GeneralTool::kGeneralToolStamp; path_output_.WriteFiles(out_, outs); @@ -529,7 +572,7 @@ << "\"" << stamp_file.value() << "\""; out_ << "build "; - path_output_.WriteFile(out_, stamp_file); + WriteOutput(std::move(stamp_file)); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << GeneralTool::kGeneralToolStamp;
diff --git a/src/gn/ninja_target_writer.h b/src/gn/ninja_target_writer.h index 74378da..9655c72 100644 --- a/src/gn/ninja_target_writer.h +++ b/src/gn/ninja_target_writer.h
@@ -34,6 +34,11 @@ // instances to share the same cached information. void SetResolvedTargetData(ResolvedTargetData* resolved); + // Set the vector that will receive the Ninja output file paths generated + // by this writer. A nullptr value means no output files needs to be + // collected. + void SetNinjaOutputs(std::vector<OutputFile>* ninja_outputs); + // Returns the build line to be written to the toolchain build file. // // Some targets have their rules written to separate files, and some can have @@ -41,8 +46,13 @@ // function will return the rules as a string. For the separate file case, // the separate ninja file will be written and the return string will be the // subninja command to load that file. - static std::string RunAndWriteFile(const Target* target, - ResolvedTargetData* resolved = nullptr); + // + // If |ninja_outputs| is not nullptr, it will be set with the list of + // Ninja output paths generated by the corresponding writer. + static std::string RunAndWriteFile( + const Target* target, + ResolvedTargetData* resolved = nullptr, + std::vector<OutputFile>* ninja_outputs = nullptr); virtual void Run() = 0; @@ -94,6 +104,21 @@ std::ostream& out_; PathOutput path_output_; + // Write a Ninja output file to out_, and also add it to |*ninja_outputs_| + // if needed. + void WriteOutput(const OutputFile& output) const; + void WriteOutput(OutputFile&& output) const; + + // Same as WriteOutput() for a list of Ninja output file paths. + void WriteOutputs(const std::vector<OutputFile>& outputs) const; + void WriteOutputs(std::vector<OutputFile>&& outputs) const; + + // The list of all Ninja output file paths generated by this writer for + // this target. Used to implement the --ide=ninja_outputs `gn gen` flag. + // Needs to be mutable because WriteOutput() and WriteOutputs() need to + // be const. + mutable std::vector<OutputFile>* ninja_outputs_ = nullptr; + // The ResolvedTargetData instance can be set through SetResolvedTargetData() // or it will be created lazily when resolved() is called, hence the need // for 'mutable' here.