Record generated_file()s as outputs of `gn`

This is an accurate relationship and allows for tooling to ensure a
given generated_file() is up-to-date without an unconditional `gn gen`.

build.ninja was manually verified in a particular GN-based build,
wherein `ninja $SOME_GENERATED_FILE` now works.

Fixed: 301
Change-Id: I06c7ec142072d2e73860cdde4898af6f9e65065a
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/14580
Commit-Queue: Joshua Seaton <joshuaseaton@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc
index 6a5b91d..5e83bc8 100644
--- a/src/gn/ninja_build_writer.cc
+++ b/src/gn/ninja_build_writer.cc
@@ -23,6 +23,7 @@
 #include "gn/input_file_manager.h"
 #include "gn/loader.h"
 #include "gn/ninja_utils.h"
+#include "gn/pointer_set.h"
 #include "gn/pool.h"
 #include "gn/scheduler.h"
 #include "gn/string_atom.h"
@@ -140,10 +141,10 @@
 
 // Given an output that appears more than once, generates an error message
 // that describes the problem and which targets generate it.
-Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets,
+Err GetDuplicateOutputError(const std::vector<const Target*>& targets,
                             const OutputFile& bad_output) {
   std::vector<const Target*> matches;
-  for (const Target* target : all_targets) {
+  for (const Target* target : targets) {
     for (const auto& output : target->computed_outputs()) {
       if (output == bad_output) {
         matches.push_back(target);
@@ -196,7 +197,7 @@
     const BuildSettings* build_settings,
     const std::unordered_map<const Settings*, const Toolchain*>&
         used_toolchains,
-    const std::vector<const Target*>& all_targets,
+    const PointerSet<const Target>& all_targets,
     const Toolchain* default_toolchain,
     const std::vector<const Target*>& default_toolchain_targets,
     std::ostream& out,
@@ -240,9 +241,11 @@
   // skip adding it to the list every time in the loop.
   used_toolchains[default_toolchain_settings] = default_toolchain;
 
+  PointerSet<const Target> target_set;
   std::vector<const Target*> default_toolchain_targets;
   default_toolchain_targets.reserve(all_targets.size());
   for (const Target* target : all_targets) {
+    target_set.insert(target);
     if (target->settings() == default_toolchain_settings) {
       default_toolchain_targets.push_back(target);
       // The default toolchain will already have been added to the used
@@ -256,7 +259,7 @@
 
   std::stringstream file;
   std::stringstream depfile;
-  NinjaBuildWriter gen(build_settings, used_toolchains, all_targets,
+  NinjaBuildWriter gen(build_settings, used_toolchains, target_set,
                        default_toolchain, default_toolchain_targets, file,
                        depfile);
   if (!gen.Run(err))
@@ -332,7 +335,27 @@
        << "# build.ninja edge is separate to prevent ninja from deleting it\n"
        << "# (due to depfile usage) if interrupted. gn uses atomic writes to\n"
        << "# ensure that build.ninja is always valid even if interrupted.\n"
-       << "build build.ninja.stamp: gn\n"
+       << "build build.ninja.stamp";
+
+  // Record all generated files as outputs of `gn` as well.
+  //
+  // TODO(fxbug.dev/303): We currently rely on the sort below to dedupe
+  // entries.
+  std::multimap<OutputFile, const Target*> generated_files = g_scheduler->GetGeneratedFiles();
+  VectorSetSorter<OutputFile> generated_file_sorter(generated_files.size());
+  for (const auto& entry : generated_files) {
+    // Only includes those actually in the build.
+    if (all_targets_.contains(entry.second)) {
+      generated_file_sorter.Add(entry.first);
+    }
+  }
+  generated_file_sorter.IterateOver(
+      [this](const OutputFile& output_file) {
+        out_ << " ";
+        path_output_.WriteFile(out_, output_file);
+      });
+
+  out_ << ": gn\n"
        << "  generator = 1\n"
        << "  depfile = build.ninja.d\n"
        << "\n"
@@ -359,7 +382,6 @@
 
   const base::FilePath build_path =
       build_settings_->build_dir().Resolve(build_settings_->root_path());
-
   EscapeOptions depfile_escape;
   depfile_escape.mode = ESCAPE_DEPFILE;
   auto item_callback = [this, &depfile_escape,
diff --git a/src/gn/ninja_build_writer.h b/src/gn/ninja_build_writer.h
index c21769a..dbfe891 100644
--- a/src/gn/ninja_build_writer.h
+++ b/src/gn/ninja_build_writer.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "gn/path_output.h"
+#include "gn/pointer_set.h"
 
 class Builder;
 class BuildSettings;
@@ -32,7 +33,7 @@
   NinjaBuildWriter(const BuildSettings* settings,
                    const std::unordered_map<const Settings*, const Toolchain*>&
                        used_toolchains,
-                   const std::vector<const Target*>& all_targets,
+                   const PointerSet<const Target>& all_targets,
                    const Toolchain* default_toolchain,
                    const std::vector<const Target*>& default_toolchain_targets,
                    std::ostream& out,
@@ -88,7 +89,7 @@
   const BuildSettings* build_settings_;
 
   const std::unordered_map<const Settings*, const Toolchain*>& used_toolchains_;
-  const std::vector<const Target*>& all_targets_;
+  const PointerSet<const Target>& all_targets_;
   const Toolchain* default_toolchain_;
   const std::vector<const Target*>& default_toolchain_targets_;
 
diff --git a/src/gn/ninja_build_writer_unittest.cc b/src/gn/ninja_build_writer_unittest.cc
index 8ab5e56..8966f59 100644
--- a/src/gn/ninja_build_writer_unittest.cc
+++ b/src/gn/ninja_build_writer_unittest.cc
@@ -134,13 +134,14 @@
   used_toolchains[setup.settings()] = setup.toolchain();
   used_toolchains[&other_settings] = &other_toolchain;
 
-  std::vector<const Target*> targets = {&target_foo, &target_bar, &target_baz};
+  std::vector<const Target*> target_list = {&target_foo, &target_bar, &target_baz};
+  PointerSet<const Target> target_set(target_list.begin(), target_list.end());
 
   std::ostringstream ninja_out;
   std::ostringstream depfile_out;
 
-  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
-                          setup.toolchain(), targets, ninja_out, depfile_out);
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, target_set,
+                          setup.toolchain(), target_list, ninja_out, depfile_out);
   ASSERT_TRUE(writer.Run(&err));
 
   const char expected_rule_gn[] = "rule gn\n";
@@ -189,6 +190,65 @@
   EXPECT_EQ(std::string::npos, out_str.find("pool console"));
 }
 
+TEST_F(NinjaBuildWriterTest, GeneratedFiles) {
+  TestWithScope setup;
+  Err err;
+
+  Target target_foo(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+  target_foo.set_output_type(Target::GENERATED_FILE);
+  target_foo.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/foo.json");
+  target_foo.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target_foo.OnResolved(&err));
+
+  Target target_bar(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+  target_bar.set_output_type(Target::GENERATED_FILE);
+  target_bar.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/bar.txt");
+  target_bar.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target_bar.OnResolved(&err));
+
+  // Make a secondary toolchain that references two pools.
+  Label other_toolchain_label(SourceDir("//other/"), "toolchain");
+  Toolchain other_toolchain(setup.settings(), other_toolchain_label);
+  TestWithScope::SetupToolchain(&other_toolchain);
+
+  std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
+  used_toolchains[setup.settings()] = setup.toolchain();
+
+  std::vector<const Target*> target_list = {&target_foo, &target_bar};
+  PointerSet<const Target> target_set(target_list.begin(), target_list.end());
+
+  std::ostringstream ninja_out;
+  std::ostringstream depfile_out;
+
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, target_set,
+                          setup.toolchain(), target_list, ninja_out, depfile_out);
+  ASSERT_TRUE(writer.Run(&err));
+
+  const char expected_rule_gn[] = "rule gn\n";
+  const char expected_build_ninja_stamp[] =
+      "build build.ninja.stamp bar.txt foo.json: gn\n"
+      "  generator = 1\n"
+      "  depfile = build.ninja.d\n";
+  const char expected_build_ninja[] =
+      "build build.ninja: phony build.ninja.stamp\n"
+      "  generator = 1\n";
+  const char expected_toolchain[] = "subninja toolchain.ninja\n";
+  const char expected_default[] = "default all\n";
+  std::string out_str = ninja_out.str();
+#define EXPECT_SNIPPET(expected)                       \
+  EXPECT_NE(std::string::npos, out_str.find(expected)) \
+      << "Expected to find: " << expected << "\n"      \
+      << "Within: " << out_str
+  EXPECT_SNIPPET(expected_rule_gn);
+  EXPECT_SNIPPET(expected_build_ninja_stamp);
+  EXPECT_SNIPPET(expected_build_ninja);
+  EXPECT_SNIPPET(expected_toolchain);
+  EXPECT_SNIPPET(expected_default);
+#undef EXPECT_SNIPPET
+}
+
 TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands) {
   TestWithScope setup;
   Err err;
@@ -210,13 +270,14 @@
   std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
   used_toolchains[setup.settings()] = setup.toolchain();
 
-  std::vector<const Target*> targets = {&target_foo};
+  std::vector<const Target*> target_list = {&target_foo};
+  PointerSet<const Target> target_set(target_list.begin(), target_list.end());
 
   std::stringstream ninja_out;
   std::ostringstream depfile_out;
 
-  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
-                          setup.toolchain(), targets, ninja_out, depfile_out);
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, target_set,
+                          setup.toolchain(), target_list, ninja_out, depfile_out);
   ASSERT_TRUE(writer.Run(&err));
 
   const char expected_rule_gn[] = "rule gn\n";
@@ -287,11 +348,10 @@
 
   std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
   used_toolchains[setup.settings()] = setup.toolchain();
-  std::vector<const Target*> targets;
   std::ostringstream ninja_out;
   std::ostringstream depfile_out;
-  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
-                          setup.toolchain(), targets, ninja_out, depfile_out);
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, {},
+                          setup.toolchain(), {}, ninja_out, depfile_out);
   ASSERT_TRUE(writer.Run(&err));
 
   EXPECT_EQ(depfile_out.str(),
@@ -320,11 +380,13 @@
 
   std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
   used_toolchains[setup.settings()] = setup.toolchain();
-  std::vector<const Target*> targets = {&target_foo, &target_bar};
+
+  std::vector<const Target*> target_list = {&target_foo, &target_bar};
+  PointerSet<const Target> target_set(target_list.begin(), target_list.end());
   std::ostringstream ninja_out;
   std::ostringstream depfile_out;
-  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
-                          setup.toolchain(), targets, ninja_out, depfile_out);
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, target_set,
+                          setup.toolchain(), target_list, ninja_out, depfile_out);
   ASSERT_FALSE(writer.Run(&err));
 
   const char expected_help_test[] =
diff --git a/src/gn/scheduler.cc b/src/gn/scheduler.cc
index 6525f33..35ae097 100644
--- a/src/gn/scheduler.cc
+++ b/src/gn/scheduler.cc
@@ -111,16 +111,21 @@
   return false;
 }
 
-void Scheduler::AddGeneratedFile(const SourceFile& entry) {
+void Scheduler::AddGeneratedFile(const Target* target, const OutputFile& file) {
   std::lock_guard<std::mutex> lock(lock_);
-  generated_files_.insert(std::make_pair(entry, true));
+  generated_files_.insert(std::make_pair(file, target));
 }
 
-bool Scheduler::IsFileGeneratedByTarget(const SourceFile& file) const {
+bool Scheduler::IsFileGeneratedByTarget(const OutputFile& file) const {
   std::lock_guard<std::mutex> lock(lock_);
   return generated_files_.find(file) != generated_files_.end();
 }
 
+std::multimap<OutputFile, const Target*> Scheduler::GetGeneratedFiles() const {
+  std::lock_guard<std::mutex> lock(lock_);
+  return generated_files_;
+}
+
 std::multimap<SourceFile, const Target*> Scheduler::GetUnknownGeneratedInputs()
     const {
   std::lock_guard<std::mutex> lock(lock_);
diff --git a/src/gn/scheduler.h b/src/gn/scheduler.h
index cf29fe8..52e0b47 100644
--- a/src/gn/scheduler.h
+++ b/src/gn/scheduler.h
@@ -70,8 +70,17 @@
   bool IsFileGeneratedByWriteRuntimeDeps(const OutputFile& file) const;
 
   // Tracks generated_file calls.
-  void AddGeneratedFile(const SourceFile& entry);
-  bool IsFileGeneratedByTarget(const SourceFile& file) const;
+  void AddGeneratedFile(const Target* target, const OutputFile& file);
+  bool IsFileGeneratedByTarget(const OutputFile& file) const;
+
+  // Returns the collection of generated files. The associated target is
+  // tracked as well in order to determine whether the generated file actually
+  // was created (i.e., whether the target was included in the build). This
+  // target pointer must only be dereferenced after the graph is complete.
+  //
+  // TODO(crbug.com/gn/303): This should be a map but currently GN allows
+  // multiple overlapping generated files.
+  std::multimap<OutputFile, const Target*> GetGeneratedFiles() const;
 
   // Unknown generated inputs are files that a target declares as an input
   // in the output directory, but which aren't generated by any dependency.
@@ -145,7 +154,7 @@
   std::vector<SourceFile> written_files_;
   std::vector<const Target*> write_runtime_deps_targets_;
   std::multimap<SourceFile, const Target*> unknown_generated_inputs_;
-  std::map<SourceFile, bool> generated_files_;
+  std::multimap<OutputFile, const Target*> generated_files_;
 
   Scheduler(const Scheduler&) = delete;
   Scheduler& operator=(const Scheduler&) = delete;
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 7b545b5..ea6277a 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -526,8 +526,7 @@
 
   if (output_type_ == GENERATED_FILE) {
     DCHECK(!computed_outputs_.empty());
-    g_scheduler->AddGeneratedFile(
-        computed_outputs_[0].AsSourceFile(settings()->build_settings()));
+    g_scheduler->AddGeneratedFile(this, computed_outputs_[0]);
   }
 
   return true;
@@ -1221,7 +1220,7 @@
     // Allow dependency to be through data_deps for files generated by gn.
     check_data_deps =
         g_scheduler->IsFileGeneratedByWriteRuntimeDeps(out_file) ||
-        g_scheduler->IsFileGeneratedByTarget(source);
+        g_scheduler->IsFileGeneratedByTarget(out_file);
     // Check object files (much slower and very rare) only if the "normal"
     // output check failed.
     consider_object_files = !check_data_deps;