Check for inputs not generated by deps

Adds a check in GN that looks for generated input files on a target that were
not generated by a dependency of the current target. In order to depend on the
output of a previous target, that target must be in your deps.

This adds checking output files to "gn refs" to help in debugging these issues.

Relaxes a wider range of checking when doing introspection commands like
"desc", "refs", and "ls" so they can be run to debug such issues.

Adds an additional test helper for setting up test targets that saves some code. Use this in the target unittests.

Review URL: https://codereview.chromium.org/1126193005

Cr-Original-Commit-Position: refs/heads/master@{#332925}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 56affab3b067e55d692281864d98a78a96aa21cc
diff --git a/tools/gn/action_values.cc b/tools/gn/action_values.cc
index 2215167..ede8020 100644
--- a/tools/gn/action_values.cc
+++ b/tools/gn/action_values.cc
@@ -4,6 +4,7 @@
 
 #include "tools/gn/action_values.h"
 
+#include "tools/gn/settings.h"
 #include "tools/gn/substitution_writer.h"
 #include "tools/gn/target.h"
 
diff --git a/tools/gn/build_settings.cc b/tools/gn/build_settings.cc
index 3ffb828..63ff358 100644
--- a/tools/gn/build_settings.cc
+++ b/tools/gn/build_settings.cc
@@ -7,7 +7,8 @@
 #include "base/files/file_util.h"
 #include "tools/gn/filesystem_utils.h"
 
-BuildSettings::BuildSettings() {
+BuildSettings::BuildSettings()
+    : check_for_bad_items_(true) {
 }
 
 BuildSettings::BuildSettings(const BuildSettings& other)
@@ -17,7 +18,8 @@
       python_path_(other.python_path_),
       build_config_file_(other.build_config_file_),
       build_dir_(other.build_dir_),
-      build_args_(other.build_args_) {
+      build_args_(other.build_args_),
+      check_for_bad_items_(true) {
 }
 
 BuildSettings::~BuildSettings() {
diff --git a/tools/gn/build_settings.h b/tools/gn/build_settings.h
index a9752f1..f64d5b7 100644
--- a/tools/gn/build_settings.h
+++ b/tools/gn/build_settings.h
@@ -94,6 +94,18 @@
     exec_script_whitelist_ = list.Pass();
   }
 
+  // When set (the default), code should perform normal validation of inputs
+  // and structures, like undefined or possibly incorrectly used things. For
+  // some interrogation commands, we don't care about this and actually want
+  // to allow the user to check the structure of the build to solve their
+  // problem, and these checks are undesirable.
+  bool check_for_bad_items() const {
+    return check_for_bad_items_;
+  }
+  void set_check_for_bad_items(bool c) {
+    check_for_bad_items_ = c;
+  }
+
  private:
   base::FilePath root_path_;
   std::string root_path_utf8_;
@@ -109,6 +121,8 @@
 
   scoped_ptr<std::set<SourceFile>> exec_script_whitelist_;
 
+  bool check_for_bad_items_;
+
   BuildSettings& operator=(const BuildSettings& other);  // Disallow.
 };
 
diff --git a/tools/gn/command_args.cc b/tools/gn/command_args.cc
index 7210ef3..c4c16bb 100644
--- a/tools/gn/command_args.cc
+++ b/tools/gn/command_args.cc
@@ -114,7 +114,7 @@
 
 int ListArgs(const std::string& build_dir) {
   Setup* setup = new Setup;
-  setup->set_check_for_bad_items(false);
+  setup->build_settings().set_check_for_bad_items(false);
   if (!setup->DoSetup(build_dir, false) || !setup->Run())
     return 1;
 
@@ -227,7 +227,7 @@
     // Scope the setup. We only use it for some basic state. We'll do the
     // "real" build below in the gen command.
     Setup setup;
-    setup.set_check_for_bad_items(false);
+    setup.build_settings().set_check_for_bad_items(false);
     // Don't fill build arguments. We're about to edit the file which supplies
     // these in the first place.
     setup.set_fill_arguments(false);
diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc
index efb0b06..48fa31a 100644
--- a/tools/gn/command_desc.cc
+++ b/tools/gn/command_desc.cc
@@ -617,6 +617,7 @@
 
   // Deliberately leaked to avoid expensive process teardown.
   Setup* setup = new Setup;
+  setup->build_settings().set_check_for_bad_items(false);
   if (!setup->DoSetup(args[0], false))
     return 1;
   if (!setup->Run())
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc
index 76f1f36..eceb8df 100644
--- a/tools/gn/command_gen.cc
+++ b/tools/gn/command_gen.cc
@@ -6,6 +6,7 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
 #include "base/timer/elapsed_timer.h"
 #include "tools/gn/build_settings.h"
 #include "tools/gn/commands.h"
@@ -44,6 +45,89 @@
   }
 }
 
+// Returns a pointer to the target with the given file as an output, or null
+// if no targets generate the file. This is brute force since this is an
+// error condition and performance shouldn't matter.
+const Target* FindTargetThatGeneratesFile(const Builder* builder,
+                                          const SourceFile& file) {
+  std::vector<const Target*> targets = builder->GetAllResolvedTargets();
+  if (targets.empty())
+    return nullptr;
+
+  OutputFile output_file(targets[0]->settings()->build_settings(), file);
+  for (const Target* target : targets) {
+    for (const auto& cur_output : target->computed_outputs()) {
+      if (cur_output == output_file)
+        return target;
+    }
+  }
+  return nullptr;
+}
+
+// Prints an error that the given file was present as a source or input in
+// the given target(s) but was not generated by any of its dependencies.
+void PrintInvalidGeneratedInput(const Builder* builder,
+                                const SourceFile& file,
+                                const std::vector<const Target*>& targets) {
+  std::string err;
+
+  const std::string target_str = targets.size() > 1 ? "targets" : "target";
+  err += "The file:\n";
+  err += "  " + file.value() + "\n";
+  err += "is listed as an input or source for the " + target_str + ":\n";
+  for (const Target* target : targets)
+    err += "  " + target->label().GetUserVisibleName(false) + "\n";
+
+  const Target* generator = FindTargetThatGeneratesFile(builder, file);
+  if (generator) {
+    err += "but this file was not generated by any dependencies of the " +
+        target_str + ". The target\nthat generates the file is:\n  ";
+    err += generator->label().GetUserVisibleName(false);
+  } else {
+    err += "but no targets in the build generate that file.";
+  }
+
+  Err(Location(), "Input to " + target_str + " not generated by a dependency.",
+      err).PrintToStdout();
+}
+
+bool CheckForInvalidGeneratedInputs(Setup* setup) {
+  std::multimap<SourceFile, const Target*> unknown_inputs =
+      g_scheduler->GetUnknownGeneratedInputs();
+  if (unknown_inputs.empty())
+    return true;  // No bad files.
+
+  int errors_found = 0;
+  auto cur = unknown_inputs.begin();
+  while (cur != unknown_inputs.end()) {
+    errors_found++;
+    auto end_of_range = unknown_inputs.upper_bound(cur->first);
+
+    // Package the values more conveniently for printing.
+    SourceFile bad_input = cur->first;
+    std::vector<const Target*> targets;
+    while (cur != end_of_range)
+      targets.push_back((cur++)->second);
+
+    PrintInvalidGeneratedInput(setup->builder(), bad_input, targets);
+    OutputString("\n");
+  }
+
+  OutputString(
+      "If you have generated inputs, there needs to be a dependency path "
+      "between the\ntwo targets in addition to just listing the files. For "
+      "indirect dependencies,\nthe intermediate ones must be public_deps. "
+      "data_deps don't count since they're\nonly runtime dependencies. If "
+      "you think a dependency chain exists, it might be\nbecause the chain "
+      "is private. Try \"gn path\" to analyze.\n");
+
+  if (errors_found > 1) {
+    OutputString(base::StringPrintf("\n%d generated input errors found.\n",
+                                    errors_found), DECORATION_YELLOW);
+  }
+  return false;
+}
+
 }  // namespace
 
 const char kGen[] = "gen";
@@ -107,6 +191,9 @@
     return 1;
   }
 
+  if (!CheckForInvalidGeneratedInputs(setup))
+    return 1;
+
   base::TimeDelta elapsed_time = timer.Elapsed();
 
   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) {
diff --git a/tools/gn/command_ls.cc b/tools/gn/command_ls.cc
index ed4aaca..eddb51c 100644
--- a/tools/gn/command_ls.cc
+++ b/tools/gn/command_ls.cc
@@ -76,6 +76,7 @@
   }
 
   Setup* setup = new Setup;
+  setup->build_settings().set_check_for_bad_items(false);
   if (!setup->DoSetup(args[0], false) || !setup->Run())
     return 1;
 
diff --git a/tools/gn/command_refs.cc b/tools/gn/command_refs.cc
index 9c89114..a97e9a7 100644
--- a/tools/gn/command_refs.cc
+++ b/tools/gn/command_refs.cc
@@ -141,6 +141,13 @@
     if (cur_file == file.value())
       return true;
   }
+
+  std::vector<SourceFile> outputs;
+  target->action_values().GetOutputsAsSourceFiles(target, &outputs);
+  for (const auto& cur_file : outputs) {
+    if (cur_file == file)
+      return true;
+  }
   return false;
 }
 
@@ -286,9 +293,9 @@
     "     \"gn help label_pattern\" for details.\n"
     "\n"
     "   - File name: The result will be which targets list the given file in\n"
-    "     its \"inputs\", \"sources\", \"public\", or \"data\". Any input\n"
-    "     that does not contain wildcards and does not match a target or a\n"
-    "     config will be treated as a file.\n"
+    "     its \"inputs\", \"sources\", \"public\", \"data\", or \"outputs\".\n"
+    "     Any input that does not contain wildcards and does not match a\n"
+    "     target or a config will be treated as a file.\n"
     "\n"
     "   - Response file: If the input starts with an \"@\", it will be\n"
     "     interpreted as a path to a file containing a list of labels or\n"
@@ -391,7 +398,7 @@
   bool all_toolchains = cmdline->HasSwitch("all-toolchains");
 
   Setup* setup = new Setup;
-  setup->set_check_for_bad_items(false);
+  setup->build_settings().set_check_for_bad_items(false);
   if (!setup->DoSetup(args[0], false) || !setup->Run())
     return 1;
 
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index 7495b05..325cfc6 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -310,14 +310,18 @@
   return base::StringPiece(&dir_string[0], end);
 }
 
-bool EnsureStringIsInOutputDir(const SourceDir& dir,
+bool IsStringInOutputDir(const SourceDir& output_dir, const std::string& str) {
+  // This check will be wrong for all proper prefixes "e.g. "/output" will
+  // match "/out" but we don't really care since this is just a sanity check.
+  const std::string& dir_str = output_dir.value();
+  return str.compare(0, dir_str.length(), dir_str) == 0;
+}
+
+bool EnsureStringIsInOutputDir(const SourceDir& output_dir,
                                const std::string& str,
                                const ParseNode* origin,
                                Err* err) {
-  // This check will be wrong for all proper prefixes "e.g. "/output" will
-  // match "/out" but we don't really care since this is just a sanity check.
-  const std::string& dir_str = dir.value();
-  if (str.compare(0, dir_str.length(), dir_str) == 0)
+  if (IsStringInOutputDir(output_dir, str))
     return true;  // Output directory is hardcoded.
 
   *err = Err(origin, "File is not inside output directory.",
diff --git a/tools/gn/filesystem_utils.h b/tools/gn/filesystem_utils.h
index 52352c6..4f258e1 100644
--- a/tools/gn/filesystem_utils.h
+++ b/tools/gn/filesystem_utils.h
@@ -82,16 +82,20 @@
 // empty substring if none. For example "//foo/bar/" -> "bar".
 base::StringPiece FindLastDirComponent(const SourceDir& dir);
 
+// Returns true if the given string is in the given output dir. This is pretty
+// stupid and doesn't handle "." and "..", etc., it is designed for a sanity
+// check to keep people from writing output files to the source directory
+// accidentally.
+bool IsStringInOutputDir(const SourceDir& output_dir, const std::string& str);
+
 // Verifies that the given string references a file inside of the given
-// directory. This is pretty stupid and doesn't handle "." and "..", etc.,
-// it is designed for a sanity check to keep people from writing output files
-// to the source directory accidentally.
+// directory. This just uses IsStringInOutputDir above.
 //
 // The origin will be blamed in the error.
 //
 // If the file isn't in the dir, returns false and sets the error. Otherwise
 // returns true and leaves the error untouched.
-bool EnsureStringIsInOutputDir(const SourceDir& dir,
+bool EnsureStringIsInOutputDir(const SourceDir& output_dir,
                                const std::string& str,
                                const ParseNode* origin,
                                Err* err);
diff --git a/tools/gn/function_write_file.cc b/tools/gn/function_write_file.cc
index 8a6fee3..45387a3 100644
--- a/tools/gn/function_write_file.cc
+++ b/tools/gn/function_write_file.cc
@@ -21,14 +21,18 @@
 namespace {
 
 // On Windows, provide a custom implementation of base::WriteFile. Sometimes
-// the base version would fail, and this alternate implementation provides
-// additional logging. See http://crbug.com/468437
+// the base version fails, especially on the bots. The guess is that Windows
+// Defender or other antivirus programs still have the file open (after
+// checking for the read) when the write happens immediately after. This
+// version opens with FILE_SHARE_READ (normally not what you want when
+// replacing the entire contents of the file) which lets us continue even if
+// another program has the file open for reading. See http://crbug.com/468437
 #if defined(OS_WIN)
 int DoWriteFile(const base::FilePath& filename, const char* data, int size) {
   base::win::ScopedHandle file(::CreateFile(
       filename.value().c_str(),
       GENERIC_WRITE,
-      FILE_SHARE_READ,  // Not present in the base version, speculative fix.
+      FILE_SHARE_READ,
       NULL,
       CREATE_ALWAYS,
       0,
@@ -109,6 +113,7 @@
           scope->settings()->build_settings()->build_dir(),
           source_file.value(), args[0].origin(), err))
     return Value();
+  g_scheduler->AddWrittenFile(source_file);  // Track that we wrote this file.
 
   // Compute output.
   std::ostringstream contents;
diff --git a/tools/gn/function_write_file_unittest.cc b/tools/gn/function_write_file_unittest.cc
index 90bfa4c..8212e99 100644
--- a/tools/gn/function_write_file_unittest.cc
+++ b/tools/gn/function_write_file_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "tools/gn/functions.h"
+#include "tools/gn/scheduler.h"
 #include "tools/gn/test_with_scope.h"
 
 namespace {
@@ -31,6 +32,7 @@
 }  // namespace
 
 TEST(WriteFile, WithData) {
+  Scheduler scheduler;
   TestWithScope setup;
 
   // Make a real directory for writing the files.
diff --git a/tools/gn/scheduler.cc b/tools/gn/scheduler.cc
index 488149b..622019e 100644
--- a/tools/gn/scheduler.cc
+++ b/tools/gn/scheduler.cc
@@ -108,6 +108,39 @@
   return gen_dependencies_;
 }
 
+void Scheduler::AddWrittenFile(const SourceFile& file) {
+  base::AutoLock lock(lock_);
+  written_files_.push_back(file);
+}
+
+void Scheduler::AddUnknownGeneratedInput(const Target* target,
+                                         const SourceFile& file) {
+  base::AutoLock lock(lock_);
+  unknown_generated_inputs_.insert(std::make_pair(file, target));
+}
+
+std::multimap<SourceFile, const Target*>
+    Scheduler::GetUnknownGeneratedInputs() const {
+  base::AutoLock lock(lock_);
+
+  // Remove all unknown inputs that were written files. These are OK as inputs
+  // to build steps since they were written as a side-effect of running GN.
+  //
+  // It's assumed that this function is called once during cleanup to check for
+  // errors, so performing this work in the lock doesn't matter.
+  std::multimap<SourceFile, const Target*> filtered = unknown_generated_inputs_;
+  for (const SourceFile& file : written_files_)
+    filtered.erase(file);
+
+  return filtered;
+}
+
+void Scheduler::ClearUnknownGeneratedInputsAndWrittenFiles() {
+  base::AutoLock lock(lock_);
+  unknown_generated_inputs_.clear();
+  written_files_.clear();
+}
+
 void Scheduler::IncrementWorkCount() {
   base::AtomicRefCountInc(&work_count_);
 }
diff --git a/tools/gn/scheduler.h b/tools/gn/scheduler.h
index 912ca7e..4375f63 100644
--- a/tools/gn/scheduler.h
+++ b/tools/gn/scheduler.h
@@ -5,6 +5,8 @@
 #ifndef TOOLS_GN_SCHEDULER_H_
 #define TOOLS_GN_SCHEDULER_H_
 
+#include <map>
+
 #include "base/atomic_ref_count.h"
 #include "base/basictypes.h"
 #include "base/files/file_path.h"
@@ -47,9 +49,33 @@
   // TODO(brettw) this is global rather than per-BuildSettings. If we
   // start using >1 build settings, then we probably want this to take a
   // BuildSettings object so we know the depdency on a per-build basis.
+  // If moved, most of the Add/Get functions below should move as well.
   void AddGenDependency(const base::FilePath& file);
   std::vector<base::FilePath> GetGenDependencies() const;
 
+  // Tracks calls to write_file for resolving with the unknown generated
+  // inputs (see AddUnknownGeneratedInput below).
+  void AddWrittenFile(const SourceFile& file);
+
+  // Unknown generated inputs are files that a target declares as an input
+  // in the output directory, but which aren't generated by any dependency.
+  //
+  // Some of these files will be files written by write_file and will be
+  // GenDependencies (see AddWrittenFile above). There are OK and include
+  // things like response files for scripts. Others cases will be ones where
+  // the file is generated by a target that's not a dependency.
+  //
+  // In order to distinguish these two cases, the checking for these input
+  // files needs to be done after all targets are complete. This also has the
+  // nice side effect that if a target generates the file we can find it and
+  // tell the user which dependency is missing.
+  //
+  // The result returned by GetUnknownGeneratedInputs will not count any files
+  // that were written by write_file during execution.
+  void AddUnknownGeneratedInput(const Target* target, const SourceFile& file);
+  std::multimap<SourceFile, const Target*> GetUnknownGeneratedInputs() const;
+  void ClearUnknownGeneratedInputsAndWrittenFiles();  // For testing.
+
   // We maintain a count of the things we need to do that works like a
   // refcount. When this reaches 0, the program exits.
   void IncrementWorkCount();
@@ -84,8 +110,10 @@
   // loop.
   bool has_been_shutdown_;
 
-  // Additional input dependencies. Protected by the lock.
+  // Protected by the lock. See the corresponding Add/Get functions above.
   std::vector<base::FilePath> gen_dependencies_;
+  std::vector<SourceFile> written_files_;
+  std::multimap<SourceFile, const Target*> unknown_generated_inputs_;
 
   DISALLOW_COPY_AND_ASSIGN(Scheduler);
 };
diff --git a/tools/gn/setup.cc b/tools/gn/setup.cc
index 79c46e7..aa8e8f0 100644
--- a/tools/gn/setup.cc
+++ b/tools/gn/setup.cc
@@ -157,8 +157,6 @@
       loader_(new LoaderImpl(&build_settings_)),
       builder_(new Builder(loader_.get())),
       root_build_file_("//BUILD.gn"),
-      check_for_bad_items_(true),
-      check_for_unused_overrides_(true),
       check_public_headers_(false),
       dotfile_settings_(&build_settings_, std::string()),
       dotfile_scope_(&dotfile_settings_),
@@ -234,14 +232,12 @@
 
 bool Setup::RunPostMessageLoop() {
   Err err;
-  if (check_for_bad_items_) {
+  if (build_settings_.check_for_bad_items()) {
     if (!builder_->CheckForBadItems(&err)) {
       err.PrintToStdout();
       return false;
     }
-  }
 
-  if (check_for_unused_overrides_) {
     if (!build_settings_.build_args().VerifyAllOverridesUsed(&err)) {
       // TODO(brettw) implement a system of warnings. Until we have a better
       // system, print the error but don't return failure.
diff --git a/tools/gn/setup.h b/tools/gn/setup.h
index 2e028e3..9c87367 100644
--- a/tools/gn/setup.h
+++ b/tools/gn/setup.h
@@ -68,16 +68,6 @@
   // want to rely on them being valid.
   void set_fill_arguments(bool fa) { fill_arguments_ = fa; }
 
-  // When true (the default), Run() will check for unresolved dependencies and
-  // cycles upon completion. When false, such errors will be ignored.
-  void set_check_for_bad_items(bool s) { check_for_bad_items_ = s; }
-
-  // When true (the default), RunPostMessageLoop will check for overrides that
-  // were specified but not used. When false, such errors will be ignored.
-  void set_check_for_unused_overrides(bool s) {
-    check_for_unused_overrides_ = s;
-  }
-
   // After a successful run, setting this will additionally cause the public
   // headers to be checked. Defaults to false.
   void set_check_public_headers(bool s) {
@@ -142,8 +132,6 @@
 
   SourceFile root_build_file_;
 
-  bool check_for_bad_items_;
-  bool check_for_unused_overrides_;
   bool check_public_headers_;
 
   // See getter for info.
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 40c8a21..2ea2859 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -60,6 +60,47 @@
                  "Use source sets for intermediate targets instead.");
 }
 
+// Set check_private_deps to true for the first invocation since a target
+// can see all of its dependencies. For recursive invocations this will be set
+// to false to follow only public dependency paths.
+//
+// Pass a pointer to an empty set for the first invocation. This will be used
+// to avoid duplicate checking.
+bool EnsureFileIsGeneratedByDependency(const Target* target,
+                                       const OutputFile& file,
+                                       bool check_private_deps,
+                                       std::set<const Target*>* seen_targets) {
+  if (seen_targets->find(target) != seen_targets->end())
+    return false;  // Already checked this one and it's not found.
+  seen_targets->insert(target);
+
+  // Assume that we have relatively few generated inputs so brute-force
+  // searching here is OK. If this becomes a bottleneck, consider storing
+  // computed_outputs as a hash set.
+  for (const OutputFile& cur : target->computed_outputs()) {
+    if (file == cur)
+      return true;
+  }
+
+  // Check all public dependencies (don't do data ones since those are
+  // runtime-only).
+  for (const auto& pair : target->public_deps()) {
+    if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
+                                          seen_targets))
+      return true;  // Found a path.
+  }
+
+  // Only check private deps if requested.
+  if (check_private_deps) {
+    for (const auto& pair : target->private_deps()) {
+      if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
+                                            seen_targets))
+        return true;  // Found a path.
+    }
+  }
+  return false;
+}
+
 }  // namespace
 
 Target::Target(const Settings* settings, const Label& label)
@@ -132,12 +173,15 @@
 
   FillOutputFiles();
 
-  if (!CheckVisibility(err))
-    return false;
-  if (!CheckTestonly(err))
-    return false;
-  if (!CheckNoNestedStaticLibs(err))
-    return false;
+  if (settings()->build_settings()->check_for_bad_items()) {
+    if (!CheckVisibility(err))
+      return false;
+    if (!CheckTestonly(err))
+      return false;
+    if (!CheckNoNestedStaticLibs(err))
+      return false;
+    CheckSourcesGenerated();
+  }
 
   return true;
 }
@@ -304,6 +348,7 @@
 
 void Target::FillOutputFiles() {
   const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
+  bool check_tool_outputs = false;
   switch (output_type_) {
     case GROUP:
     case SOURCE_SET:
@@ -322,6 +367,7 @@
       // Executables don't get linked to, but the first output is used for
       // dependency management.
       CHECK_GE(tool->outputs().list().size(), 1u);
+      check_tool_outputs = true;
       dependency_output_file_ =
           SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
               this, tool, tool->outputs().list()[0]);
@@ -330,12 +376,14 @@
       // Static libraries both have dependencies and linking going off of the
       // first output.
       CHECK(tool->outputs().list().size() >= 1);
+      check_tool_outputs = true;
       link_output_file_ = dependency_output_file_ =
           SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
               this, tool, tool->outputs().list()[0]);
       break;
     case SHARED_LIBRARY:
       CHECK(tool->outputs().list().size() >= 1);
+      check_tool_outputs = true;
       if (tool->link_output().empty() && tool->depend_output().empty()) {
         // Default behavior, use the first output file for both.
         link_output_file_ = dependency_output_file_ =
@@ -359,6 +407,26 @@
     default:
       NOTREACHED();
   }
+
+  // Count all outputs from this tool as something generated by this target.
+  if (check_tool_outputs) {
+    SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+        this, tool, tool->outputs(), &computed_outputs_);
+
+    // Output names aren't canonicalized in the same way that source files
+    // are. For example, the tool outputs often use
+    // {{some_var}}/{{output_name}} which expands to "./foo", but this won't
+    // match "foo" which is what we'll compute when converting a SourceFile to
+    // an OutputFile.
+    for (auto& out : computed_outputs_)
+      NormalizePath(&out.value());
+  }
+
+  // Also count anything the target has declared to be an output.
+  std::vector<SourceFile> outputs_as_sources;
+  action_values_.GetOutputsAsSourceFiles(this, &outputs_as_sources);
+  for (const SourceFile& out : outputs_as_sources)
+    computed_outputs_.push_back(OutputFile(settings()->build_settings(), out));
 }
 
 bool Target::CheckVisibility(Err* err) const {
@@ -409,3 +477,30 @@
   }
   return true;
 }
+
+void Target::CheckSourcesGenerated() const {
+  // Checks that any inputs or sources to this target that are in the build
+  // directory are generated by a target that this one transitively depends on
+  // in some way. We already guarantee that all generated files are written
+  // to the build dir.
+  //
+  // See Scheduler::AddUnknownGeneratedInput's declaration for more.
+  for (const SourceFile& file : sources_)
+    CheckSourceGenerated(file);
+  for (const SourceFile& file : inputs_)
+    CheckSourceGenerated(file);
+}
+
+void Target::CheckSourceGenerated(const SourceFile& source) const {
+  if (!IsStringInOutputDir(settings()->build_settings()->build_dir(),
+                           source.value()))
+    return;  // Not in output dir, this is OK.
+
+  // Tell the scheduler about unknown files. This will be noted for later so
+  // the list of files written by the GN build itself (often response files)
+  // can be filtered out of this list.
+  OutputFile out_file(settings()->build_settings(), source);
+  std::set<const Target*> seen_targets;
+  if (!EnsureFileIsGeneratedByDependency(this, out_file, true, &seen_targets))
+    g_scheduler->AddUnknownGeneratedInput(this, source);
+}
diff --git a/tools/gn/target.h b/tools/gn/target.h
index 0050048..58319f1 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -221,6 +221,16 @@
   // frequently by unit tests which become needlessly verbose.
   bool SetToolchain(const Toolchain* toolchain, Err* err = nullptr);
 
+  // Once this target has been resolved, all outputs from the target will be
+  // listed here. This will include things listed in the "outputs" for an
+  // action or a copy step, and the output library or executable file(s) from
+  // binary targets.
+  //
+  // It will NOT include stamp files and object files.
+  const std::vector<OutputFile>& computed_outputs() const {
+    return computed_outputs_;
+  }
+
   // Returns outputs from this target. The link output file is the one that
   // other targets link to when they depend on this target. This will only be
   // valid for libraries and will be empty for all other target types.
@@ -260,6 +270,8 @@
   bool CheckVisibility(Err* err) const;
   bool CheckTestonly(Err* err) const;
   bool CheckNoNestedStaticLibs(Err* err) const;
+  void CheckSourcesGenerated() const;
+  void CheckSourceGenerated(const SourceFile& source) const;
 
   OutputType output_type_;
   std::string output_name_;
@@ -304,7 +316,8 @@
   // Toolchain used by this target. Null until target is resolved.
   const Toolchain* toolchain_;
 
-  // Output files. Null until the target is resolved.
+  // Output files. Empty until the target is resolved.
+  std::vector<OutputFile> computed_outputs_;
   OutputFile link_output_file_;
   OutputFile dependency_output_file_;
 
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index 020d432..48fb84c 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -5,11 +5,30 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "tools/gn/build_settings.h"
 #include "tools/gn/config.h"
+#include "tools/gn/scheduler.h"
 #include "tools/gn/settings.h"
 #include "tools/gn/target.h"
 #include "tools/gn/test_with_scope.h"
 #include "tools/gn/toolchain.h"
 
+namespace {
+
+// Asserts that the current global scheduler has a single unknown generated
+// file with the given name from the given target.
+void AssertSchedulerHasOneUnknownFileMatching(const Target* target,
+                                              const SourceFile& file) {
+  auto unknown = g_scheduler->GetUnknownGeneratedInputs();
+  ASSERT_EQ(1u, unknown.size());  // Should be one unknown file.
+  auto found = unknown.find(file);
+  ASSERT_TRUE(found != unknown.end()) << file.value();
+  EXPECT_TRUE(target == found->second)
+      << "Target doesn't match. Expected\n  "
+      << target->label().GetUserVisibleName(false)
+      << "\nBut got\n  " << found->second->label().GetUserVisibleName(false);
+}
+
+}  // namespace
+
 // Tests that lib[_dir]s are inherited across deps boundaries for static
 // libraries but not executables.
 TEST(Target, LibInheritance) {
@@ -20,12 +39,9 @@
   const SourceDir libdir("/foo_dir/");
 
   // Leaf target with ldflags set.
-  Target z(setup.settings(), Label(SourceDir("//foo/"), "z"));
-  z.set_output_type(Target::STATIC_LIBRARY);
+  TestTarget z(setup, "//foo:z", Target::STATIC_LIBRARY);
   z.config_values().libs().push_back(lib);
   z.config_values().lib_dirs().push_back(libdir);
-  z.visibility().SetPublic();
-  z.SetToolchain(setup.toolchain());
   ASSERT_TRUE(z.OnResolved(&err));
 
   // All lib[_dir]s should be set when target is resolved.
@@ -38,13 +54,10 @@
   // and its own. Its own flag should be before the inherited one.
   const std::string second_lib("bar");
   const SourceDir second_libdir("/bar_dir/");
-  Target shared(setup.settings(), Label(SourceDir("//foo/"), "shared"));
-  shared.set_output_type(Target::SHARED_LIBRARY);
+  TestTarget shared(setup, "//foo:shared", Target::SHARED_LIBRARY);
   shared.config_values().libs().push_back(second_lib);
   shared.config_values().lib_dirs().push_back(second_libdir);
   shared.private_deps().push_back(LabelTargetPair(&z));
-  shared.visibility().SetPublic();
-  shared.SetToolchain(setup.toolchain());
   ASSERT_TRUE(shared.OnResolved(&err));
 
   ASSERT_EQ(2u, shared.all_libs().size());
@@ -55,10 +68,8 @@
   EXPECT_EQ(libdir, shared.all_lib_dirs()[1]);
 
   // Executable target shouldn't get either by depending on shared.
-  Target exec(setup.settings(), Label(SourceDir("//foo/"), "exec"));
-  exec.set_output_type(Target::EXECUTABLE);
+  TestTarget exec(setup, "//foo:exec", Target::EXECUTABLE);
   exec.private_deps().push_back(LabelTargetPair(&shared));
-  exec.SetToolchain(setup.toolchain());
   ASSERT_TRUE(exec.OnResolved(&err));
   EXPECT_EQ(0u, exec.all_libs().size());
   EXPECT_EQ(0u, exec.all_lib_dirs().size());
@@ -71,18 +82,9 @@
   Err err;
 
   // Set up a dependency chain of a -> b -> c
-  Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
-  a.set_output_type(Target::EXECUTABLE);
-  a.visibility().SetPublic();
-  a.SetToolchain(setup.toolchain());
-  Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
-  b.set_output_type(Target::STATIC_LIBRARY);
-  b.visibility().SetPublic();
-  b.SetToolchain(setup.toolchain());
-  Target c(setup.settings(), Label(SourceDir("//foo/"), "c"));
-  c.set_output_type(Target::STATIC_LIBRARY);
-  c.visibility().SetPublic();
-  c.SetToolchain(setup.toolchain());
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
   a.private_deps().push_back(LabelTargetPair(&b));
   b.private_deps().push_back(LabelTargetPair(&c));
 
@@ -115,14 +117,8 @@
   EXPECT_EQ(&all, a.all_dependent_configs()[0].ptr);
 
   // Making an an alternate A and B with B forwarding the direct dependents.
-  Target a_fwd(setup.settings(), Label(SourceDir("//foo/"), "a_fwd"));
-  a_fwd.set_output_type(Target::EXECUTABLE);
-  a_fwd.visibility().SetPublic();
-  a_fwd.SetToolchain(setup.toolchain());
-  Target b_fwd(setup.settings(), Label(SourceDir("//foo/"), "b_fwd"));
-  b_fwd.set_output_type(Target::STATIC_LIBRARY);
-  b_fwd.SetToolchain(setup.toolchain());
-  b_fwd.visibility().SetPublic();
+  TestTarget a_fwd(setup, "//foo:a_fwd", Target::EXECUTABLE);
+  TestTarget b_fwd(setup, "//foo:b_fwd", Target::STATIC_LIBRARY);
   a_fwd.private_deps().push_back(LabelTargetPair(&b_fwd));
   b_fwd.private_deps().push_back(LabelTargetPair(&c));
   b_fwd.forward_dependent_configs().push_back(LabelTargetPair(&c));
@@ -144,22 +140,10 @@
 
   // Create a dependency chain:
   //   A (executable) -> B (shared lib) -> C (static lib) -> D (source set)
-  Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
-  a.set_output_type(Target::EXECUTABLE);
-  a.visibility().SetPublic();
-  a.SetToolchain(setup.toolchain());
-  Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
-  b.set_output_type(Target::SHARED_LIBRARY);
-  b.visibility().SetPublic();
-  b.SetToolchain(setup.toolchain());
-  Target c(setup.settings(), Label(SourceDir("//foo/"), "c"));
-  c.set_output_type(Target::STATIC_LIBRARY);
-  c.visibility().SetPublic();
-  c.SetToolchain(setup.toolchain());
-  Target d(setup.settings(), Label(SourceDir("//foo/"), "d"));
-  d.set_output_type(Target::SOURCE_SET);
-  d.visibility().SetPublic();
-  d.SetToolchain(setup.toolchain());
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::SHARED_LIBRARY);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  TestTarget d(setup, "//foo:d", Target::SOURCE_SET);
   a.private_deps().push_back(LabelTargetPair(&b));
   b.private_deps().push_back(LabelTargetPair(&c));
   c.private_deps().push_back(LabelTargetPair(&d));
@@ -193,19 +177,10 @@
 
   // Create a dependency chain:
   //   A (executable) -> B (complete static lib) -> C (source set)
-  Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
-  a.set_output_type(Target::EXECUTABLE);
-  a.visibility().SetPublic();
-  a.SetToolchain(setup.toolchain());
-  Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
-  b.set_output_type(Target::STATIC_LIBRARY);
-  b.visibility().SetPublic();
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
   b.set_complete_static_lib(true);
-  b.SetToolchain(setup.toolchain());
-  Target c(setup.settings(), Label(SourceDir("//foo/"), "c"));
-  c.set_output_type(Target::SOURCE_SET);
-  c.visibility().SetPublic();
-  c.SetToolchain(setup.toolchain());
+  TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
   a.public_deps().push_back(LabelTargetPair(&b));
   b.public_deps().push_back(LabelTargetPair(&c));
 
@@ -231,15 +206,9 @@
 
   // Create a dependency chain:
   //   A (complete static lib) -> B (static lib)
-  Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
-  a.set_output_type(Target::STATIC_LIBRARY);
-  a.visibility().SetPublic();
+  TestTarget a(setup, "//foo:a", Target::STATIC_LIBRARY);
   a.set_complete_static_lib(true);
-  a.SetToolchain(setup.toolchain());
-  Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
-  b.set_output_type(Target::STATIC_LIBRARY);
-  b.visibility().SetPublic();
-  b.SetToolchain(setup.toolchain());
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
 
   a.public_deps().push_back(LabelTargetPair(&b));
   ASSERT_TRUE(b.OnResolved(&err));
@@ -252,19 +221,10 @@
 
   // Create a dependency chain:
   //   A (complete static lib) -> B (source set) -> C (static lib)
-  Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
-  a.set_output_type(Target::STATIC_LIBRARY);
-  a.visibility().SetPublic();
+  TestTarget a(setup, "//foo:a", Target::STATIC_LIBRARY);
   a.set_complete_static_lib(true);
-  a.SetToolchain(setup.toolchain());
-  Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
-  b.set_output_type(Target::SOURCE_SET);
-  b.visibility().SetPublic();
-  b.SetToolchain(setup.toolchain());
-  Target c(setup.settings(), Label(SourceDir("//foo/"), "c"));
-  c.set_output_type(Target::STATIC_LIBRARY);
-  c.visibility().SetPublic();
-  c.SetToolchain(setup.toolchain());
+  TestTarget b(setup, "//foo:b", Target::SOURCE_SET);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
 
   a.public_deps().push_back(LabelTargetPair(&b));
   b.public_deps().push_back(LabelTargetPair(&c));
@@ -280,37 +240,29 @@
 
   // Basic target with no prefix (executable type tool in the TestWithScope has
   // no prefix) or output name.
-  Target basic(setup.settings(), Label(SourceDir("//foo/"), "bar"));
-  basic.set_output_type(Target::EXECUTABLE);
-  basic.SetToolchain(setup.toolchain());
+  TestTarget basic(setup, "//foo:bar", Target::EXECUTABLE);
   ASSERT_TRUE(basic.OnResolved(&err));
   EXPECT_EQ("bar", basic.GetComputedOutputName(false));
   EXPECT_EQ("bar", basic.GetComputedOutputName(true));
 
   // Target with no prefix but an output name.
-  Target with_name(setup.settings(), Label(SourceDir("//foo/"), "bar"));
-  with_name.set_output_type(Target::EXECUTABLE);
+  TestTarget with_name(setup, "//foo:bar", Target::EXECUTABLE);
   with_name.set_output_name("myoutput");
-  with_name.SetToolchain(setup.toolchain());
   ASSERT_TRUE(with_name.OnResolved(&err));
   EXPECT_EQ("myoutput", with_name.GetComputedOutputName(false));
   EXPECT_EQ("myoutput", with_name.GetComputedOutputName(true));
 
   // Target with a "lib" prefix (the static library tool in the TestWithScope
   // should specify a "lib" output prefix).
-  Target with_prefix(setup.settings(), Label(SourceDir("//foo/"), "bar"));
-  with_prefix.set_output_type(Target::STATIC_LIBRARY);
-  with_prefix.SetToolchain(setup.toolchain());
+  TestTarget with_prefix(setup, "//foo:bar", Target::STATIC_LIBRARY);
   ASSERT_TRUE(with_prefix.OnResolved(&err));
   EXPECT_EQ("bar", with_prefix.GetComputedOutputName(false));
   EXPECT_EQ("libbar", with_prefix.GetComputedOutputName(true));
 
   // Target with a "lib" prefix that already has it applied. The prefix should
   // not duplicate something already in the target name.
-  Target dup_prefix(setup.settings(), Label(SourceDir("//foo/"), "bar"));
-  dup_prefix.set_output_type(Target::STATIC_LIBRARY);
+  TestTarget dup_prefix(setup, "//foo:bar", Target::STATIC_LIBRARY);
   dup_prefix.set_output_name("libbar");
-  dup_prefix.SetToolchain(setup.toolchain());
   ASSERT_TRUE(dup_prefix.OnResolved(&err));
   EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(false));
   EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(true));
@@ -321,20 +273,16 @@
   TestWithScope setup;
   Err err;
 
-  Target b(setup.settings(), Label(SourceDir("//private/"), "b"));
-  b.set_output_type(Target::STATIC_LIBRARY);
-  b.SetToolchain(setup.toolchain());
+  TestTarget b(setup, "//private:b", Target::STATIC_LIBRARY);
   b.visibility().SetPrivate(b.label().dir());
   ASSERT_TRUE(b.OnResolved(&err));
 
   // Make a target depending on "b". The dependency must have an origin to mark
   // it as user-set so we check visibility. This check should fail.
-  Target a(setup.settings(), Label(SourceDir("//app/"), "a"));
-  a.set_output_type(Target::EXECUTABLE);
+  TestTarget a(setup, "//app:a", Target::EXECUTABLE);
   a.private_deps().push_back(LabelTargetPair(&b));
   IdentifierNode origin;  // Dummy origin.
   a.private_deps()[0].origin = &origin;
-  a.SetToolchain(setup.toolchain());
   ASSERT_FALSE(a.OnResolved(&err));
 }
 
@@ -343,20 +291,15 @@
   TestWithScope setup;
   Err err;
 
-  Target b(setup.settings(), Label(SourceDir("//public/"), "b"));
-  b.set_output_type(Target::STATIC_LIBRARY);
-  b.SetToolchain(setup.toolchain());
-  b.visibility().SetPublic();
+  TestTarget b(setup, "//public:b", Target::STATIC_LIBRARY);
   ASSERT_TRUE(b.OnResolved(&err));
 
   // Make a target depending on "b". The dependency must have an origin to mark
   // it as user-set so we check visibility. This check should fail.
-  Target a(setup.settings(), Label(SourceDir("//app/"), "a"));
-  a.set_output_type(Target::EXECUTABLE);
+  TestTarget a(setup, "//app:a", Target::EXECUTABLE);
   a.data_deps().push_back(LabelTargetPair(&b));
   IdentifierNode origin;  // Dummy origin.
   a.data_deps()[0].origin = &origin;
-  a.SetToolchain(setup.toolchain());
   ASSERT_TRUE(a.OnResolved(&err)) << err.help_text();
 }
 
@@ -370,27 +313,20 @@
 
   // B has private visibility. This lets the group see it since the group is in
   // the same directory.
-  Target b(setup.settings(), Label(SourceDir("//private/"), "b"));
-  b.set_output_type(Target::STATIC_LIBRARY);
-  b.SetToolchain(setup.toolchain());
+  TestTarget b(setup, "//private:b", Target::STATIC_LIBRARY);
   b.visibility().SetPrivate(b.label().dir());
   ASSERT_TRUE(b.OnResolved(&err));
 
   // The group has public visibility and depends on b.
-  Target g(setup.settings(), Label(SourceDir("//private/"), "g"));
-  g.set_output_type(Target::GROUP);
-  g.SetToolchain(setup.toolchain());
+  TestTarget g(setup, "//public:g", Target::GROUP);
   g.private_deps().push_back(LabelTargetPair(&b));
   g.private_deps()[0].origin = &origin;
-  g.visibility().SetPublic();
   ASSERT_TRUE(b.OnResolved(&err));
 
   // Make a target depending on "g". This should succeed.
-  Target a(setup.settings(), Label(SourceDir("//app/"), "a"));
-  a.set_output_type(Target::EXECUTABLE);
+  TestTarget a(setup, "//app:a", Target::EXECUTABLE);
   a.private_deps().push_back(LabelTargetPair(&g));
   a.private_deps()[0].origin = &origin;
-  a.SetToolchain(setup.toolchain());
   ASSERT_TRUE(a.OnResolved(&err));
 }
 
@@ -402,27 +338,20 @@
   Err err;
 
   // "testlib" is a test-only library.
-  Target testlib(setup.settings(), Label(SourceDir("//test/"), "testlib"));
+  TestTarget testlib(setup, "//test:testlib", Target::STATIC_LIBRARY);
   testlib.set_testonly(true);
-  testlib.set_output_type(Target::STATIC_LIBRARY);
-  testlib.visibility().SetPublic();
-  testlib.SetToolchain(setup.toolchain());
   ASSERT_TRUE(testlib.OnResolved(&err));
 
   // "test" is a test-only executable depending on testlib, this is OK.
-  Target test(setup.settings(), Label(SourceDir("//test/"), "test"));
+  TestTarget test(setup, "//test:test", Target::EXECUTABLE);
   test.set_testonly(true);
-  test.set_output_type(Target::EXECUTABLE);
   test.private_deps().push_back(LabelTargetPair(&testlib));
-  test.SetToolchain(setup.toolchain());
   ASSERT_TRUE(test.OnResolved(&err));
 
   // "product" is a non-test depending on testlib. This should fail.
-  Target product(setup.settings(), Label(SourceDir("//app/"), "product"));
+  TestTarget product(setup, "//app:product", Target::EXECUTABLE);
   product.set_testonly(false);
-  product.set_output_type(Target::EXECUTABLE);
   product.private_deps().push_back(LabelTargetPair(&testlib));
-  product.SetToolchain(setup.toolchain());
   ASSERT_FALSE(product.OnResolved(&err));
 }
 
@@ -434,46 +363,31 @@
   Config pub_config(setup.settings(), pub_config_label);
 
   // This is the destination target that has a public config.
-  Target dest(setup.settings(), Label(SourceDir("//a/"), "a"));
-  dest.set_output_type(Target::SOURCE_SET);
-  dest.visibility().SetPublic();
-  dest.SetToolchain(setup.toolchain());
+  TestTarget dest(setup, "//a:a", Target::SOURCE_SET);
   dest.public_configs().push_back(LabelConfigPair(&pub_config));
   ASSERT_TRUE(dest.OnResolved(&err));
 
   // This target has a public dependency on dest.
-  Target pub(setup.settings(), Label(SourceDir("//a/"), "pub"));
-  pub.set_output_type(Target::SOURCE_SET);
-  pub.visibility().SetPublic();
-  pub.SetToolchain(setup.toolchain());
+  TestTarget pub(setup, "//a:pub", Target::SOURCE_SET);
   pub.public_deps().push_back(LabelTargetPair(&dest));
   ASSERT_TRUE(pub.OnResolved(&err));
 
   // Depending on the target with the public dependency should forward dest's
   // to the current target.
-  Target dep_on_pub(setup.settings(), Label(SourceDir("//a/"), "dop"));
-  dep_on_pub.set_output_type(Target::SOURCE_SET);
-  dep_on_pub.visibility().SetPublic();
-  dep_on_pub.SetToolchain(setup.toolchain());
+  TestTarget dep_on_pub(setup, "//a:dop", Target::SOURCE_SET);
   dep_on_pub.private_deps().push_back(LabelTargetPair(&pub));
   ASSERT_TRUE(dep_on_pub.OnResolved(&err));
   ASSERT_EQ(1u, dep_on_pub.configs().size());
   EXPECT_EQ(&pub_config, dep_on_pub.configs()[0].ptr);
 
   // This target has a private dependency on dest for forwards configs.
-  Target forward(setup.settings(), Label(SourceDir("//a/"), "f"));
-  forward.set_output_type(Target::SOURCE_SET);
-  forward.visibility().SetPublic();
-  forward.SetToolchain(setup.toolchain());
+  TestTarget forward(setup, "//a:f", Target::SOURCE_SET);
   forward.private_deps().push_back(LabelTargetPair(&dest));
   forward.forward_dependent_configs().push_back(LabelTargetPair(&dest));
   ASSERT_TRUE(forward.OnResolved(&err));
 
   // Depending on the forward target should apply the config.
-  Target dep_on_forward(setup.settings(), Label(SourceDir("//a/"), "dof"));
-  dep_on_forward.set_output_type(Target::SOURCE_SET);
-  dep_on_forward.visibility().SetPublic();
-  dep_on_forward.SetToolchain(setup.toolchain());
+  TestTarget dep_on_forward(setup, "//a:dof", Target::SOURCE_SET);
   dep_on_forward.private_deps().push_back(LabelTargetPair(&forward));
   ASSERT_TRUE(dep_on_forward.OnResolved(&err));
   ASSERT_EQ(1u, dep_on_forward.configs().size());
@@ -524,26 +438,17 @@
   Err err;
 
   // Create two leaf shared libraries.
-  Target pub(setup.settings(), Label(SourceDir("//foo/"), "pub"));
-  pub.set_output_type(Target::SHARED_LIBRARY);
-  pub.visibility().SetPublic();
-  pub.SetToolchain(setup.toolchain());
+  TestTarget pub(setup, "//foo:pub", Target::SHARED_LIBRARY);
   ASSERT_TRUE(pub.OnResolved(&err));
 
-  Target priv(setup.settings(), Label(SourceDir("//foo/"), "priv"));
-  priv.set_output_type(Target::SHARED_LIBRARY);
-  priv.visibility().SetPublic();
-  priv.SetToolchain(setup.toolchain());
+  TestTarget priv(setup, "//foo:priv", Target::SHARED_LIBRARY);
   ASSERT_TRUE(priv.OnResolved(&err));
 
   // Intermediate shared library with the leaf shared libraries as
   // dependencies, one public, one private.
-  Target inter(setup.settings(), Label(SourceDir("//foo/"), "inter"));
-  inter.set_output_type(Target::SHARED_LIBRARY);
-  inter.visibility().SetPublic();
+  TestTarget inter(setup, "//foo:inter", Target::SHARED_LIBRARY);
   inter.public_deps().push_back(LabelTargetPair(&pub));
   inter.private_deps().push_back(LabelTargetPair(&priv));
-  inter.SetToolchain(setup.toolchain());
   ASSERT_TRUE(inter.OnResolved(&err));
 
   // The intermediate shared library should have both "pub" and "priv" in its
@@ -555,11 +460,8 @@
   EXPECT_EQ(&priv, inter_inherited[1]);
 
   // Make a toplevel executable target depending on the intermediate one.
-  Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
-  exe.set_output_type(Target::SHARED_LIBRARY);
-  exe.visibility().SetPublic();
+  TestTarget exe(setup, "//foo:exe", Target::SHARED_LIBRARY);
   exe.private_deps().push_back(LabelTargetPair(&inter));
-  exe.SetToolchain(setup.toolchain());
   ASSERT_TRUE(exe.OnResolved(&err));
 
   // The exe's inherited libraries should be "inter" (because it depended
@@ -570,3 +472,89 @@
   EXPECT_EQ(&inter, exe_inherited[0]);
   EXPECT_EQ(&pub, exe_inherited[1]);
 }
+
+TEST(Target, GeneratedInputs) {
+  Scheduler scheduler;
+  TestWithScope setup;
+  Err err;
+
+  SourceFile generated_file("//out/Debug/generated.cc");
+
+  // This target has a generated input and no dependency makes it.
+  TestTarget non_existent_generator(setup, "//foo:non_existent_generator",
+                                    Target::EXECUTABLE);
+  non_existent_generator.sources().push_back(generated_file);
+  EXPECT_TRUE(non_existent_generator.OnResolved(&err)) << err.message();
+  AssertSchedulerHasOneUnknownFileMatching(&non_existent_generator,
+                                           generated_file);
+  scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // Make a target that generates the file.
+  TestTarget generator(setup, "//foo:generator", Target::ACTION);
+  generator.action_values().outputs() =
+      SubstitutionList::MakeForTest(generated_file.value().c_str());
+  err = Err();
+  EXPECT_TRUE(generator.OnResolved(&err)) << err.message();
+
+  // A target that depends on the generator that uses the file as a source
+  // should be OK. This uses a private dep (will be used later).
+  TestTarget existent_generator(setup, "//foo:existent_generator",
+                                Target::SHARED_LIBRARY);
+  existent_generator.sources().push_back(generated_file);
+  existent_generator.private_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(existent_generator.OnResolved(&err)) << err.message();
+  EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+
+  // A target that depends on the previous one should *not* be allowed to
+  // use the generated file, because existent_generator used private deps.
+  // This is:
+  //    indirect_private --> existent_generator --[private]--> generator
+  TestTarget indirect_private(setup, "//foo:indirect_private",
+                              Target::EXECUTABLE);
+  indirect_private.sources().push_back(generated_file);
+  indirect_private.public_deps().push_back(
+      LabelTargetPair(&existent_generator));
+  EXPECT_TRUE(indirect_private.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&indirect_private, generated_file);
+  scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // Now make a chain like the above but with all public deps, it should be OK.
+  TestTarget existent_public(setup, "//foo:existent_public",
+                             Target::SHARED_LIBRARY);
+  existent_public.public_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(existent_public.OnResolved(&err)) << err.message();
+  TestTarget indirect_public(setup, "//foo:indirect_public",
+                             Target::EXECUTABLE);
+  indirect_public.sources().push_back(generated_file);
+  indirect_public.public_deps().push_back(LabelTargetPair(&existent_public));
+  EXPECT_TRUE(indirect_public.OnResolved(&err)) << err.message();
+  EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+}
+
+// This is sort of a Scheduler test, but is related to the above test more.
+TEST(Target, WriteFileGeneratedInputs) {
+  Scheduler scheduler;
+  TestWithScope setup;
+  Err err;
+
+  SourceFile generated_file("//out/Debug/generated.data");
+
+  // This target has a generated input and no dependency makes it.
+  TestTarget non_existent_generator(setup, "//foo:non_existent_generator",
+                                    Target::EXECUTABLE);
+  non_existent_generator.sources().push_back(generated_file);
+  EXPECT_TRUE(non_existent_generator.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&non_existent_generator,
+                                           generated_file);
+  scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've decared we write it.
+  TestTarget existent_generator(setup, "//foo:existent_generator",
+                                Target::EXECUTABLE);
+  existent_generator.sources().push_back(generated_file);
+  EXPECT_TRUE(existent_generator.OnResolved(&err));
+  scheduler.AddWrittenFile(generated_file);
+
+  // Should be OK.
+  EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+}
diff --git a/tools/gn/test_with_scope.cc b/tools/gn/test_with_scope.cc
index 62849af..847af62 100644
--- a/tools/gn/test_with_scope.cc
+++ b/tools/gn/test_with_scope.cc
@@ -39,6 +39,14 @@
 TestWithScope::~TestWithScope() {
 }
 
+Label TestWithScope::ParseLabel(const std::string& str) const {
+  Err err;
+  Label result = Label::Resolve(SourceDir("//"), toolchain_.label(),
+                                Value(nullptr, str), &err);
+  CHECK(!err.has_error());
+  return result;
+}
+
 // static
 void TestWithScope::SetupToolchain(Toolchain* toolchain) {
   Err err;
@@ -139,3 +147,15 @@
 
 TestParseInput::~TestParseInput() {
 }
+
+TestTarget::TestTarget(TestWithScope& setup,
+                       const std::string& label_string,
+                       Target::OutputType type)
+    : Target(setup.settings(), setup.ParseLabel(label_string)) {
+  visibility().SetPublic();
+  set_output_type(type);
+  SetToolchain(setup.toolchain());
+}
+
+TestTarget::~TestTarget() {
+}
diff --git a/tools/gn/test_with_scope.h b/tools/gn/test_with_scope.h
index 26b082f..79c6a08 100644
--- a/tools/gn/test_with_scope.h
+++ b/tools/gn/test_with_scope.h
@@ -14,6 +14,7 @@
 #include "tools/gn/parse_tree.h"
 #include "tools/gn/scope.h"
 #include "tools/gn/settings.h"
+#include "tools/gn/target.h"
 #include "tools/gn/token.h"
 #include "tools/gn/toolchain.h"
 #include "tools/gn/value.h"
@@ -35,6 +36,10 @@
   // threadsafe so don't write tests that call print from multiple threads.
   std::string& print_output() { return print_output_; }
 
+  // Parse the given string into a label in the default toolchain. This will
+  // assert if the label isn't valid (this is intended for hardcoded labels).
+  Label ParseLabel(const std::string& str) const;
+
   // Fills in the tools for the given toolchain with reasonable default values.
   // The toolchain in this object will be automatically set up with this
   // function, it is exposed to allow tests to get the same functionality for
@@ -82,4 +87,15 @@
   DISALLOW_COPY_AND_ASSIGN(TestParseInput);
 };
 
+// Shortcut for creating targets for tests that take the test setup, a pretty-
+// style label, and a target type and sets everything up. The target will
+// default to public visibility.
+class TestTarget : public Target {
+ public:
+  TestTarget(TestWithScope& setup,
+             const std::string& label_string,
+             Target::OutputType type);
+  ~TestTarget() override;
+};
+
 #endif  // TOOLS_GN_TEST_WITH_SCOPE_H_