clean: remove outputs specified by dyndep files

Some outputs may not be known in the main build manifest and are instead
discovered through a dyndep binding.  Load dyndep files that are
available during cleaning so that we can clean these outputs too.
diff --git a/src/clean.cc b/src/clean.cc
index caee8d3..d1f221d 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -27,6 +27,7 @@
                  DiskInterface* disk_interface)
   : state_(state),
     config_(config),
+    dyndep_loader_(state, disk_interface),
     removed_(),
     cleaned_(),
     cleaned_files_count_(0),
@@ -103,6 +104,7 @@
 int Cleaner::CleanAll(bool generator) {
   Reset();
   PrintHeader();
+  LoadDyndeps();
   for (vector<Edge*>::iterator e = state_->edges_.begin();
        e != state_->edges_.end(); ++e) {
     // Do not try to remove phony targets
@@ -148,6 +150,7 @@
 
   Reset();
   PrintHeader();
+  LoadDyndeps();
   DoCleanTarget(target);
   PrintFooter();
   return status_;
@@ -170,6 +173,7 @@
 int Cleaner::CleanTargets(int target_count, char* targets[]) {
   Reset();
   PrintHeader();
+  LoadDyndeps();
   for (int i = 0; i < target_count; ++i) {
     string target_name = targets[i];
     uint64_t slash_bits;
@@ -213,6 +217,7 @@
 
   Reset();
   PrintHeader();
+  LoadDyndeps();
   DoCleanRule(rule);
   PrintFooter();
   return status_;
@@ -237,6 +242,7 @@
 
   Reset();
   PrintHeader();
+  LoadDyndeps();
   for (int i = 0; i < rule_count; ++i) {
     const char* rule_name = rules[i];
     const Rule* rule = state_->bindings_.LookupRule(rule_name);
@@ -259,3 +265,16 @@
   removed_.clear();
   cleaned_.clear();
 }
+
+void Cleaner::LoadDyndeps() {
+  // Load dyndep files that exist, before they are cleaned.
+  for (vector<Edge*>::iterator e = state_->edges_.begin();
+       e != state_->edges_.end(); ++e) {
+    if (Node* dyndep = (*e)->dyndep_) {
+      // Capture and ignore errors loading the dyndep file.
+      // We clean as much of the graph as we know.
+      std::string err;
+      dyndep_loader_.LoadDyndeps(dyndep, &err);
+    }
+  }
+}
diff --git a/src/clean.h b/src/clean.h
index a007486..d044fb1 100644
--- a/src/clean.h
+++ b/src/clean.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "build.h"
+#include "dyndep.h"
 
 using namespace std;
 
@@ -91,8 +92,12 @@
   void DoCleanRule(const Rule* rule);
   void Reset();
 
+  /// Load dependencies from dyndep bindings.
+  void LoadDyndeps();
+
   State* state_;
   const BuildConfig& config_;
+  DyndepLoader dyndep_loader_;
   set<string> removed_;
   set<Node*> cleaned_;
   int cleaned_files_count_;
diff --git a/src/clean_test.cc b/src/clean_test.cc
index 63734ac..45187f4 100644
--- a/src/clean_test.cc
+++ b/src/clean_test.cc
@@ -285,6 +285,55 @@
   EXPECT_EQ(2u, fs_.files_removed_.size());
 }
 
+TEST_F(CleanTest, CleanDyndep) {
+  // Verify that a dyndep file can be loaded to discover a new output
+  // to be cleaned.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out: cat in || dd\n"
+"  dyndep = dd\n"
+  ));
+  fs_.Create("in", "");
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out | out.imp: dyndep\n"
+);
+  fs_.Create("out", "");
+  fs_.Create("out.imp", "");
+
+  Cleaner cleaner(&state_, config_, &fs_);
+
+  ASSERT_EQ(0, cleaner.cleaned_files_count());
+  EXPECT_EQ(0, cleaner.CleanAll());
+  EXPECT_EQ(2, cleaner.cleaned_files_count());
+  EXPECT_EQ(2u, fs_.files_removed_.size());
+
+  string err;
+  EXPECT_EQ(0, fs_.Stat("out", &err));
+  EXPECT_EQ(0, fs_.Stat("out.imp", &err));
+}
+
+TEST_F(CleanTest, CleanDyndepMissing) {
+  // Verify that a missing dyndep file is tolerated.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out: cat in || dd\n"
+"  dyndep = dd\n"
+  ));
+  fs_.Create("in", "");
+  fs_.Create("out", "");
+  fs_.Create("out.imp", "");
+
+  Cleaner cleaner(&state_, config_, &fs_);
+
+  ASSERT_EQ(0, cleaner.cleaned_files_count());
+  EXPECT_EQ(0, cleaner.CleanAll());
+  EXPECT_EQ(1, cleaner.cleaned_files_count());
+  EXPECT_EQ(1u, fs_.files_removed_.size());
+
+  string err;
+  EXPECT_EQ(0, fs_.Stat("out", &err));
+  EXPECT_EQ(1, fs_.Stat("out.imp", &err));
+}
+
 TEST_F(CleanTest, CleanRspFile) {
   ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
 "rule cc\n"