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.