Merge pull request #1508 from mqudsi/colored_fail

Emit "FAILED: " in red if terminal supports ANSI color output
diff --git a/.gitignore b/.gitignore
index 11150c9..46736a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,4 @@
 
 # Visual Studio Code project files
 /.vscode/
+/.ccls-cache/
diff --git a/.travis.yml b/.travis.yml
index 19a9b28..f76b982 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,7 @@
 sudo: false
 language: cpp
 script:
+  - ./misc/ci.py
   - ./configure.py --bootstrap
   - ./ninja all
   - ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots
diff --git a/HACKING.md b/HACKING.md
index 086940b..bd6fec7 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -109,17 +109,9 @@
 
 ## Testing performance impact of changes
 
-If you have a Chrome build handy, it's a good test case.  Otherwise,
-[the github downoads page](https://github.com/ninja-build/ninja/releases)
-has a copy of the Chrome build files (and depfiles). You can untar
-that, then run
-
-    path/to/my/ninja chrome
-
-and compare that against a baseline Ninja.
-
-There's a script at `misc/measure.py` that repeatedly runs a command like
-the above (to address variance) and summarizes its runtime.  E.g.
+If you have a Chrome build handy, it's a good test case.  There's a
+script at `misc/measure.py` that repeatedly runs a command (to address
+variance) and summarizes its runtime.  E.g.
 
     path/to/misc/measure.py path/to/my/ninja chrome
 
diff --git a/configure.py b/configure.py
index 78cd1de..850bb98 100755
--- a/configure.py
+++ b/configure.py
@@ -496,6 +496,8 @@
              'depfile_parser',
              'deps_log',
              'disk_interface',
+             'dyndep',
+             'dyndep_parser',
              'edit_distance',
              'eval_env',
              'graph',
@@ -504,11 +506,12 @@
              'line_printer',
              'manifest_parser',
              'metrics',
+             'parser',
              'state',
              'string_piece_util',
              'util',
              'version']:
-    objs += cxx(name, variables=cxxvariables) 
+    objs += cxx(name, variables=cxxvariables)
 if platform.is_windows():
     for name in ['subprocess-win32',
                  'includes_normalize-win32',
@@ -563,6 +566,7 @@
              'clparser_test',
              'depfile_parser_test',
              'deps_log_test',
+             'dyndep_parser_test',
              'disk_interface_test',
              'edit_distance_test',
              'graph_test',
diff --git a/doc/docbook.xsl b/doc/docbook.xsl
index 19cc126..2235be2 100644
--- a/doc/docbook.xsl
+++ b/doc/docbook.xsl
@@ -21,6 +21,9 @@
   <!-- Don't put the "Chapter 1." prefix on the "chapters". -->
   <xsl:param name="chapter.autolabel">0</xsl:param>
 
+  <!-- Make builds reproducible by generating the same IDs from the same inputs -->
+  <xsl:param name="generate.consistent.ids">1</xsl:param>
+
   <!-- Use <ul> for the table of contents.  By default DocBook uses a
        <dl>, which makes no semantic sense.  I imagine they just did
        it because it looks nice? -->
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index 3440740..e49d26d 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -156,7 +156,7 @@
 
 https://gn.googlesource.com/gn/[gn]:: The meta-build system used to
 generate build files for Google Chrome and related projects (v8,
-node.js), as well as Google Fuschia.  gn can generate Ninja files for
+node.js), as well as Google Fuchsia.  gn can generate Ninja files for
 all platforms supported by Chrome.
 
 https://cmake.org/[CMake]:: A widely used meta-build system that
@@ -164,7 +164,7 @@
 of CMake support generating Ninja files on Windows and Mac OS X too.
 
 https://github.com/ninja-build/ninja/wiki/List-of-generators-producing-ninja-build-files[others]:: Ninja ought to fit perfectly into other meta-build software
-like http://industriousone.com/premake[premake].  If you do this work,
+like https://premake.github.io/[premake].  If you do this work,
 please let us know!
 
 Running Ninja
@@ -283,6 +283,9 @@
 
 `recompact`:: recompact the `.ninja_deps` file. _Available since Ninja 1.4._
 
+`rules`:: output the list of all rules (eventually with their description
+if they have one).  It can be used to know which rule name to pass to
++ninja -t targets rule _name_+ or +ninja -t compdb+.
 
 Writing your own Ninja files
 ----------------------------
@@ -449,6 +452,14 @@
 it does not exist.  Without a phony build statement, Ninja will report
 an error if the file does not exist and is required by the build.
 
+To create a rule that never rebuilds, use a build rule without any input:
+----------------
+rule touch
+  command = touch $out
+build file_that_always_exists.dummy: touch
+build dummy_target_to_follow_a_pattern: phony file_that_always_exists.dummy
+----------------
+
 
 Default target statements
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -679,6 +690,7 @@
 as progress status and output from concurrent tasks) is buffered until
 it completes.
 
+[[ref_ninja_file]]
 Ninja file reference
 --------------------
 
@@ -710,6 +722,7 @@
 6. A pool declaration, which looks like +pool _poolname_+. Pools are explained
    <<ref_pool, in the section on pools>>.
 
+[[ref_lexer]]
 Lexical syntax
 ~~~~~~~~~~~~~~
 
@@ -814,6 +827,11 @@
   the full command or its description; if a command fails, the full command
   line will always be printed before the command's output.
 
+`dyndep`:: _(Available since Ninja 1.10.)_ Used only on build statements.
+  If present, must name one of the build statement inputs.  Dynamically
+  discovered dependency information will be loaded from the file.
+  See the <<ref_dyndep,dynamic dependencies>> section for details.
+
 `generator`:: if present, specifies that this rule is used to
   re-invoke the generator program.  Files built using `generator`
   rules are treated specially in two ways: firstly, they will not be
@@ -1003,3 +1021,146 @@
 
 5. Variables from the file that included that file using the
    `subninja` keyword.
+
+[[ref_dyndep]]
+Dynamic Dependencies
+--------------------
+
+_Available since Ninja 1.10._
+
+Some use cases require implicit dependency information to be dynamically
+discovered from source file content _during the build_ in order to build
+correctly on the first run (e.g. Fortran module dependencies).  This is
+unlike <<ref_headers,header dependencies>> which are only needed on the
+second run and later to rebuild correctly.  A build statement may have a
+`dyndep` binding naming one of its inputs to specify that dynamic
+dependency information must be loaded from the file.  For example:
+
+----
+build out: ... || foo
+  dyndep = foo
+build foo: ...
+----
+
+This specifies that file `foo` is a dyndep file.  Since it is an input,
+the build statement for `out` can never be executed before `foo` is built.
+As soon as `foo` is finished Ninja will read it to load dynamically
+discovered dependency information for `out`.  This may include additional
+implicit inputs and/or outputs.  Ninja will update the build graph
+accordingly and the build will proceed as if the information was known
+originally.
+
+Dyndep file reference
+~~~~~~~~~~~~~~~~~~~~~
+
+Files specified by `dyndep` bindings use the same <<ref_lexer,lexical syntax>>
+as <<ref_ninja_file,ninja build files>> and have the following layout.
+
+1. A version number in the form `<major>[.<minor>][<suffix>]`:
++
+----
+ninja_dyndep_version = 1
+----
++
+Currently the version number must always be `1` or `1.0` but may have
+an arbitrary suffix.
+
+2. One or more build statements of the form:
++
+----
+build out | imp-outs... : dyndep | imp-ins...
+----
++
+Every statement must specify exactly one explicit output and must use
+the rule name `dyndep`.  The `| imp-outs...` and `| imp-ins...` portions
+are optional.
+
+3. An optional `restat` <<ref_rule,variable binding>> on each build statement.
+
+The build statements in a dyndep file must have a one-to-one correspondence
+to build statements in the <<ref_ninja_file,ninja build file>> that name the
+dyndep file in a `dyndep` binding.  No dyndep build statement may be omitted
+and no extra build statements may be specified.
+
+Dyndep Examples
+~~~~~~~~~~~~~~~
+
+Fortran Modules
+^^^^^^^^^^^^^^^
+
+Consider a Fortran source file `foo.f90` that provides a module
+`foo.mod` (an implicit output of compilation) and another source file
+`bar.f90` that uses the module (an implicit input of compilation).  This
+implicit dependency must be discovered before we compile either source
+in order to ensure that `bar.f90` never compiles before `foo.f90`, and
+that `bar.f90` recompiles when `foo.mod` changes.  We can achieve this
+as follows:
+
+----
+rule f95
+  command = f95 -o $out -c $in
+rule fscan
+  command = fscan -o $out $in
+
+build foobar.dd: fscan foo.f90 bar.f90
+
+build foo.o: f95 foo.f90 || foobar.dd
+  dyndep = foobar.dd
+build bar.o: f95 bar.f90 || foobar.dd
+  dyndep = foobar.dd
+----
+
+In this example the order-only dependencies ensure that `foobar.dd` is
+generated before either source compiles.  The hypothetical `fscan` tool
+scans the source files, assumes each will be compiled to a `.o` of the
+same name, and writes `foobar.dd` with content such as:
+
+----
+ninja_dyndep_version = 1
+build foo.o | foo.mod: dyndep
+build bar.o: dyndep |  foo.mod
+----
+
+Ninja will load this file to add `foo.mod` as an implicit output of
+`foo.o` and implicit input of `bar.o`.  This ensures that the Fortran
+sources are always compiled in the proper order and recompiled when
+needed.
+
+Tarball Extraction
+^^^^^^^^^^^^^^^^^^
+
+Consider a tarball `foo.tar` that we want to extract.  The extraction time
+can be recorded with a `foo.tar.stamp` file so that extraction repeats if
+the tarball changes, but we also would like to re-extract if any of the
+outputs is missing.  However, the list of outputs depends on the content
+of the tarball and cannot be spelled out explicitly in the ninja build file.
+We can achieve this as follows:
+
+----
+rule untar
+  command = tar xf $in && touch $out
+rule scantar
+  command = scantar --stamp=$stamp --dd=$out $in
+build foo.tar.dd: scantar foo.tar
+  stamp = foo.tar.stamp
+build foo.tar.stamp: untar foo.tar || foo.tar.dd
+  dyndep = foo.tar.dd
+----
+
+In this example the order-only dependency ensures that `foo.tar.dd` is
+built before the tarball extracts.  The hypothetical `scantar` tool
+will read the tarball (e.g. via `tar tf`) and write `foo.tar.dd` with
+content such as:
+
+----
+ninja_dyndep_version = 1
+build foo.tar.stamp | file1.txt file2.txt : dyndep
+  restat = 1
+----
+
+Ninja will load this file to add `file1.txt` and `file2.txt` as implicit
+outputs of `foo.tar.stamp`, and to mark the build statement for `restat`.
+On future builds, if any implicit output is missing the tarball will be
+extracted again.  The `restat` binding tells Ninja to tolerate the fact
+that the implicit outputs may not have modification times newer than
+the tarball itself (avoiding re-extraction on every build).
diff --git a/misc/ci.py b/misc/ci.py
new file mode 100755
index 0000000..17cbf14
--- /dev/null
+++ b/misc/ci.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+
+import os
+
+ignores = [
+	'.git/',
+	'misc/afl-fuzz-tokens/',
+	'ninja_deps',
+	'src/depfile_parser.cc',
+	'src/lexer.cc',
+]
+
+error_count = 0
+
+def error(path, msg):
+	global error_count
+	error_count += 1
+	print('\x1b[1;31m{}\x1b[0;31m{}\x1b[0m'.format(path, msg))
+
+for root, directory, filenames in os.walk('.'):
+	for filename in filenames:
+		path = os.path.join(root, filename)[2:]
+		if any([path.startswith(x) for x in ignores]):
+			continue
+		with open(path, 'rb') as file:
+			line_nr = 1
+			try:
+				for line in [x.decode() for x in file.readlines()]:
+					if len(line) == 0 or line[-1] != '\n':
+						error(path, ' missing newline at end of file.')
+					if len(line) > 1:
+						if line[-2] == '\r':
+							error(path, ' has Windows line endings.')
+							break
+						if line[-2] == ' ' or line[-2] == '\t':
+							error(path, ':{} has trailing whitespace.'.format(line_nr))
+					line_nr += 1
+			except UnicodeError:
+				pass # binary file
+
+exit(error_count)
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index 051bac1..ebe6490 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -21,7 +21,7 @@
     def newline(self):
         self.output.write('\n')
 
-    def comment(self, text, has_path=False):
+    def comment(self, text):
         for line in textwrap.wrap(text, self.width - 2, break_long_words=False,
                                   break_on_hyphens=False):
             self.output.write('# ' + line + '\n')
diff --git a/src/build.cc b/src/build.cc
index 386fa65..8ef88b5 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -97,6 +97,7 @@
 }
 
 void BuildStatus::BuildEdgeStarted(Edge* edge) {
+  assert(running_edges_.find(edge) == running_edges_.end());
   int start_time = (int)(GetTimeMillis() - start_time_millis_);
   running_edges_.insert(make_pair(edge, start_time));
   ++started_edges_;
@@ -177,6 +178,20 @@
   }
 }
 
+void BuildStatus::BuildLoadDyndeps() {
+  // The DependencyScan calls EXPLAIN() to print lines explaining why
+  // it considers a portion of the graph to be out of date.  Normally
+  // this is done before the build starts, but our caller is about to
+  // load a dyndep file during the build.  Doing so may generate more
+  // exlanation lines (via fprintf directly to stderr), but in an
+  // interactive console the cursor is currently at the end of a status
+  // line.  Start a new line so that the first explanation does not
+  // append to the status line.  After the explanations are done a
+  // new build status line will appear.
+  if (g_explaining)
+    printer_.PrintOnNewLine("");
+}
+
 void BuildStatus::BuildStarted() {
   overall_rate_.Restart();
   current_rate_.Restart();
@@ -291,7 +306,11 @@
                  force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
 }
 
-Plan::Plan() : command_edges_(0), wanted_edges_(0) {}
+Plan::Plan(Builder* builder)
+  : builder_(builder)
+  , command_edges_(0)
+  , wanted_edges_(0)
+{}
 
 void Plan::Reset() {
   command_edges_ = 0;
@@ -301,10 +320,11 @@
 }
 
 bool Plan::AddTarget(Node* node, string* err) {
-  return AddSubTarget(node, NULL, err);
+  return AddSubTarget(node, NULL, err, NULL);
 }
 
-bool Plan::AddSubTarget(Node* node, Node* dependent, string* err) {
+bool Plan::AddSubTarget(Node* node, Node* dependent, string* err,
+                        set<Edge*>* dyndep_walk) {
   Edge* edge = node->in_edge();
   if (!edge) {  // Leaf node.
     if (node->dirty()) {
@@ -326,29 +346,39 @@
     want_.insert(make_pair(edge, kWantNothing));
   Want& want = want_ins.first->second;
 
+  if (dyndep_walk && want == kWantToFinish)
+    return false;  // Don't need to do anything with already-scheduled edge.
+
   // If we do need to build edge and we haven't already marked it as wanted,
   // mark it now.
   if (node->dirty() && want == kWantNothing) {
     want = kWantToStart;
-    ++wanted_edges_;
-    if (edge->AllInputsReady())
+    EdgeWanted(edge);
+    if (!dyndep_walk && edge->AllInputsReady())
       ScheduleWork(want_ins.first);
-    if (!edge->is_phony())
-      ++command_edges_;
   }
 
+  if (dyndep_walk)
+    dyndep_walk->insert(edge);
+
   if (!want_ins.second)
     return true;  // We've already processed the inputs.
 
   for (vector<Node*>::iterator i = edge->inputs_.begin();
        i != edge->inputs_.end(); ++i) {
-    if (!AddSubTarget(*i, node, err) && !err->empty())
+    if (!AddSubTarget(*i, node, err, dyndep_walk) && !err->empty())
       return false;
   }
 
   return true;
 }
 
+void Plan::EdgeWanted(Edge* edge) {
+  ++wanted_edges_;
+  if (!edge->is_phony())
+    ++command_edges_;
+}
+
 Edge* Plan::FindWork() {
   if (ready_.empty())
     return NULL;
@@ -380,7 +410,7 @@
   }
 }
 
-void Plan::EdgeFinished(Edge* edge, EdgeResult result) {
+bool Plan::EdgeFinished(Edge* edge, EdgeResult result, string* err) {
   map<Edge*, Want>::iterator e = want_.find(edge);
   assert(e != want_.end());
   bool directly_wanted = e->second != kWantNothing;
@@ -392,7 +422,7 @@
 
   // The rest of this function only applies to successful commands.
   if (result != kEdgeSucceeded)
-    return;
+    return true;
 
   if (directly_wanted)
     --wanted_edges_;
@@ -402,11 +432,21 @@
   // Check off any nodes we were waiting for with this edge.
   for (vector<Node*>::iterator o = edge->outputs_.begin();
        o != edge->outputs_.end(); ++o) {
-    NodeFinished(*o);
+    if (!NodeFinished(*o, err))
+      return false;
   }
+  return true;
 }
 
-void Plan::NodeFinished(Node* node) {
+bool Plan::NodeFinished(Node* node, string* err) {
+  // If this node provides dyndep info, load it now.
+  if (node->dyndep_pending()) {
+    assert(builder_ && "dyndep requires Plan to have a Builder");
+    // Load the now-clean dyndep file.  This will also update the
+    // build plan and schedule any new work that is ready.
+    return builder_->LoadDyndeps(node, err);
+  }
+
   // See if we we want any edges from this node.
   for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
        oe != node->out_edges().end(); ++oe) {
@@ -415,16 +455,25 @@
       continue;
 
     // See if the edge is now ready.
-    if ((*oe)->AllInputsReady()) {
-      if (want_e->second != kWantNothing) {
-        ScheduleWork(want_e);
-      } else {
-        // We do not need to build this edge, but we might need to build one of
-        // its dependents.
-        EdgeFinished(*oe, kEdgeSucceeded);
-      }
+    if (!EdgeMaybeReady(want_e, err))
+      return false;
+  }
+  return true;
+}
+
+bool Plan::EdgeMaybeReady(map<Edge*, Want>::iterator want_e, string* err) {
+  Edge* edge = want_e->first;
+  if (edge->AllInputsReady()) {
+    if (want_e->second != kWantNothing) {
+      ScheduleWork(want_e);
+    } else {
+      // We do not need to build this edge, but we might need to build one of
+      // its dependents.
+      if (!EdgeFinished(edge, kEdgeSucceeded, err))
+        return false;
     }
   }
+  return true;
 }
 
 bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) {
@@ -484,6 +533,128 @@
   return true;
 }
 
+bool Plan::DyndepsLoaded(DependencyScan* scan, Node* node,
+                         const DyndepFile& ddf, string* err) {
+  // Recompute the dirty state of all our direct and indirect dependents now
+  // that our dyndep information has been loaded.
+  if (!RefreshDyndepDependents(scan, node, err))
+    return false;
+
+  // We loaded dyndep information for those out_edges of the dyndep node that
+  // specify the node in a dyndep binding, but they may not be in the plan.
+  // Starting with those already in the plan, walk newly-reachable portion
+  // of the graph through the dyndep-discovered dependencies.
+
+  // Find edges in the the build plan for which we have new dyndep info.
+  std::vector<DyndepFile::const_iterator> dyndep_roots;
+  for (DyndepFile::const_iterator oe = ddf.begin(); oe != ddf.end(); ++oe) {
+    Edge* edge = oe->first;
+
+    // If the edge outputs are ready we do not need to consider it here.
+    if (edge->outputs_ready())
+      continue;
+
+    map<Edge*, Want>::iterator want_e = want_.find(edge);
+
+    // If the edge has not been encountered before then nothing already in the
+    // plan depends on it so we do not need to consider the edge yet either.
+    if (want_e == want_.end())
+      continue;
+
+    // This edge is already in the plan so queue it for the walk.
+    dyndep_roots.push_back(oe);
+  }
+
+  // Walk dyndep-discovered portion of the graph to add it to the build plan.
+  std::set<Edge*> dyndep_walk;
+  for (std::vector<DyndepFile::const_iterator>::iterator
+       oei = dyndep_roots.begin(); oei != dyndep_roots.end(); ++oei) {
+    DyndepFile::const_iterator oe = *oei;
+    for (vector<Node*>::const_iterator i = oe->second.implicit_inputs_.begin();
+         i != oe->second.implicit_inputs_.end(); ++i) {
+      if (!AddSubTarget(*i, oe->first->outputs_[0], err, &dyndep_walk) &&
+          !err->empty())
+        return false;
+    }
+  }
+
+  // Add out edges from this node that are in the plan (just as
+  // Plan::NodeFinished would have without taking the dyndep code path).
+  for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
+       oe != node->out_edges().end(); ++oe) {
+    map<Edge*, Want>::iterator want_e = want_.find(*oe);
+    if (want_e == want_.end())
+      continue;
+    dyndep_walk.insert(want_e->first);
+  }
+
+  // See if any encountered edges are now ready.
+  for (set<Edge*>::iterator wi = dyndep_walk.begin();
+       wi != dyndep_walk.end(); ++wi) {
+    map<Edge*, Want>::iterator want_e = want_.find(*wi);
+    if (want_e == want_.end())
+      continue;
+    if (!EdgeMaybeReady(want_e, err))
+      return false;
+  }
+
+  return true;
+}
+
+bool Plan::RefreshDyndepDependents(DependencyScan* scan, Node* node,
+                                   string* err) {
+  // Collect the transitive closure of dependents and mark their edges
+  // as not yet visited by RecomputeDirty.
+  set<Node*> dependents;
+  UnmarkDependents(node, &dependents);
+
+  // Update the dirty state of all dependents and check if their edges
+  // have become wanted.
+  for (set<Node*>::iterator i = dependents.begin();
+       i != dependents.end(); ++i) {
+    Node* n = *i;
+
+    // Check if this dependent node is now dirty.  Also checks for new cycles.
+    if (!scan->RecomputeDirty(n, err))
+      return false;
+    if (!n->dirty())
+      continue;
+
+    // This edge was encountered before.  However, we may not have wanted to
+    // build it if the outputs were not known to be dirty.  With dyndep
+    // information an output is now known to be dirty, so we want the edge.
+    Edge* edge = n->in_edge();
+    assert(edge && !edge->outputs_ready());
+    map<Edge*, Want>::iterator want_e = want_.find(edge);
+    assert(want_e != want_.end());
+    if (want_e->second == kWantNothing) {
+      want_e->second = kWantToStart;
+      EdgeWanted(edge);
+    }
+  }
+  return true;
+}
+
+void Plan::UnmarkDependents(Node* node, set<Node*>* dependents) {
+  for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
+       oe != node->out_edges().end(); ++oe) {
+    Edge* edge = *oe;
+
+    map<Edge*, Want>::iterator want_e = want_.find(edge);
+    if (want_e == want_.end())
+      continue;
+
+    if (edge->mark_ != Edge::VisitNone) {
+      edge->mark_ = Edge::VisitNone;
+      for (vector<Node*>::iterator o = edge->outputs_.begin();
+           o != edge->outputs_.end(); ++o) {
+        if (dependents->insert(*o).second)
+          UnmarkDependents(*o, dependents);
+      }
+    }
+  }
+}
+
 void Plan::Dump() {
   printf("pending: %d\n", (int)want_.size());
   for (map<Edge*, Want>::iterator e = want_.begin(); e != want_.end(); ++e) {
@@ -560,7 +731,8 @@
 Builder::Builder(State* state, const BuildConfig& config,
                  BuildLog* build_log, DepsLog* deps_log,
                  DiskInterface* disk_interface)
-    : state_(state), config_(config), disk_interface_(disk_interface),
+    : state_(state), config_(config),
+      plan_(this), disk_interface_(disk_interface),
       scan_(state, build_log, deps_log, disk_interface,
             &config_.depfile_parser_options) {
   status_ = new BuildStatus(config);
@@ -664,7 +836,11 @@
         }
 
         if (edge->is_phony()) {
-          plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+          if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err)) {
+            Cleanup();
+            status_->BuildFinished();
+            return false;
+          }
         } else {
           ++pending_commands;
         }
@@ -784,8 +960,7 @@
 
   // The rest of this function only applies to successful commands.
   if (!result->success()) {
-    plan_.EdgeFinished(edge, Plan::kEdgeFailed);
-    return true;
+    return plan_.EdgeFinished(edge, Plan::kEdgeFailed, err);
   }
 
   // Restat the edge outputs
@@ -841,7 +1016,8 @@
     }
   }
 
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err))
+    return false;
 
   // Delete any left over response file.
   string rspfile = edge->GetUnescapedRspfile();
@@ -938,3 +1114,21 @@
 
   return true;
 }
+
+bool Builder::LoadDyndeps(Node* node, string* err) {
+  status_->BuildLoadDyndeps();
+
+  // Load the dyndep information provided by this node.
+  DyndepFile ddf;
+  if (!scan_.LoadDyndeps(node, &ddf, err))
+    return false;
+
+  // Update the build plan to account for dyndep modifications to the graph.
+  if (!plan_.DyndepsLoaded(&scan_, node, ddf, err))
+    return false;
+
+  // New command edges may have been added to the plan.
+  status_->PlanHasTotalEdges(plan_.command_edge_count());
+
+  return true;
+}
diff --git a/src/build.h b/src/build.h
index a42b8d4..ab59f0c 100644
--- a/src/build.h
+++ b/src/build.h
@@ -32,6 +32,7 @@
 
 struct BuildLog;
 struct BuildStatus;
+struct Builder;
 struct DiskInterface;
 struct Edge;
 struct Node;
@@ -40,7 +41,7 @@
 /// Plan stores the state of a build plan: what we intend to build,
 /// which steps we're ready to execute.
 struct Plan {
-  Plan();
+  Plan(Builder* builder = NULL);
 
   /// Add a target to our plan (including all its dependencies).
   /// Returns false if we don't need to build this target; may
@@ -63,7 +64,10 @@
   };
 
   /// Mark an edge as done building (whether it succeeded or failed).
-  void EdgeFinished(Edge* edge, EdgeResult result);
+  /// If any of the edge's outputs are dyndep bindings of their dependents,
+  /// this loads dynamic dependencies from the nodes' paths.
+  /// Returns 'false' if loading dyndep info fails and 'true' otherwise.
+  bool EdgeFinished(Edge* edge, EdgeResult result, string* err);
 
   /// Clean the given node during the build.
   /// Return false on error.
@@ -75,9 +79,21 @@
   /// Reset state.  Clears want and ready sets.
   void Reset();
 
+  /// Update the build plan to account for modifications made to the graph
+  /// by information loaded from a dyndep file.
+  bool DyndepsLoaded(DependencyScan* scan, Node* node,
+                     const DyndepFile& ddf, string* err);
 private:
-  bool AddSubTarget(Node* node, Node* dependent, string* err);
-  void NodeFinished(Node* node);
+  bool RefreshDyndepDependents(DependencyScan* scan, Node* node, string* err);
+  void UnmarkDependents(Node* node, set<Node*>* dependents);
+  bool AddSubTarget(Node* node, Node* dependent, string* err,
+                    set<Edge*>* dyndep_walk);
+
+  /// Update plan with knowledge that the given node is up to date.
+  /// If the node is a dyndep binding on any of its dependents, this
+  /// loads dynamic dependencies from the node's path.
+  /// Returns 'false' if loading dyndep info fails and 'true' otherwise.
+  bool NodeFinished(Node* node, string* err);
 
   /// Enumerate possible steps we want for an edge.
   enum Want
@@ -92,6 +108,9 @@
     kWantToFinish
   };
 
+  void EdgeWanted(Edge* edge);
+  bool EdgeMaybeReady(map<Edge*, Want>::iterator want_e, string* err);
+
   /// Submits a ready edge as a candidate for execution.
   /// The edge may be delayed from running, for example if it's a member of a
   /// currently-full pool.
@@ -105,6 +124,8 @@
 
   set<Edge*> ready_;
 
+  Builder* builder_;
+
   /// Total number of edges that have commands (not phony).
   int command_edges_;
 
@@ -189,6 +210,9 @@
     scan_.set_build_log(log);
   }
 
+  /// Load the dyndep information provided by the given node.
+  bool LoadDyndeps(Node* node, string* err);
+
   State* state_;
   const BuildConfig& config_;
   Plan plan_;
@@ -219,6 +243,7 @@
   void BuildEdgeStarted(Edge* edge);
   void BuildEdgeFinished(Edge* edge, bool success, const string& output,
                          int* start_time, int* end_time);
+  void BuildLoadDyndeps();
   void BuildStarted();
   void BuildFinished();
 
diff --git a/src/build_log.cc b/src/build_log.cc
index c4a08a0..774f72f 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -49,6 +49,7 @@
 namespace {
 
 const char kFileSignature[] = "# ninja log v%d\n";
+const char kFileColumnLabels[] = "# start_time end_time mtime command hash\n";
 const int kOldestSupportedVersion = 4;
 const int kCurrentVersion = 5;
 
@@ -144,7 +145,8 @@
   fseek(log_file_, 0, SEEK_END);
 
   if (ftell(log_file_) == 0) {
-    if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) {
+    if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0 ||
+        fprintf(log_file_, kFileColumnLabels) < 0) {
       *err = strerror(errno);
       return false;
     }
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index ad30380..eea818f 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -70,8 +70,9 @@
 }
 
 TEST_F(BuildLogTest, FirstWriteAddsSignature) {
-  const char kExpectedVersion[] = "# ninja log vX\n";
-  const size_t kVersionPos = strlen(kExpectedVersion) - 2;  // Points at 'X'.
+  const char kExpectedContent[] = "# ninja log vX\n"
+                                  "# start_time end_time mtime command hash\n";
+  const size_t kVersionPos = 13;  // Points at 'X'.
 
   BuildLog log;
   string contents, err;
@@ -84,7 +85,7 @@
   ASSERT_EQ("", err);
   if (contents.size() >= kVersionPos)
     contents[kVersionPos] = 'X';
-  EXPECT_EQ(kExpectedVersion, contents);
+  EXPECT_EQ(kExpectedContent, contents);
 
   // Opening the file anew shouldn't add a second version string.
   EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err));
@@ -96,7 +97,7 @@
   ASSERT_EQ("", err);
   if (contents.size() >= kVersionPos)
     contents[kVersionPos] = 'X';
-  EXPECT_EQ(kExpectedVersion, contents);
+  EXPECT_EQ(kExpectedContent, contents);
 }
 
 TEST_F(BuildLogTest, DoubleEntry) {
diff --git a/src/build_test.cc b/src/build_test.cc
index 46ab33e..b5dbc6c 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -21,6 +21,12 @@
 #include "graph.h"
 #include "test.h"
 
+struct CompareEdgesByOutput {
+  static bool cmp(const Edge* a, const Edge* b) {
+    return a->outputs_[0]->path() < b->outputs_[0]->path();
+  }
+};
+
 /// Fixture for tests involving Plan.
 // Though Plan doesn't use State, it's useful to have one around
 // to create Nodes and Edges.
@@ -31,12 +37,6 @@
   // provide a means to get available Edges in order and in a format which is
   // easy to write tests around.
   void FindWorkSorted(deque<Edge*>* ret, int count) {
-    struct CompareEdgesByOutput {
-      static bool cmp(const Edge* a, const Edge* b) {
-        return a->outputs_[0]->path() < b->outputs_[0]->path();
-      }
-    };
-
     for (int i = 0; i < count; ++i) {
       ASSERT_TRUE(plan_.more_to_do());
       Edge* edge = plan_.FindWork();
@@ -68,14 +68,16 @@
 
   ASSERT_FALSE(plan_.FindWork());
 
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);
   ASSERT_EQ("mid", edge->inputs_[0]->path());
   ASSERT_EQ("out", edge->outputs_[0]->path());
 
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   ASSERT_FALSE(plan_.more_to_do());
   edge = plan_.FindWork();
@@ -99,11 +101,13 @@
   Edge* edge;
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat in
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat mid1 mid2
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_FALSE(edge);  // done
@@ -129,19 +133,23 @@
   Edge* edge;
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat in
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat a1
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat a2
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat b1 b2
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_FALSE(edge);  // done
@@ -167,19 +175,23 @@
   Edge* edge;
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat in
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat mid
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat mid
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);  // cat a1 a2
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_FALSE(edge);  // done
@@ -204,7 +216,8 @@
   // This will be false since poolcat is serialized
   ASSERT_FALSE(plan_.FindWork());
 
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);
@@ -213,7 +226,8 @@
 
   ASSERT_FALSE(plan_.FindWork());
 
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   ASSERT_FALSE(plan_.more_to_do());
   edge = plan_.FindWork();
@@ -289,7 +303,8 @@
   ASSERT_EQ("outb3", edge->outputs_[0]->path());
 
   // finish out1
-  plan_.EdgeFinished(edges.front(), Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edges.front(), Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
   edges.pop_front();
 
   // out3 should be available
@@ -300,19 +315,22 @@
 
   ASSERT_FALSE(plan_.FindWork());
 
-  plan_.EdgeFinished(out3, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(out3, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   ASSERT_FALSE(plan_.FindWork());
 
   for (deque<Edge*>::iterator it = edges.begin(); it != edges.end(); ++it) {
-    plan_.EdgeFinished(*it, Plan::kEdgeSucceeded);
+    plan_.EdgeFinished(*it, Plan::kEdgeSucceeded, &err);
+    ASSERT_EQ("", err);
   }
 
   Edge* last = plan_.FindWork();
   ASSERT_TRUE(last);
   ASSERT_EQ("allTheThings", last->outputs_[0]->path());
 
-  plan_.EdgeFinished(last, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(last, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   ASSERT_FALSE(plan_.more_to_do());
   ASSERT_FALSE(plan_.FindWork());
@@ -354,7 +372,8 @@
 
   edge = initial_edges[1];  // Foo first
   ASSERT_EQ("foo.cpp", edge->outputs_[0]->path());
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);
@@ -362,11 +381,13 @@
   ASSERT_EQ("foo.cpp", edge->inputs_[0]->path());
   ASSERT_EQ("foo.cpp", edge->inputs_[1]->path());
   ASSERT_EQ("foo.cpp.obj", edge->outputs_[0]->path());
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = initial_edges[0];  // Now for bar
   ASSERT_EQ("bar.cpp", edge->outputs_[0]->path());
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);
@@ -374,7 +395,8 @@
   ASSERT_EQ("bar.cpp", edge->inputs_[0]->path());
   ASSERT_EQ("bar.cpp", edge->inputs_[1]->path());
   ASSERT_EQ("bar.cpp.obj", edge->outputs_[0]->path());
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);
@@ -382,14 +404,16 @@
   ASSERT_EQ("foo.cpp.obj", edge->inputs_[0]->path());
   ASSERT_EQ("bar.cpp.obj", edge->inputs_[1]->path());
   ASSERT_EQ("libfoo.a", edge->outputs_[0]->path());
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);
   ASSERT_FALSE(plan_.FindWork());
   ASSERT_EQ("libfoo.a", edge->inputs_[0]->path());
   ASSERT_EQ("all", edge->outputs_[0]->path());
-  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
+  plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_FALSE(edge);
@@ -422,7 +446,8 @@
   // This will be false since poolcat is serialized
   ASSERT_FALSE(plan_.FindWork());
 
-  plan_.EdgeFinished(edge, Plan::kEdgeFailed);
+  plan_.EdgeFinished(edge, Plan::kEdgeFailed, &err);
+  ASSERT_EQ("", err);
 
   edge = plan_.FindWork();
   ASSERT_TRUE(edge);
@@ -431,7 +456,8 @@
 
   ASSERT_FALSE(plan_.FindWork());
 
-  plan_.EdgeFinished(edge, Plan::kEdgeFailed);
+  plan_.EdgeFinished(edge, Plan::kEdgeFailed, &err);
+  ASSERT_EQ("", err);
 
   ASSERT_TRUE(plan_.more_to_do()); // Jobs have failed
   edge = plan_.FindWork();
@@ -441,7 +467,7 @@
 /// Fake implementation of CommandRunner, useful for tests.
 struct FakeCommandRunner : public CommandRunner {
   explicit FakeCommandRunner(VirtualFileSystem* fs) :
-      last_command_(NULL), fs_(fs) {}
+      max_active_edges_(1), fs_(fs) {}
 
   // CommandRunner impl
   virtual bool CanRunMore();
@@ -451,7 +477,8 @@
   virtual void Abort();
 
   vector<string> commands_ran_;
-  Edge* last_command_;
+  vector<Edge*> active_edges_;
+  size_t max_active_edges_;
   VirtualFileSystem* fs_;
 };
 
@@ -543,12 +570,13 @@
 }
 
 bool FakeCommandRunner::CanRunMore() {
-  // Only run one at a time.
-  return last_command_ == NULL;
+  return active_edges_.size() < max_active_edges_;
 }
 
 bool FakeCommandRunner::StartCommand(Edge* edge) {
-  assert(!last_command_);
+  assert(active_edges_.size() < max_active_edges_);
+  assert(find(active_edges_.begin(), active_edges_.end(), edge)
+         == active_edges_.end());
   commands_ran_.push_back(edge->EvaluateCommand());
   if (edge->rule().name() == "cat"  ||
       edge->rule().name() == "cat_rsp" ||
@@ -566,20 +594,38 @@
              edge->rule().name() == "interrupt" ||
              edge->rule().name() == "console") {
     // Don't do anything.
+  } else if (edge->rule().name() == "cp") {
+    assert(!edge->inputs_.empty());
+    assert(edge->outputs_.size() == 1);
+    string content;
+    string err;
+    if (fs_->ReadFile(edge->inputs_[0]->path(), &content, &err) ==
+        DiskInterface::Okay)
+      fs_->WriteFile(edge->outputs_[0]->path(), content);
   } else {
     printf("unknown command\n");
     return false;
   }
 
-  last_command_ = edge;
+  active_edges_.push_back(edge);
+
+  // Allow tests to control the order by the name of the first output.
+  sort(active_edges_.begin(), active_edges_.end(),
+       CompareEdgesByOutput::cmp);
+
   return true;
 }
 
 bool FakeCommandRunner::WaitForCommand(Result* result) {
-  if (!last_command_)
+  if (active_edges_.empty())
     return false;
 
-  Edge* edge = last_command_;
+  // All active edges were already completed immediately when started,
+  // so we can pick any edge here.  Pick the last edge.  Tests can
+  // control the order of edges by the name of the first output.
+  vector<Edge*>::iterator edge_iter = active_edges_.end() - 1;
+
+  Edge* edge = *edge_iter;
   result->edge = edge;
 
   if (edge->rule().name() == "interrupt" ||
@@ -593,7 +639,7 @@
       result->status = ExitSuccess;
     else
       result->status = ExitFailure;
-    last_command_ = NULL;
+    active_edges_.erase(edge_iter);
     return true;
   }
 
@@ -602,19 +648,33 @@
     result->status = ExitFailure;
   else
     result->status = ExitSuccess;
-  last_command_ = NULL;
+
+  // Provide a way for test cases to verify when an edge finishes that
+  // some other edge is still active.  This is useful for test cases
+  // covering behavior involving multiple active edges.
+  const string& verify_active_edge = edge->GetBinding("verify_active_edge");
+  if (!verify_active_edge.empty()) {
+    bool verify_active_edge_found = false;
+    for (vector<Edge*>::iterator i = active_edges_.begin();
+         i != active_edges_.end(); ++i) {
+      if ((*i)->outputs_.size() >= 1 &&
+          (*i)->outputs_[0]->path() == verify_active_edge) {
+        verify_active_edge_found = true;
+      }
+    }
+    EXPECT_TRUE(verify_active_edge_found);
+  }
+
+  active_edges_.erase(edge_iter);
   return true;
 }
 
 vector<Edge*> FakeCommandRunner::GetActiveEdges() {
-  vector<Edge*> edges;
-  if (last_command_)
-    edges.push_back(last_command_);
-  return edges;
+  return active_edges_;
 }
 
 void FakeCommandRunner::Abort() {
-  last_command_ = NULL;
+  active_edges_.clear();
 }
 
 void BuildTest::Dirty(const string& path) {
@@ -2308,3 +2368,712 @@
   EXPECT_EQ("", err);
   ASSERT_EQ(1u, command_runner_.commands_ran_.size());
 }
+
+TEST_F(BuildTest, DyndepMissingAndNoRule) {
+  // Verify that we can diagnose when a dyndep file is missing and
+  // has no rule to build it.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"build out: touch || dd\n"
+"  dyndep = dd\n"
+));
+
+  string err;
+  EXPECT_FALSE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("loading 'dd': No such file or directory", err);
+}
+
+TEST_F(BuildTest, DyndepReadyImplicitConnection) {
+  // Verify that a dyndep file can be loaded immediately to discover
+  // that one edge has an implicit output that is also an implicit
+  // input of another edge.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"build tmp: touch || dd\n"
+"  dyndep = dd\n"
+"build out: touch || dd\n"
+"  dyndep = dd\n"
+));
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out | out.imp: dyndep | tmp.imp\n"
+"build tmp | tmp.imp: dyndep\n"
+);
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(2u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[1]);
+}
+
+TEST_F(BuildTest, DyndepReadySyntaxError) {
+  // Verify that a dyndep file can be loaded immediately to discover
+  // and reject a syntax error in it.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"build out: touch || dd\n"
+"  dyndep = dd\n"
+));
+  fs_.Create("dd",
+"build out: dyndep\n"
+);
+
+  string err;
+  EXPECT_FALSE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("dd:1: expected 'ninja_dyndep_version = ...'\n", err);
+}
+
+TEST_F(BuildTest, DyndepReadyCircular) {
+  // Verify that a dyndep file can be loaded immediately to discover
+  // and reject a circular dependency.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r in || dd\n"
+"  dyndep = dd\n"
+"build in: r circ\n"
+  ));
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out | circ: dyndep\n"
+  );
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_FALSE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("dependency cycle: circ -> in -> circ", err);
+}
+
+TEST_F(BuildTest, DyndepBuild) {
+  // Verify that a dyndep file can be built and loaded to discover nothing.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build out: touch || dd\n"
+"  dyndep = dd\n"
+));
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+);
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  size_t files_created = fs_.files_created_.size();
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+
+  ASSERT_EQ(2u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch out", command_runner_.commands_ran_[1]);
+  ASSERT_EQ(2u, fs_.files_read_.size());
+  EXPECT_EQ("dd-in", fs_.files_read_[0]);
+  EXPECT_EQ("dd", fs_.files_read_[1]);
+  ASSERT_EQ(2u + files_created, fs_.files_created_.size());
+  EXPECT_EQ(1u, fs_.files_created_.count("dd"));
+  EXPECT_EQ(1u, fs_.files_created_.count("out"));
+}
+
+TEST_F(BuildTest, DyndepBuildSyntaxError) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // and reject a syntax error in it.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build out: touch || dd\n"
+"  dyndep = dd\n"
+));
+  fs_.Create("dd-in",
+"build out: dyndep\n"
+);
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_FALSE(builder_.Build(&err));
+  EXPECT_EQ("dd:1: expected 'ninja_dyndep_version = ...'\n", err);
+}
+
+TEST_F(BuildTest, DyndepBuildUnrelatedOutput) {
+  // Verify that a dyndep file can have dependents that do not specify
+  // it as their dyndep binding.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build unrelated: touch || dd\n"
+"build out: touch unrelated || dd\n"
+"  dyndep = dd\n"
+  ));
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+);
+  fs_.Tick();
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch unrelated", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch out", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverNewOutput) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // a new output of an edge.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build out: touch in || dd\n"
+"  dyndep = dd\n"
+  ));
+  fs_.Create("in", "");
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out | out.imp: dyndep\n"
+);
+  fs_.Tick();
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(2u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[1]);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverNewOutputWithMultipleRules1) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // a new output of an edge that is already the output of another edge.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build out1 | out-twice.imp: touch in\n"
+"build out2: touch in || dd\n"
+"  dyndep = dd\n"
+  ));
+  fs_.Create("in", "");
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out2 | out-twice.imp: dyndep\n"
+);
+  fs_.Tick();
+  fs_.Create("out1", "");
+  fs_.Create("out2", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out1", &err));
+  EXPECT_TRUE(builder_.AddTarget("out2", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_FALSE(builder_.Build(&err));
+  EXPECT_EQ("multiple rules generate out-twice.imp", err);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverNewOutputWithMultipleRules2) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // a new output of an edge that is already the output of another
+  // edge also discovered by dyndep.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd1: cp dd1-in\n"
+"build out1: touch || dd1\n"
+"  dyndep = dd1\n"
+"build dd2: cp dd2-in || dd1\n" // make order predictable for test
+"build out2: touch || dd2\n"
+"  dyndep = dd2\n"
+));
+  fs_.Create("out1", "");
+  fs_.Create("out2", "");
+  fs_.Create("dd1-in",
+"ninja_dyndep_version = 1\n"
+"build out1 | out-twice.imp: dyndep\n"
+);
+  fs_.Create("dd2-in", "");
+  fs_.Create("dd2",
+"ninja_dyndep_version = 1\n"
+"build out2 | out-twice.imp: dyndep\n"
+);
+  fs_.Tick();
+  fs_.Create("out1", "");
+  fs_.Create("out2", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out1", &err));
+  EXPECT_TRUE(builder_.AddTarget("out2", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_FALSE(builder_.Build(&err));
+  EXPECT_EQ("multiple rules generate out-twice.imp", err);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverNewInput) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // a new input to an edge.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build in: touch\n"
+"build out: touch || dd\n"
+"  dyndep = dd\n"
+  ));
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | in\n"
+);
+  fs_.Tick();
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch in", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch out", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverImplicitConnection) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // that one edge has an implicit output that is also an implicit
+  // input of another edge.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build tmp: touch || dd\n"
+"  dyndep = dd\n"
+"build out: touch || dd\n"
+"  dyndep = dd\n"
+));
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out | out.imp: dyndep | tmp.imp\n"
+"build tmp | tmp.imp: dyndep\n"
+);
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverNowWantEdge) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // that an edge is actually wanted due to a missing implicit output.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build tmp: touch || dd\n"
+"  dyndep = dd\n"
+"build out: touch tmp || dd\n"
+"  dyndep = dd\n"
+));
+  fs_.Create("tmp", "");
+  fs_.Create("out", "");
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"build tmp | tmp.imp: dyndep\n"
+);
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverNowWantEdgeAndDependent) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // that an edge and a dependent are actually wanted.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build tmp: touch || dd\n"
+"  dyndep = dd\n"
+"build out: touch tmp\n"
+));
+  fs_.Create("tmp", "");
+  fs_.Create("out", "");
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build tmp | tmp.imp: dyndep\n"
+);
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverCircular) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // and reject a circular dependency.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build out: r in || dd\n"
+"  depfile = out.d\n"
+"  dyndep = dd\n"
+"build in: r || dd\n"
+"  dyndep = dd\n"
+  ));
+  fs_.Create("out.d", "out: inimp\n");
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out | circ: dyndep\n"
+"build in: dyndep | circ\n"
+  );
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_FALSE(builder_.Build(&err));
+  // Depending on how the pointers in Plan::ready_ work out, we could have
+  // discovered the cycle from either starting point.
+  EXPECT_TRUE(err == "dependency cycle: circ -> in -> circ" ||
+              err == "dependency cycle: in -> circ -> in");
+}
+
+TEST_F(BuildWithLogTest, DyndepBuildDiscoverRestat) {
+  // Verify that a dyndep file can be built and loaded to discover
+  // that an edge has a restat binding.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule true\n"
+"  command = true\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build out1: true in || dd\n"
+"  dyndep = dd\n"
+"build out2: cat out1\n"));
+
+  fs_.Create("out1", "");
+  fs_.Create("out2", "");
+  fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out1: dyndep\n"
+"  restat = 1\n"
+);
+  fs_.Tick();
+  fs_.Create("in", "");
+
+  // Do a pre-build so that there's commands in the log for the outputs,
+  // otherwise, the lack of an entry in the build log will cause "out2" to
+  // rebuild regardless of restat.
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out2", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  ASSERT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("true", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("cat out1 > out2", command_runner_.commands_ran_[2]);
+
+  command_runner_.commands_ran_.clear();
+  state_.Reset();
+  fs_.Tick();
+  fs_.Create("in", "");
+
+  // We touched "in", so we should build "out1".  But because "true" does not
+  // touch "out1", we should cancel the build of "out2".
+  EXPECT_TRUE(builder_.AddTarget("out2", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("true", command_runner_.commands_ran_[0]);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverScheduledEdge) {
+  // Verify that a dyndep file can be built and loaded to discover a
+  // new input that itself is an output from an edge that has already
+  // been scheduled but not finished.  We should not re-schedule it.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build out1 | out1.imp: touch\n"
+"build zdd: cp zdd-in\n"
+"  verify_active_edge = out1\n" // verify out1 is active when zdd is finished
+"build out2: cp out1 || zdd\n"
+"  dyndep = zdd\n"
+));
+  fs_.Create("zdd-in",
+"ninja_dyndep_version = 1\n"
+"build out2: dyndep | out1.imp\n"
+);
+
+  // Enable concurrent builds so that we can load the dyndep file
+  // while another edge is still active.
+  command_runner_.max_active_edges_ = 2;
+
+  // During the build "out1" and "zdd" should be built concurrently.
+  // The fake command runner will finish these in reverse order
+  // of the names of the first outputs, so "zdd" will finish first
+  // and we will load the dyndep file while the edge for "out1" is
+  // still active.  This will add a new dependency on "out1.imp",
+  // also produced by the active edge.  The builder should not
+  // re-schedule the already-active edge.
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out1", &err));
+  EXPECT_TRUE(builder_.AddTarget("out2", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  // Depending on how the pointers in Plan::ready_ work out, the first
+  // two commands may have run in either order.
+  EXPECT_TRUE((command_runner_.commands_ran_[0] == "touch out1 out1.imp" &&
+               command_runner_.commands_ran_[1] == "cp zdd-in zdd") ||
+              (command_runner_.commands_ran_[1] == "touch out1 out1.imp" &&
+               command_runner_.commands_ran_[0] == "cp zdd-in zdd"));
+  EXPECT_EQ("cp out1 out2", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepTwoLevelDirect) {
+  // Verify that a clean dyndep file can depend on a dirty dyndep file
+  // and be loaded properly after the dirty one is built and loaded.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd1: cp dd1-in\n"
+"build out1 | out1.imp: touch || dd1\n"
+"  dyndep = dd1\n"
+"build dd2: cp dd2-in || dd1\n" // direct order-only dep on dd1
+"build out2: touch || dd2\n"
+"  dyndep = dd2\n"
+));
+  fs_.Create("out1.imp", "");
+  fs_.Create("out2", "");
+  fs_.Create("out2.imp", "");
+  fs_.Create("dd1-in",
+"ninja_dyndep_version = 1\n"
+"build out1: dyndep\n"
+);
+  fs_.Create("dd2-in", "");
+  fs_.Create("dd2",
+"ninja_dyndep_version = 1\n"
+"build out2 | out2.imp: dyndep | out1.imp\n"
+);
+
+  // During the build dd1 should be built and loaded.  The RecomputeDirty
+  // called as a result of loading dd1 should not cause dd2 to be loaded
+  // because the builder will never get a chance to update the build plan
+  // to account for dd2.  Instead dd2 should only be later loaded once the
+  // builder recognizes that it is now ready (as its order-only dependency
+  // on dd1 has been satisfied).  This test case verifies that each dyndep
+  // file is loaded to update the build graph independently.
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out2", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch out1 out1.imp", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch out2 out2.imp", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepTwoLevelIndirect) {
+  // Verify that dyndep files can add to an edge new implicit inputs that
+  // correspond to implicit outputs added to other edges by other dyndep
+  // files on which they (order-only) depend.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out $out.imp\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd1: cp dd1-in\n"
+"build out1: touch || dd1\n"
+"  dyndep = dd1\n"
+"build dd2: cp dd2-in || out1\n" // indirect order-only dep on dd1
+"build out2: touch || dd2\n"
+"  dyndep = dd2\n"
+));
+  fs_.Create("out1.imp", "");
+  fs_.Create("out2", "");
+  fs_.Create("out2.imp", "");
+  fs_.Create("dd1-in",
+"ninja_dyndep_version = 1\n"
+"build out1 | out1.imp: dyndep\n"
+);
+  fs_.Create("dd2-in", "");
+  fs_.Create("dd2",
+"ninja_dyndep_version = 1\n"
+"build out2 | out2.imp: dyndep | out1.imp\n"
+);
+
+  // During the build dd1 should be built and loaded.  Then dd2 should
+  // be built and loaded.  Loading dd2 should cause the builder to
+  // recognize that out2 needs to be built even though it was originally
+  // clean without dyndep info.
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out2", &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(3u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch out1 out1.imp", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch out2 out2.imp", command_runner_.commands_ran_[2]);
+}
+
+TEST_F(BuildTest, DyndepTwoLevelDiscoveredReady) {
+  // Verify that a dyndep file can discover a new input whose
+  // edge also has a dyndep file that is ready to load immediately.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd0: cp dd0-in\n"
+"build dd1: cp dd1-in\n"
+"build in: touch\n"
+"build tmp: touch || dd0\n"
+"  dyndep = dd0\n"
+"build out: touch || dd1\n"
+"  dyndep = dd1\n"
+  ));
+  fs_.Create("dd1-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | tmp\n"
+);
+  fs_.Create("dd0-in", "");
+  fs_.Create("dd0",
+"ninja_dyndep_version = 1\n"
+"build tmp: dyndep | in\n"
+);
+  fs_.Tick();
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(4u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("touch in", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch tmp", command_runner_.commands_ran_[2]);
+  EXPECT_EQ("touch out", command_runner_.commands_ran_[3]);
+}
+
+TEST_F(BuildTest, DyndepTwoLevelDiscoveredDirty) {
+  // Verify that a dyndep file can discover a new input whose
+  // edge also has a dyndep file that needs to be built.
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"rule cp\n"
+"  command = cp $in $out\n"
+"build dd0: cp dd0-in\n"
+"build dd1: cp dd1-in\n"
+"build in: touch\n"
+"build tmp: touch || dd0\n"
+"  dyndep = dd0\n"
+"build out: touch || dd1\n"
+"  dyndep = dd1\n"
+  ));
+  fs_.Create("dd1-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | tmp\n"
+);
+  fs_.Create("dd0-in",
+"ninja_dyndep_version = 1\n"
+"build tmp: dyndep | in\n"
+);
+  fs_.Tick();
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(builder_.AddTarget("out", &err));
+  EXPECT_EQ("", err);
+
+  EXPECT_TRUE(builder_.Build(&err));
+  EXPECT_EQ("", err);
+  ASSERT_EQ(5u, command_runner_.commands_ran_.size());
+  EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
+  EXPECT_EQ("cp dd0-in dd0", command_runner_.commands_ran_[1]);
+  EXPECT_EQ("touch in", command_runner_.commands_ran_[2]);
+  EXPECT_EQ("touch tmp", command_runner_.commands_ran_[3]);
+  EXPECT_EQ("touch out", command_runner_.commands_ran_[4]);
+}
diff --git a/src/clean.cc b/src/clean.cc
index ce6a575..d1f221d 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -22,21 +22,12 @@
 #include "state.h"
 #include "util.h"
 
-Cleaner::Cleaner(State* state, const BuildConfig& config)
-  : state_(state),
-    config_(config),
-    removed_(),
-    cleaned_(),
-    cleaned_files_count_(0),
-    disk_interface_(new RealDiskInterface),
-    status_(0) {
-}
-
 Cleaner::Cleaner(State* state,
                  const BuildConfig& config,
                  DiskInterface* disk_interface)
   : state_(state),
     config_(config),
+    dyndep_loader_(state, disk_interface),
     removed_(),
     cleaned_(),
     cleaned_files_count_(0),
@@ -113,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
@@ -158,6 +150,7 @@
 
   Reset();
   PrintHeader();
+  LoadDyndeps();
   DoCleanTarget(target);
   PrintFooter();
   return status_;
@@ -180,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;
@@ -223,6 +217,7 @@
 
   Reset();
   PrintHeader();
+  LoadDyndeps();
   DoCleanRule(rule);
   PrintFooter();
   return status_;
@@ -247,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);
@@ -269,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 19432ab..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;
 
@@ -28,11 +29,7 @@
 struct DiskInterface;
 
 struct Cleaner {
-  /// Build a cleaner object with a real disk interface.
-  Cleaner(State* state, const BuildConfig& config);
-
   /// Build a cleaner object with the given @a disk_interface
-  /// (Useful for testing).
   Cleaner(State* state,
           const BuildConfig& config,
           DiskInterface* disk_interface);
@@ -95,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 395343b..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"
@@ -325,7 +374,7 @@
   Cleaner cleaner(&state_, config_, &fs_);
   ASSERT_EQ(0, cleaner.cleaned_files_count());
   ASSERT_EQ(0, cleaner.CleanTarget("out1"));
-  EXPECT_EQ(2, cleaner.cleaned_files_count()); 
+  EXPECT_EQ(2, cleaner.cleaned_files_count());
   ASSERT_EQ(0, cleaner.CleanTarget("in2"));
   EXPECT_EQ(2, cleaner.cleaned_files_count());
   ASSERT_EQ(0, cleaner.CleanRule("cat_rsp"));
diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc
index 405289f..6faeac6 100644
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -30,9 +30,15 @@
 // How do you end a line with a backslash?  The netbsd Make docs suggest
 // reading the result of a shell command echoing a backslash!
 //
-// Rather than implement all of above, we do a simpler thing here:
-// Backslashes escape a set of characters (see "escapes" defined below),
-// otherwise they are passed through verbatim.
+// Rather than implement all of above, we follow what GCC/Clang produces:
+// Backslashes escape a space or hash sign.
+// When a space is preceded by 2N+1 backslashes, it is represents N backslashes
+// followed by space.
+// When a space is preceded by 2N backslashes, it represents 2N backslashes at
+// the end of a filename.
+// A hash sign is escaped by a single backslash. All other backslashes remain
+// unchanged.
+//
 // If anyone actually has depfiles that rely on the more complicated
 // behavior we can adjust this.
 bool DepfileParser::Parse(string* content, string* err) {
@@ -72,7 +78,7 @@
         128, 128, 128, 128, 128, 128, 128, 128, 
         128, 128, 128, 128, 128, 128, 128, 128, 
         128, 128, 128, 128, 128, 128, 128, 128, 
-        128, 128, 128,   0,   0,   0,   0, 128, 
+        128, 128, 128, 128,   0, 128,   0, 128, 
           0, 128, 128, 128, 128, 128, 128, 128, 
         128, 128, 128, 128, 128, 128, 128, 128, 
         128, 128, 128, 128, 128, 128, 128, 128, 
@@ -111,7 +117,8 @@
           if (yych <= '#') goto yy4;
           goto yy12;
         } else {
-          if (yych == '\\') goto yy13;
+          if (yych <= '?') goto yy4;
+          if (yych <= '\\') goto yy13;
           goto yy4;
         }
       }
@@ -143,6 +150,7 @@
       if (yybm[0+yych] & 128) {
         goto yy9;
       }
+yy11:
       {
         // Got a span of plain text.
         int len = (int)(in - start);
@@ -158,24 +166,22 @@
       goto yy5;
 yy13:
       yych = *(yymarker = ++in);
-      if (yych <= '"') {
-        if (yych <= '\f') {
+      if (yych <= 0x1F) {
+        if (yych <= '\n') {
           if (yych <= 0x00) goto yy5;
-          if (yych == '\n') goto yy18;
-          goto yy16;
+          if (yych <= '\t') goto yy16;
+          goto yy17;
         } else {
-          if (yych <= '\r') goto yy20;
-          if (yych == ' ') goto yy22;
+          if (yych == '\r') goto yy19;
           goto yy16;
         }
       } else {
-        if (yych <= 'Z') {
-          if (yych <= '#') goto yy22;
-          if (yych == '*') goto yy22;
-          goto yy16;
+        if (yych <= '#') {
+          if (yych <= ' ') goto yy21;
+          if (yych <= '"') goto yy16;
+          goto yy23;
         } else {
-          if (yych <= ']') goto yy22;
-          if (yych == '|') goto yy22;
+          if (yych == '\\') goto yy25;
           goto yy16;
         }
       }
@@ -188,30 +194,93 @@
       }
 yy16:
       ++in;
-      {
-        // Let backslash before other characters through verbatim.
-        *out++ = '\\';
-        *out++ = yych;
-        continue;
-      }
-yy18:
+      goto yy11;
+yy17:
       ++in;
       {
         // A line continuation ends the current file name.
         break;
       }
-yy20:
+yy19:
       yych = *++in;
-      if (yych == '\n') goto yy18;
+      if (yych == '\n') goto yy17;
       in = yymarker;
       goto yy5;
-yy22:
+yy21:
       ++in;
       {
-        // De-escape backslashed character.
-        *out++ = yych;
+        // 2N+1 backslashes plus space -> N backslashes plus space.
+        int len = (int)(in - start);
+        int n = len / 2 - 1;
+        if (out < start)
+          memset(out, '\\', n);
+        out += n;
+        *out++ = ' ';
         continue;
       }
+yy23:
+      ++in;
+      {
+        // De-escape hash sign, but preserve other leading backslashes.
+        int len = (int)(in - start);
+        if (len > 2 && out < start)
+          memset(out, '\\', len - 2);
+        out += len - 2;
+        *out++ = '#';
+        continue;
+      }
+yy25:
+      yych = *++in;
+      if (yych <= 0x1F) {
+        if (yych <= '\n') {
+          if (yych <= 0x00) goto yy11;
+          if (yych <= '\t') goto yy16;
+          goto yy11;
+        } else {
+          if (yych == '\r') goto yy11;
+          goto yy16;
+        }
+      } else {
+        if (yych <= '#') {
+          if (yych <= ' ') goto yy26;
+          if (yych <= '"') goto yy16;
+          goto yy23;
+        } else {
+          if (yych == '\\') goto yy28;
+          goto yy16;
+        }
+      }
+yy26:
+      ++in;
+      {
+        // 2N backslashes plus space -> 2N backslashes, end of filename.
+        int len = (int)(in - start);
+        if (out < start)
+          memset(out, '\\', len - 1);
+        out += len - 1;
+        break;
+      }
+yy28:
+      yych = *++in;
+      if (yych <= 0x1F) {
+        if (yych <= '\n') {
+          if (yych <= 0x00) goto yy11;
+          if (yych <= '\t') goto yy16;
+          goto yy11;
+        } else {
+          if (yych == '\r') goto yy11;
+          goto yy16;
+        }
+      } else {
+        if (yych <= '#') {
+          if (yych <= ' ') goto yy21;
+          if (yych <= '"') goto yy16;
+          goto yy23;
+        } else {
+          if (yych == '\\') goto yy25;
+          goto yy16;
+        }
+      }
     }
 
     }
diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc
index f8c94b3..735a0c3 100644
--- a/src/depfile_parser.in.cc
+++ b/src/depfile_parser.in.cc
@@ -29,9 +29,15 @@
 // How do you end a line with a backslash?  The netbsd Make docs suggest
 // reading the result of a shell command echoing a backslash!
 //
-// Rather than implement all of above, we do a simpler thing here:
-// Backslashes escape a set of characters (see "escapes" defined below),
-// otherwise they are passed through verbatim.
+// Rather than implement all of above, we follow what GCC/Clang produces:
+// Backslashes escape a space or hash sign.
+// When a space is preceded by 2N+1 backslashes, it is represents N backslashes
+// followed by space.
+// When a space is preceded by 2N backslashes, it represents 2N backslashes at
+// the end of a filename.
+// A hash sign is escaped by a single backslash. All other backslashes remain
+// unchanged.
+//
 // If anyone actually has depfiles that rely on the more complicated
 // behavior we can adjust this.
 bool DepfileParser::Parse(string* content, string* err) {
@@ -68,12 +74,33 @@
       re2c:indent:string = "  ";
 
       nul = "\000";
-      escape = [ \\#*[|\]];
       newline = '\r'?'\n';
 
-      '\\' escape {
-        // De-escape backslashed character.
-        *out++ = yych;
+      '\\\\'* '\\ ' {
+        // 2N+1 backslashes plus space -> N backslashes plus space.
+        int len = (int)(in - start);
+        int n = len / 2 - 1;
+        if (out < start)
+          memset(out, '\\', n);
+        out += n;
+        *out++ = ' ';
+        continue;
+      }
+      '\\\\'+ ' ' {
+        // 2N backslashes plus space -> 2N backslashes, end of filename.
+        int len = (int)(in - start);
+        if (out < start)
+          memset(out, '\\', len - 1);
+        out += len - 1;
+        break;
+      }
+      '\\'+ '#' {
+        // De-escape hash sign, but preserve other leading backslashes.
+        int len = (int)(in - start);
+        if (len > 2 && out < start)
+          memset(out, '\\', len - 2);
+        out += len - 2;
+        *out++ = '#';
         continue;
       }
       '$$' {
@@ -81,13 +108,7 @@
         *out++ = '$';
         continue;
       }
-      '\\' [^\000\r\n] {
-        // Let backslash before other characters through verbatim.
-        *out++ = '\\';
-        *out++ = yych;
-        continue;
-      }
-      [a-zA-Z0-9+,/_:.~()}{%@=!\x80-\xFF-]+ {
+      '\\'+ [^\000\r\n] | [a-zA-Z0-9+,/_:.~()}{%=@\x5B\x5D!\x80-\xFF-]+ {
         // Got a span of plain text.
         int len = (int)(in - start);
         // Need to shift it over if we're overwriting backslashes.
diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc
index 52fe7cd..19224f3 100644
--- a/src/depfile_parser_test.cc
+++ b/src/depfile_parser_test.cc
@@ -101,15 +101,36 @@
             parser_.ins_[2].AsString());
 }
 
+TEST_F(DepfileParserTest, MultipleBackslashes) {
+  // Successive 2N+1 backslashes followed by space (' ') are replaced by N >= 0
+  // backslashes and the space. A single backslash before hash sign is removed.
+  // Other backslashes remain untouched (including 2N backslashes followed by
+  // space).
+  string err;
+  EXPECT_TRUE(Parse(
+"a\\ b\\#c.h: \\\\\\\\\\  \\\\\\\\ \\\\share\\info\\\\#1",
+      &err));
+  ASSERT_EQ("", err);
+  EXPECT_EQ("a b#c.h",
+            parser_.out_.AsString());
+  ASSERT_EQ(3u, parser_.ins_.size());
+  EXPECT_EQ("\\\\ ",
+            parser_.ins_[0].AsString());
+  EXPECT_EQ("\\\\\\\\",
+            parser_.ins_[1].AsString());
+  EXPECT_EQ("\\\\share\\info\\#1",
+            parser_.ins_[2].AsString());
+}
+
 TEST_F(DepfileParserTest, Escapes) {
   // Put backslashes before a variety of characters, see which ones make
   // it through.
   string err;
   EXPECT_TRUE(Parse(
-"\\!\\@\\#$$\\%\\^\\&\\\\:",
+"\\!\\@\\#$$\\%\\^\\&\\[\\]\\\\:",
       &err));
   ASSERT_EQ("", err);
-  EXPECT_EQ("\\!\\@#$\\%\\^\\&\\",
+  EXPECT_EQ("\\!\\@#$\\%\\^\\&\\[\\]\\\\",
             parser_.out_.AsString());
   ASSERT_EQ(0u, parser_.ins_.size());
 }
@@ -123,7 +144,7 @@
 " en@quot.header~ t+t-x!=1 \\\n"
 " openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif\\\n"
 " Fu\303\244ball\\\n"
-" a\\[1\\]b@2%c",
+" a[1]b@2%c",
       &err));
   ASSERT_EQ("", err);
   EXPECT_EQ("C:/Program Files (x86)/Microsoft crtdefs.h",
diff --git a/src/deps_log.cc b/src/deps_log.cc
index 0bb96f3..4aaffeb 100644
--- a/src/deps_log.cc
+++ b/src/deps_log.cc
@@ -48,7 +48,7 @@
     if (!Recompact(path, err))
       return false;
   }
-  
+
   file_ = fopen(path.c_str(), "ab");
   if (!file_) {
     *err = strerror(errno);
@@ -331,7 +331,7 @@
   // will refer to the ordering in new_log, not in the current log.
   for (vector<Node*>::iterator i = nodes_.begin(); i != nodes_.end(); ++i)
     (*i)->set_id(-1);
-  
+
   // Write out all deps again.
   for (int old_id = 0; old_id < (int)deps_.size(); ++old_id) {
     Deps* deps = deps_[old_id];
diff --git a/src/dyndep.cc b/src/dyndep.cc
new file mode 100644
index 0000000..2aee601
--- /dev/null
+++ b/src/dyndep.cc
@@ -0,0 +1,124 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dyndep.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "debug_flags.h"
+#include "disk_interface.h"
+#include "dyndep_parser.h"
+#include "graph.h"
+#include "state.h"
+#include "util.h"
+
+bool DyndepLoader::LoadDyndeps(Node* node, std::string* err) const {
+  DyndepFile ddf;
+  return LoadDyndeps(node, &ddf, err);
+}
+
+bool DyndepLoader::LoadDyndeps(Node* node, DyndepFile* ddf,
+                               std::string* err) const {
+  // We are loading the dyndep file now so it is no longer pending.
+  node->set_dyndep_pending(false);
+
+  // Load the dyndep information from the file.
+  EXPLAIN("loading dyndep file '%s'", node->path().c_str());
+  if (!LoadDyndepFile(node, ddf, err))
+    return false;
+
+  // Update each edge that specified this node as its dyndep binding.
+  std::vector<Edge*> const& out_edges = node->out_edges();
+  for (std::vector<Edge*>::const_iterator oe = out_edges.begin();
+       oe != out_edges.end(); ++oe) {
+    Edge* const edge = *oe;
+    if (edge->dyndep_ != node)
+      continue;
+
+    DyndepFile::iterator ddi = ddf->find(edge);
+    if (ddi == ddf->end()) {
+      *err = ("'" + edge->outputs_[0]->path() + "' "
+              "not mentioned in its dyndep file "
+              "'" + node->path() + "'");
+      return false;
+    }
+
+    ddi->second.used_ = true;
+    Dyndeps const& dyndeps = ddi->second;
+    if (!UpdateEdge(edge, &dyndeps, err)) {
+      return false;
+    }
+  }
+
+  // Reject extra outputs in dyndep file.
+  for (DyndepFile::const_iterator oe = ddf->begin(); oe != ddf->end();
+       ++oe) {
+    if (!oe->second.used_) {
+      Edge* const edge = oe->first;
+      *err = ("dyndep file '" + node->path() + "' mentions output "
+              "'" + edge->outputs_[0]->path() + "' whose build statement "
+              "does not have a dyndep binding for the file");
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool DyndepLoader::UpdateEdge(Edge* edge, Dyndeps const* dyndeps,
+                              std::string* err) const {
+  // Add dyndep-discovered bindings to the edge.
+  // We know the edge already has its own binding
+  // scope because it has a "dyndep" binding.
+  if (dyndeps->restat_)
+    edge->env_->AddBinding("restat", "1");
+
+  // Add the dyndep-discovered outputs to the edge.
+  edge->outputs_.insert(edge->outputs_.end(),
+                        dyndeps->implicit_outputs_.begin(),
+                        dyndeps->implicit_outputs_.end());
+  edge->implicit_outs_ += dyndeps->implicit_outputs_.size();
+
+  // Add this edge as incoming to each new output.
+  for (std::vector<Node*>::const_iterator i =
+           dyndeps->implicit_outputs_.begin();
+       i != dyndeps->implicit_outputs_.end(); ++i) {
+    if ((*i)->in_edge() != NULL) {
+      *err = "multiple rules generate " + (*i)->path();
+      return false;
+    }
+    (*i)->set_in_edge(edge);
+  }
+
+  // Add the dyndep-discovered inputs to the edge.
+  edge->inputs_.insert(edge->inputs_.end() - edge->order_only_deps_,
+                       dyndeps->implicit_inputs_.begin(),
+                       dyndeps->implicit_inputs_.end());
+  edge->implicit_deps_ += dyndeps->implicit_inputs_.size();
+
+  // Add this edge as outgoing from each new input.
+  for (std::vector<Node*>::const_iterator i =
+           dyndeps->implicit_inputs_.begin();
+       i != dyndeps->implicit_inputs_.end(); ++i)
+    (*i)->AddOutEdge(edge);
+
+  return true;
+}
+
+bool DyndepLoader::LoadDyndepFile(Node* file, DyndepFile* ddf,
+                                  std::string* err) const {
+  DyndepParser parser(state_, disk_interface_, ddf);
+  return parser.Load(file->path(), err);
+}
diff --git a/src/dyndep.h b/src/dyndep.h
new file mode 100644
index 0000000..907f921
--- /dev/null
+++ b/src/dyndep.h
@@ -0,0 +1,64 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NINJA_DYNDEP_LOADER_H_
+#define NINJA_DYNDEP_LOADER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+struct DiskInterface;
+struct Edge;
+struct Node;
+struct State;
+
+/// Store dynamically-discovered dependency information for one edge.
+struct Dyndeps {
+  Dyndeps() : used_(false), restat_(false) {}
+  bool used_;
+  bool restat_;
+  std::vector<Node*> implicit_inputs_;
+  std::vector<Node*> implicit_outputs_;
+};
+
+/// Store data loaded from one dyndep file.  Map from an edge
+/// to its dynamically-discovered dependency information.
+/// This is a struct rather than a typedef so that we can
+/// forward-declare it in other headers.
+struct DyndepFile: public std::map<Edge*, Dyndeps> {};
+
+/// DyndepLoader loads dynamically discovered dependencies, as
+/// referenced via the "dyndep" attribute in build files.
+struct DyndepLoader {
+  DyndepLoader(State* state, DiskInterface* disk_interface)
+      : state_(state), disk_interface_(disk_interface) {}
+
+  /// Load a dyndep file from the given node's path and update the
+  /// build graph with the new information.  One overload accepts
+  /// a caller-owned 'DyndepFile' object in which to store the
+  /// information loaded from the dyndep file.
+  bool LoadDyndeps(Node* node, std::string* err) const;
+  bool LoadDyndeps(Node* node, DyndepFile* ddf, std::string* err) const;
+
+ private:
+  bool LoadDyndepFile(Node* file, DyndepFile* ddf, std::string* err) const;
+
+  bool UpdateEdge(Edge* edge, Dyndeps const* dyndeps, std::string* err) const;
+
+  State* state_;
+  DiskInterface* disk_interface_;
+};
+
+#endif  // NINJA_DYNDEP_LOADER_H_
diff --git a/src/dyndep_parser.cc b/src/dyndep_parser.cc
new file mode 100644
index 0000000..baebbac
--- /dev/null
+++ b/src/dyndep_parser.cc
@@ -0,0 +1,223 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dyndep_parser.h"
+
+#include <vector>
+
+#include "dyndep.h"
+#include "graph.h"
+#include "state.h"
+#include "util.h"
+#include "version.h"
+
+DyndepParser::DyndepParser(State* state, FileReader* file_reader,
+                           DyndepFile* dyndep_file)
+    : Parser(state, file_reader)
+    , dyndep_file_(dyndep_file) {
+}
+
+bool DyndepParser::Parse(const string& filename, const string& input,
+                         string* err) {
+  lexer_.Start(filename, input);
+
+  // Require a supported ninja_dyndep_version value immediately so
+  // we can exit before encountering any syntactic surprises.
+  bool haveDyndepVersion = false;
+
+  for (;;) {
+    Lexer::Token token = lexer_.ReadToken();
+    switch (token) {
+    case Lexer::BUILD: {
+      if (!haveDyndepVersion)
+        return lexer_.Error("expected 'ninja_dyndep_version = ...'", err);
+      if (!ParseEdge(err))
+        return false;
+      break;
+    }
+    case Lexer::IDENT: {
+      lexer_.UnreadToken();
+      if (haveDyndepVersion)
+        return lexer_.Error(string("unexpected ") + Lexer::TokenName(token),
+                            err);
+      if (!ParseDyndepVersion(err))
+        return false;
+      haveDyndepVersion = true;
+      break;
+    }
+    case Lexer::ERROR:
+      return lexer_.Error(lexer_.DescribeLastError(), err);
+    case Lexer::TEOF:
+      if (!haveDyndepVersion)
+        return lexer_.Error("expected 'ninja_dyndep_version = ...'", err);
+      return true;
+    case Lexer::NEWLINE:
+      break;
+    default:
+      return lexer_.Error(string("unexpected ") + Lexer::TokenName(token),
+                          err);
+    }
+  }
+  return false;  // not reached
+}
+
+bool DyndepParser::ParseDyndepVersion(string* err) {
+  string name;
+  EvalString let_value;
+  if (!ParseLet(&name, &let_value, err))
+    return false;
+  if (name != "ninja_dyndep_version") {
+    return lexer_.Error("expected 'ninja_dyndep_version = ...'", err);
+  }
+  string version = let_value.Evaluate(&env_);
+  int major, minor;
+  ParseVersion(version, &major, &minor);
+  if (major != 1 || minor != 0) {
+    return lexer_.Error(
+      string("unsupported 'ninja_dyndep_version = ") + version + "'", err);
+    return false;
+  }
+  return true;
+}
+
+bool DyndepParser::ParseLet(string* key, EvalString* value, string* err) {
+  if (!lexer_.ReadIdent(key))
+    return lexer_.Error("expected variable name", err);
+  if (!ExpectToken(Lexer::EQUALS, err))
+    return false;
+  if (!lexer_.ReadVarValue(value, err))
+    return false;
+  return true;
+}
+
+bool DyndepParser::ParseEdge(string* err) {
+  // Parse one explicit output.  We expect it to already have an edge.
+  // We will record its dynamically-discovered dependency information.
+  Dyndeps* dyndeps = NULL;
+  {
+    EvalString out0;
+    if (!lexer_.ReadPath(&out0, err))
+      return false;
+    if (out0.empty())
+      return lexer_.Error("expected path", err);
+
+    string path = out0.Evaluate(&env_);
+    string path_err;
+    uint64_t slash_bits;
+    if (!CanonicalizePath(&path, &slash_bits, &path_err))
+      return lexer_.Error(path_err, err);
+    Node* node = state_->LookupNode(path);
+    if (!node || !node->in_edge())
+      return lexer_.Error("no build statement exists for '" + path + "'", err);
+    Edge* edge = node->in_edge();
+    std::pair<DyndepFile::iterator, bool> res =
+      dyndep_file_->insert(DyndepFile::value_type(edge, Dyndeps()));
+    if (!res.second)
+      return lexer_.Error("multiple statements for '" + path + "'", err);
+    dyndeps = &res.first->second;
+  }
+
+  // Disallow explicit outputs.
+  {
+    EvalString out;
+    if (!lexer_.ReadPath(&out, err))
+      return false;
+    if (!out.empty())
+      return lexer_.Error("explicit outputs not supported", err);
+  }
+
+  // Parse implicit outputs, if any.
+  vector<EvalString> outs;
+  if (lexer_.PeekToken(Lexer::PIPE)) {
+    for (;;) {
+      EvalString out;
+      if (!lexer_.ReadPath(&out, err))
+        return err;
+      if (out.empty())
+        break;
+      outs.push_back(out);
+    }
+  }
+
+  if (!ExpectToken(Lexer::COLON, err))
+    return false;
+
+  string rule_name;
+  if (!lexer_.ReadIdent(&rule_name) || rule_name != "dyndep")
+    return lexer_.Error("expected build command name 'dyndep'", err);
+
+  // Disallow explicit inputs.
+  {
+    EvalString in;
+    if (!lexer_.ReadPath(&in, err))
+      return false;
+    if (!in.empty())
+      return lexer_.Error("explicit inputs not supported", err);
+  }
+
+  // Parse implicit inputs, if any.
+  vector<EvalString> ins;
+  if (lexer_.PeekToken(Lexer::PIPE)) {
+    for (;;) {
+      EvalString in;
+      if (!lexer_.ReadPath(&in, err))
+        return err;
+      if (in.empty())
+        break;
+      ins.push_back(in);
+    }
+  }
+
+  // Disallow order-only inputs.
+  if (lexer_.PeekToken(Lexer::PIPE2))
+    return lexer_.Error("order-only inputs not supported", err);
+
+  if (!ExpectToken(Lexer::NEWLINE, err))
+    return false;
+
+  if (lexer_.PeekToken(Lexer::INDENT)) {
+    string key;
+    EvalString val;
+    if (!ParseLet(&key, &val, err))
+      return false;
+    if (key != "restat")
+      return lexer_.Error("binding is not 'restat'", err);
+    string value = val.Evaluate(&env_);
+    dyndeps->restat_ = !value.empty();
+  }
+
+  dyndeps->implicit_inputs_.reserve(ins.size());
+  for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) {
+    string path = i->Evaluate(&env_);
+    string path_err;
+    uint64_t slash_bits;
+    if (!CanonicalizePath(&path, &slash_bits, &path_err))
+      return lexer_.Error(path_err, err);
+    Node* n = state_->GetNode(path, slash_bits);
+    dyndeps->implicit_inputs_.push_back(n);
+  }
+
+  dyndeps->implicit_outputs_.reserve(outs.size());
+  for (vector<EvalString>::iterator i = outs.begin(); i != outs.end(); ++i) {
+    string path = i->Evaluate(&env_);
+    string path_err;
+    uint64_t slash_bits;
+    if (!CanonicalizePath(&path, &slash_bits, &path_err))
+      return lexer_.Error(path_err, err);
+    Node* n = state_->GetNode(path, slash_bits);
+    dyndeps->implicit_outputs_.push_back(n);
+  }
+
+  return true;
+}
diff --git a/src/dyndep_parser.h b/src/dyndep_parser.h
new file mode 100644
index 0000000..09a3722
--- /dev/null
+++ b/src/dyndep_parser.h
@@ -0,0 +1,46 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NINJA_DYNDEP_PARSER_H_
+#define NINJA_DYNDEP_PARSER_H_
+
+#include "eval_env.h"
+#include "parser.h"
+
+struct DyndepFile;
+struct EvalString;
+
+/// Parses dyndep files.
+struct DyndepParser: public Parser {
+  DyndepParser(State* state, FileReader* file_reader,
+               DyndepFile* dyndep_file);
+
+  /// Parse a text string of input.  Used by tests.
+  bool ParseTest(const string& input, string* err) {
+    return Parse("input", input, err);
+  }
+
+private:
+  /// Parse a file, given its contents as a string.
+  bool Parse(const string& filename, const string& input, string* err);
+
+  bool ParseDyndepVersion(string* err);
+  bool ParseLet(string* key, EvalString* val, string* err);
+  bool ParseEdge(string* err);
+
+  DyndepFile* dyndep_file_;
+  BindingEnv env_;
+};
+
+#endif  // NINJA_DYNDEP_PARSER_H_
diff --git a/src/dyndep_parser_test.cc b/src/dyndep_parser_test.cc
new file mode 100644
index 0000000..39ec657
--- /dev/null
+++ b/src/dyndep_parser_test.cc
@@ -0,0 +1,512 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dyndep_parser.h"
+
+#include <map>
+#include <vector>
+
+#include "dyndep.h"
+#include "graph.h"
+#include "state.h"
+#include "test.h"
+
+struct DyndepParserTest : public testing::Test {
+  void AssertParse(const char* input) {
+    DyndepParser parser(&state_, &fs_, &dyndep_file_);
+    string err;
+    EXPECT_TRUE(parser.ParseTest(input, &err));
+    ASSERT_EQ("", err);
+  }
+
+  virtual void SetUp() {
+    ::AssertParse(&state_,
+"rule touch\n"
+"  command = touch $out\n"
+"build out otherout: touch\n");
+  }
+
+  State state_;
+  VirtualFileSystem fs_;
+  DyndepFile dyndep_file_;
+};
+
+TEST_F(DyndepParserTest, Empty) {
+  const char kInput[] =
+"";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n", err);
+}
+
+TEST_F(DyndepParserTest, Version1) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"));
+}
+
+TEST_F(DyndepParserTest, Version1Extra) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1-extra\n"));
+}
+
+TEST_F(DyndepParserTest, Version1_0) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1.0\n"));
+}
+
+TEST_F(DyndepParserTest, Version1_0Extra) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1.0-extra\n"));
+}
+
+TEST_F(DyndepParserTest, CommentVersion) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"# comment\n"
+"ninja_dyndep_version = 1\n"));
+}
+
+TEST_F(DyndepParserTest, BlankLineVersion) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"\n"
+"ninja_dyndep_version = 1\n"));
+}
+
+TEST_F(DyndepParserTest, VersionCRLF) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\r\n"));
+}
+
+TEST_F(DyndepParserTest, CommentVersionCRLF) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"# comment\r\n"
+"ninja_dyndep_version = 1\r\n"));
+}
+
+TEST_F(DyndepParserTest, BlankLineVersionCRLF) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"\r\n"
+"ninja_dyndep_version = 1\r\n"));
+}
+
+TEST_F(DyndepParserTest, VersionUnexpectedEOF) {
+  const char kInput[] =
+"ninja_dyndep_version = 1.0";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: unexpected EOF\n"
+            "ninja_dyndep_version = 1.0\n"
+            "                          ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, UnsupportedVersion0) {
+  const char kInput[] =
+"ninja_dyndep_version = 0\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: unsupported 'ninja_dyndep_version = 0'\n"
+            "ninja_dyndep_version = 0\n"
+            "                        ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, UnsupportedVersion1_1) {
+  const char kInput[] =
+"ninja_dyndep_version = 1.1\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: unsupported 'ninja_dyndep_version = 1.1'\n"
+            "ninja_dyndep_version = 1.1\n"
+            "                          ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, DuplicateVersion) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"ninja_dyndep_version = 1\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: unexpected identifier\n", err);
+}
+
+TEST_F(DyndepParserTest, MissingVersionOtherVar) {
+  const char kInput[] =
+"not_ninja_dyndep_version = 1\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n"
+            "not_ninja_dyndep_version = 1\n"
+            "                            ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, MissingVersionBuild) {
+  const char kInput[] =
+"build out: dyndep\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n", err);
+}
+
+TEST_F(DyndepParserTest, UnexpectedEqual) {
+  const char kInput[] =
+"= 1\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: unexpected '='\n", err);
+}
+
+TEST_F(DyndepParserTest, UnexpectedIndent) {
+  const char kInput[] =
+" = 1\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:1: unexpected indent\n", err);
+}
+
+TEST_F(DyndepParserTest, OutDuplicate) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"build out: dyndep\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:3: multiple statements for 'out'\n"
+            "build out: dyndep\n"
+            "         ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, OutDuplicateThroughOther) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"build otherout: dyndep\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:3: multiple statements for 'otherout'\n"
+            "build otherout: dyndep\n"
+            "              ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, NoOutEOF) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: unexpected EOF\n"
+            "build\n"
+            "     ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, NoOutColon) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build :\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: expected path\n"
+            "build :\n"
+            "      ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, OutNoStatement) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build missing: dyndep\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: no build statement exists for 'missing'\n"
+            "build missing: dyndep\n"
+            "             ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, OutEOF) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: unexpected EOF\n"
+            "build out\n"
+            "         ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, OutNoRule) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out:";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: expected build command name 'dyndep'\n"
+            "build out:\n"
+            "          ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, OutBadRule) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: touch";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: expected build command name 'dyndep'\n"
+            "build out: touch\n"
+            "           ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, BuildEOF) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: dyndep";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: unexpected EOF\n"
+            "build out: dyndep\n"
+            "                 ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, ExplicitOut) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out exp: dyndep\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: explicit outputs not supported\n"
+            "build out exp: dyndep\n"
+            "             ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, ExplicitIn) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: dyndep exp\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: explicit inputs not supported\n"
+            "build out: dyndep exp\n"
+            "                     ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, OrderOnlyIn) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: dyndep ||\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:2: order-only inputs not supported\n"
+            "build out: dyndep ||\n"
+            "                  ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, BadBinding) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"  not_restat = 1\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:3: binding is not 'restat'\n"
+            "  not_restat = 1\n"
+            "                ^ near here", err);
+}
+
+TEST_F(DyndepParserTest, RestatTwice) {
+  const char kInput[] =
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"  restat = 1\n"
+"  restat = 1\n";
+  DyndepParser parser(&state_, &fs_, &dyndep_file_);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(kInput, &err));
+  EXPECT_EQ("input:4: unexpected indent\n", err);
+}
+
+TEST_F(DyndepParserTest, NoImplicit) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+}
+
+TEST_F(DyndepParserTest, EmptyImplicit) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out | : dyndep |\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+}
+
+TEST_F(DyndepParserTest, ImplicitIn) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | impin\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+  ASSERT_EQ(1u, i->second.implicit_inputs_.size());
+  EXPECT_EQ("impin", i->second.implicit_inputs_[0]->path());
+}
+
+TEST_F(DyndepParserTest, ImplicitIns) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | impin1 impin2\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+  ASSERT_EQ(2u, i->second.implicit_inputs_.size());
+  EXPECT_EQ("impin1", i->second.implicit_inputs_[0]->path());
+  EXPECT_EQ("impin2", i->second.implicit_inputs_[1]->path());
+}
+
+TEST_F(DyndepParserTest, ImplicitOut) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out | impout: dyndep\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  ASSERT_EQ(1u, i->second.implicit_outputs_.size());
+  EXPECT_EQ("impout", i->second.implicit_outputs_[0]->path());
+  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+}
+
+TEST_F(DyndepParserTest, ImplicitOuts) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out | impout1 impout2 : dyndep\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  ASSERT_EQ(2u, i->second.implicit_outputs_.size());
+  EXPECT_EQ("impout1", i->second.implicit_outputs_[0]->path());
+  EXPECT_EQ("impout2", i->second.implicit_outputs_[1]->path());
+  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+}
+
+TEST_F(DyndepParserTest, ImplicitInsAndOuts) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out | impout1 impout2: dyndep | impin1 impin2\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  ASSERT_EQ(2u, i->second.implicit_outputs_.size());
+  EXPECT_EQ("impout1", i->second.implicit_outputs_[0]->path());
+  EXPECT_EQ("impout2", i->second.implicit_outputs_[1]->path());
+  ASSERT_EQ(2u, i->second.implicit_inputs_.size());
+  EXPECT_EQ("impin1", i->second.implicit_inputs_[0]->path());
+  EXPECT_EQ("impin2", i->second.implicit_inputs_[1]->path());
+}
+
+TEST_F(DyndepParserTest, Restat) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"  restat = 1\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(true, i->second.restat_);
+  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+}
+
+TEST_F(DyndepParserTest, OtherOutput) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build otherout: dyndep\n"));
+
+  EXPECT_EQ(1u, dyndep_file_.size());
+  DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+  ASSERT_NE(i, dyndep_file_.end());
+  EXPECT_EQ(false, i->second.restat_);
+  EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+  EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+}
+
+TEST_F(DyndepParserTest, MultipleEdges) {
+    ::AssertParse(&state_,
+"build out2: touch\n");
+  ASSERT_EQ(2u, state_.edges_.size());
+  ASSERT_EQ(1u, state_.edges_[1]->outputs_.size());
+  EXPECT_EQ("out2", state_.edges_[1]->outputs_[0]->path());
+  EXPECT_EQ(0u, state_.edges_[0]->inputs_.size());
+
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"build out2: dyndep\n"
+"  restat = 1\n"));
+
+  EXPECT_EQ(2u, dyndep_file_.size());
+  {
+    DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
+    ASSERT_NE(i, dyndep_file_.end());
+    EXPECT_EQ(false, i->second.restat_);
+    EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+    EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+  }
+  {
+    DyndepFile::iterator i = dyndep_file_.find(state_.edges_[1]);
+    ASSERT_NE(i, dyndep_file_.end());
+    EXPECT_EQ(true, i->second.restat_);
+    EXPECT_EQ(0u, i->second.implicit_outputs_.size());
+    EXPECT_EQ(0u, i->second.implicit_inputs_.size());
+  }
+}
diff --git a/src/eval_env.cc b/src/eval_env.cc
index 8817a87..e9b6c43 100644
--- a/src/eval_env.cc
+++ b/src/eval_env.cc
@@ -65,6 +65,7 @@
 bool Rule::IsReservedBinding(const string& var) {
   return var == "command" ||
       var == "depfile" ||
+      var == "dyndep" ||
       var == "description" ||
       var == "deps" ||
       var == "generator" ||
@@ -130,3 +131,17 @@
   }
   return result;
 }
+
+string EvalString::Unparse() const {
+  string result;
+  for (TokenList::const_iterator i = parsed_.begin();
+       i != parsed_.end(); ++i) {
+    bool special = (i->second == SPECIAL);
+    if (special)
+      result.append("${");
+    result.append(i->first);
+    if (special)
+      result.append("}");
+  }
+  return result;
+}
diff --git a/src/eval_env.h b/src/eval_env.h
index 999ce42..8fb9bf4 100644
--- a/src/eval_env.h
+++ b/src/eval_env.h
@@ -33,8 +33,13 @@
 /// A tokenized string that contains variable references.
 /// Can be evaluated relative to an Env.
 struct EvalString {
+  /// @return The evaluated string with variable expanded using value found in
+  ///         environment @a env.
   string Evaluate(Env* env) const;
 
+  /// @return The string with variables not expanded.
+  string Unparse() const;
+
   void Clear() { parsed_.clear(); }
   bool empty() const { return parsed_.empty(); }
 
diff --git a/src/getopt.c b/src/getopt.c
index 0c2ef35..861f07f 100644
--- a/src/getopt.c
+++ b/src/getopt.c
@@ -75,7 +75,7 @@
 
 Copyright (C) 1997 Gregory Pietsch
 
-This file and the accompanying getopt.h header file are hereby placed in the 
+This file and the accompanying getopt.h header file are hereby placed in the
 public domain without restrictions.  Just give the author credit, don't
 claim you wrote it or prevent anyone else from using it.
 
diff --git a/src/graph.cc b/src/graph.cc
index 9c2f784..376b911 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -68,6 +68,31 @@
   edge->outputs_ready_ = true;
   edge->deps_missing_ = false;
 
+  if (!edge->deps_loaded_) {
+    // This is our first encounter with this edge.
+    // If there is a pending dyndep file, visit it now:
+    // * If the dyndep file is ready then load it now to get any
+    //   additional inputs and outputs for this and other edges.
+    //   Once the dyndep file is loaded it will no longer be pending
+    //   if any other edges encounter it, but they will already have
+    //   been updated.
+    // * If the dyndep file is not ready then since is known to be an
+    //   input to this edge, the edge will not be considered ready below.
+    //   Later during the build the dyndep file will become ready and be
+    //   loaded to update this edge before it can possibly be scheduled.
+    if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
+      if (!RecomputeDirty(edge->dyndep_, stack, err))
+        return false;
+
+      if (!edge->dyndep_->in_edge() ||
+          edge->dyndep_->in_edge()->outputs_ready()) {
+        // The dyndep file is ready, so load it now.
+        if (!LoadDyndeps(edge->dyndep_, err))
+          return false;
+      }
+    }
+  }
+
   // Load output mtimes so we can compare them to the most recent input below.
   for (vector<Node*>::iterator o = edge->outputs_.begin();
        o != edge->outputs_.end(); ++o) {
@@ -75,12 +100,16 @@
       return false;
   }
 
-  if (!dep_loader_.LoadDeps(edge, err)) {
-    if (!err->empty())
-      return false;
-    // Failed to load dependency info: rebuild to regenerate it.
-    // LoadDeps() did EXPLAIN() already, no need to do it here.
-    dirty = edge->deps_missing_ = true;
+  if (!edge->deps_loaded_) {
+    // This is our first encounter with this edge.  Load discovered deps.
+    edge->deps_loaded_ = true;
+    if (!dep_loader_.LoadDeps(edge, err)) {
+      if (!err->empty())
+        return false;
+      // Failed to load dependency info: rebuild to regenerate it.
+      // LoadDeps() did EXPLAIN() already, no need to do it here.
+      dirty = edge->deps_missing_ = true;
+    }
   }
 
   // Visit all inputs; we're dirty if any of the inputs are dirty.
@@ -272,6 +301,15 @@
   return false;
 }
 
+bool DependencyScan::LoadDyndeps(Node* node, string* err) const {
+  return dyndep_loader_.LoadDyndeps(node, err);
+}
+
+bool DependencyScan::LoadDyndeps(Node* node, DyndepFile* ddf,
+                                 string* err) const {
+  return dyndep_loader_.LoadDyndeps(node, ddf, err);
+}
+
 bool Edge::AllInputsReady() const {
   for (vector<Node*>::const_iterator i = inputs_.begin();
        i != inputs_.end(); ++i) {
@@ -285,19 +323,17 @@
 struct EdgeEnv : public Env {
   enum EscapeKind { kShellEscape, kDoNotEscape };
 
-  EdgeEnv(Edge* edge, EscapeKind escape)
+  EdgeEnv(const Edge* const edge, const EscapeKind escape)
       : edge_(edge), escape_in_out_(escape), recursive_(false) {}
   virtual string LookupVariable(const string& var);
 
   /// Given a span of Nodes, construct a list of paths suitable for a command
   /// line.
-  string MakePathList(vector<Node*>::iterator begin,
-                      vector<Node*>::iterator end,
-                      char sep);
+  std::string MakePathList(const Node* const* span, size_t size, char sep) const;
 
  private:
   vector<string> lookups_;
-  Edge* edge_;
+  const Edge* const edge_;
   EscapeKind escape_in_out_;
   bool recursive_;
 };
@@ -306,14 +342,11 @@
   if (var == "in" || var == "in_newline") {
     int explicit_deps_count = edge_->inputs_.size() - edge_->implicit_deps_ -
       edge_->order_only_deps_;
-    return MakePathList(edge_->inputs_.begin(),
-                        edge_->inputs_.begin() + explicit_deps_count,
+    return MakePathList(&edge_->inputs_[0], explicit_deps_count,
                         var == "in" ? ' ' : '\n');
   } else if (var == "out") {
     int explicit_outs_count = edge_->outputs_.size() - edge_->implicit_outs_;
-    return MakePathList(edge_->outputs_.begin(),
-                        edge_->outputs_.begin() + explicit_outs_count,
-                        ' ');
+    return MakePathList(&edge_->outputs_[0], explicit_outs_count, ' ');
   }
 
   if (recursive_) {
@@ -338,11 +371,10 @@
   return edge_->env_->LookupWithFallback(var, eval, this);
 }
 
-string EdgeEnv::MakePathList(vector<Node*>::iterator begin,
-                             vector<Node*>::iterator end,
-                             char sep) {
+std::string EdgeEnv::MakePathList(const Node* const* const span,
+                                  const size_t size, const char sep) const {
   string result;
-  for (vector<Node*>::iterator i = begin; i != end; ++i) {
+  for (const Node* const* i = span; i != span + size; ++i) {
     if (!result.empty())
       result.push_back(sep);
     const string& path = (*i)->PathDecanonicalized();
@@ -359,7 +391,7 @@
   return result;
 }
 
-string Edge::EvaluateCommand(bool incl_rsp_file) {
+std::string Edge::EvaluateCommand(const bool incl_rsp_file) const {
   string command = GetBinding("command");
   if (incl_rsp_file) {
     string rspfile_content = GetBinding("rspfile_content");
@@ -369,7 +401,7 @@
   return command;
 }
 
-string Edge::GetBinding(const string& key) {
+std::string Edge::GetBinding(const std::string& key) const {
   EdgeEnv env(this, EdgeEnv::kShellEscape);
   return env.LookupVariable(key);
 }
@@ -383,7 +415,12 @@
   return env.LookupVariable("depfile");
 }
 
-string Edge::GetUnescapedRspfile() {
+string Edge::GetUnescapedDyndep() {
+  EdgeEnv env(this, EdgeEnv::kDoNotEscape);
+  return env.LookupVariable("dyndep");
+}
+
+std::string Edge::GetUnescapedRspfile() const {
   EdgeEnv env(this, EdgeEnv::kDoNotEscape);
   return env.LookupVariable("rspfile");
 }
@@ -541,7 +578,7 @@
 bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) {
   // NOTE: deps are only supported for single-target edges.
   Node* output = edge->outputs_[0];
-  DepsLog::Deps* deps = deps_log_->GetDeps(output);
+  DepsLog::Deps* deps = deps_log_ ? deps_log_->GetDeps(output) : NULL;
   if (!deps) {
     EXPLAIN("deps for '%s' are missing", output->path().c_str());
     return false;
diff --git a/src/graph.h b/src/graph.h
index d58fecd..6122837 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -19,6 +19,7 @@
 #include <vector>
 using namespace std;
 
+#include "dyndep.h"
 #include "eval_env.h"
 #include "timestamp.h"
 #include "util.h"
@@ -40,6 +41,7 @@
         slash_bits_(slash_bits),
         mtime_(-1),
         dirty_(false),
+        dyndep_pending_(false),
         in_edge_(NULL),
         id_(-1) {}
 
@@ -87,6 +89,9 @@
   void set_dirty(bool dirty) { dirty_ = dirty; }
   void MarkDirty() { dirty_ = true; }
 
+  bool dyndep_pending() const { return dyndep_pending_; }
+  void set_dyndep_pending(bool pending) { dyndep_pending_ = pending; }
+
   Edge* in_edge() const { return in_edge_; }
   void set_in_edge(Edge* edge) { in_edge_ = edge; }
 
@@ -116,6 +121,10 @@
   /// edges to build.
   bool dirty_;
 
+  /// Store whether dyndep information is expected from this node but
+  /// has not yet been loaded.
+  bool dyndep_pending_;
+
   /// The Edge that produces this Node, or NULL when there is no
   /// known edge to produce it.
   Edge* in_edge_;
@@ -135,9 +144,10 @@
     VisitDone
   };
 
-  Edge() : rule_(NULL), pool_(NULL), env_(NULL), mark_(VisitNone),
-           outputs_ready_(false), deps_missing_(false),
-           implicit_deps_(0), order_only_deps_(0), implicit_outs_(0) {}
+  Edge() : rule_(NULL), pool_(NULL), dyndep_(NULL), env_(NULL),
+           mark_(VisitNone), outputs_ready_(false), deps_loaded_(false),
+           deps_missing_(false), implicit_deps_(0), order_only_deps_(0),
+           implicit_outs_(0) {}
 
   /// Return true if all inputs' in-edges are ready.
   bool AllInputsReady() const;
@@ -145,16 +155,18 @@
   /// Expand all variables in a command and return it as a string.
   /// If incl_rsp_file is enabled, the string will also contain the
   /// full contents of a response file (if applicable)
-  string EvaluateCommand(bool incl_rsp_file = false);
+  std::string EvaluateCommand(bool incl_rsp_file = false) const;
 
   /// Returns the shell-escaped value of |key|.
-  string GetBinding(const string& key);
+  std::string GetBinding(const string& key) const;
   bool GetBindingBool(const string& key);
 
   /// Like GetBinding("depfile"), but without shell escaping.
   string GetUnescapedDepfile();
+  /// Like GetBinding("dyndep"), but without shell escaping.
+  string GetUnescapedDyndep();
   /// Like GetBinding("rspfile"), but without shell escaping.
-  string GetUnescapedRspfile();
+  std::string GetUnescapedRspfile() const;
 
   void Dump(const char* prefix="") const;
 
@@ -162,9 +174,11 @@
   Pool* pool_;
   vector<Node*> inputs_;
   vector<Node*> outputs_;
+  Node* dyndep_;
   BindingEnv* env_;
   VisitMark mark_;
   bool outputs_ready_;
+  bool deps_loaded_;
   bool deps_missing_;
 
   const Rule& rule() const { return *rule_; }
@@ -257,7 +271,8 @@
                  DepfileParserOptions const* depfile_parser_options)
       : build_log_(build_log),
         disk_interface_(disk_interface),
-        dep_loader_(state, deps_log, disk_interface, depfile_parser_options) {}
+        dep_loader_(state, deps_log, disk_interface, depfile_parser_options),
+        dyndep_loader_(state, disk_interface) {}
 
   /// Update the |dirty_| state of the given node by inspecting its input edge.
   /// Examine inputs, outputs, and command lines to judge whether an edge
@@ -282,6 +297,13 @@
     return dep_loader_.deps_log();
   }
 
+  /// Load a dyndep file from the given node's path and update the
+  /// build graph with the new information.  One overload accepts
+  /// a caller-owned 'DyndepFile' object in which to store the
+  /// information loaded from the dyndep file.
+  bool LoadDyndeps(Node* node, string* err) const;
+  bool LoadDyndeps(Node* node, DyndepFile* ddf, string* err) const;
+
  private:
   bool RecomputeDirty(Node* node, vector<Node*>* stack, string* err);
   bool VerifyDAG(Node* node, vector<Node*>* stack, string* err);
@@ -294,6 +316,7 @@
   BuildLog* build_log_;
   DiskInterface* disk_interface_;
   ImplicitDepLoader dep_loader_;
+  DyndepLoader dyndep_loader_;
 };
 
 #endif  // NINJA_GRAPH_H_
diff --git a/src/graph_test.cc b/src/graph_test.cc
index 4a66831..c8cca1c 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -479,3 +479,380 @@
   EXPECT_EQ(root_nodes[3]->PathDecanonicalized(), "out4\\foo");
 }
 #endif
+
+TEST_F(GraphTest, DyndepLoadTrivial) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r in || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+  );
+
+  string err;
+  ASSERT_TRUE(GetNode("dd")->dyndep_pending());
+  EXPECT_TRUE(scan_.LoadDyndeps(GetNode("dd"), &err));
+  EXPECT_EQ("", err);
+  EXPECT_FALSE(GetNode("dd")->dyndep_pending());
+
+  Edge* edge = GetNode("out")->in_edge();
+  ASSERT_EQ(1u, edge->outputs_.size());
+  EXPECT_EQ("out", edge->outputs_[0]->path());
+  ASSERT_EQ(2u, edge->inputs_.size());
+  EXPECT_EQ("in", edge->inputs_[0]->path());
+  EXPECT_EQ("dd", edge->inputs_[1]->path());
+  EXPECT_EQ(0u, edge->implicit_deps_);
+  EXPECT_EQ(1u, edge->order_only_deps_);
+  EXPECT_FALSE(edge->GetBindingBool("restat"));
+}
+
+TEST_F(GraphTest, DyndepLoadMissingFile) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r in || dd\n"
+"  dyndep = dd\n"
+  );
+
+  string err;
+  ASSERT_TRUE(GetNode("dd")->dyndep_pending());
+  EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
+  EXPECT_EQ("loading 'dd': No such file or directory", err);
+}
+
+TEST_F(GraphTest, DyndepLoadMissingEntry) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r in || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+  );
+
+  string err;
+  ASSERT_TRUE(GetNode("dd")->dyndep_pending());
+  EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
+  EXPECT_EQ("'out' not mentioned in its dyndep file 'dd'", err);
+}
+
+TEST_F(GraphTest, DyndepLoadExtraEntry) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r in || dd\n"
+"  dyndep = dd\n"
+"build out2: r in || dd\n"
+  );
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep\n"
+"build out2: dyndep\n"
+  );
+
+  string err;
+  ASSERT_TRUE(GetNode("dd")->dyndep_pending());
+  EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
+  EXPECT_EQ("dyndep file 'dd' mentions output 'out2' whose build statement "
+            "does not have a dyndep binding for the file", err);
+}
+
+TEST_F(GraphTest, DyndepLoadOutputWithMultipleRules1) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out1 | out-twice.imp: r in1\n"
+"build out2: r in2 || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out2 | out-twice.imp: dyndep\n"
+  );
+
+  string err;
+  ASSERT_TRUE(GetNode("dd")->dyndep_pending());
+  EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
+  EXPECT_EQ("multiple rules generate out-twice.imp", err);
+}
+
+TEST_F(GraphTest, DyndepLoadOutputWithMultipleRules2) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out1: r in1 || dd1\n"
+"  dyndep = dd1\n"
+"build out2: r in2 || dd2\n"
+"  dyndep = dd2\n"
+  );
+  fs_.Create("dd1",
+"ninja_dyndep_version = 1\n"
+"build out1 | out-twice.imp: dyndep\n"
+  );
+  fs_.Create("dd2",
+"ninja_dyndep_version = 1\n"
+"build out2 | out-twice.imp: dyndep\n"
+  );
+
+  string err;
+  ASSERT_TRUE(GetNode("dd1")->dyndep_pending());
+  EXPECT_TRUE(scan_.LoadDyndeps(GetNode("dd1"), &err));
+  EXPECT_EQ("", err);
+  ASSERT_TRUE(GetNode("dd2")->dyndep_pending());
+  EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd2"), &err));
+  EXPECT_EQ("multiple rules generate out-twice.imp", err);
+}
+
+TEST_F(GraphTest, DyndepLoadMultiple) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out1: r in1 || dd\n"
+"  dyndep = dd\n"
+"build out2: r in2 || dd\n"
+"  dyndep = dd\n"
+"build outNot: r in3 || dd\n"
+  );
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out1 | out1imp: dyndep | in1imp\n"
+"build out2: dyndep | in2imp\n"
+"  restat = 1\n"
+  );
+
+  string err;
+  ASSERT_TRUE(GetNode("dd")->dyndep_pending());
+  EXPECT_TRUE(scan_.LoadDyndeps(GetNode("dd"), &err));
+  EXPECT_EQ("", err);
+  EXPECT_FALSE(GetNode("dd")->dyndep_pending());
+
+  Edge* edge1 = GetNode("out1")->in_edge();
+  ASSERT_EQ(2u, edge1->outputs_.size());
+  EXPECT_EQ("out1", edge1->outputs_[0]->path());
+  EXPECT_EQ("out1imp", edge1->outputs_[1]->path());
+  EXPECT_EQ(1u, edge1->implicit_outs_);
+  ASSERT_EQ(3u, edge1->inputs_.size());
+  EXPECT_EQ("in1", edge1->inputs_[0]->path());
+  EXPECT_EQ("in1imp", edge1->inputs_[1]->path());
+  EXPECT_EQ("dd", edge1->inputs_[2]->path());
+  EXPECT_EQ(1u, edge1->implicit_deps_);
+  EXPECT_EQ(1u, edge1->order_only_deps_);
+  EXPECT_FALSE(edge1->GetBindingBool("restat"));
+  EXPECT_EQ(edge1, GetNode("out1imp")->in_edge());
+  Node* in1imp = GetNode("in1imp");
+  ASSERT_EQ(1u, in1imp->out_edges().size());
+  EXPECT_EQ(edge1, in1imp->out_edges()[0]);
+
+  Edge* edge2 = GetNode("out2")->in_edge();
+  ASSERT_EQ(1u, edge2->outputs_.size());
+  EXPECT_EQ("out2", edge2->outputs_[0]->path());
+  EXPECT_EQ(0u, edge2->implicit_outs_);
+  ASSERT_EQ(3u, edge2->inputs_.size());
+  EXPECT_EQ("in2", edge2->inputs_[0]->path());
+  EXPECT_EQ("in2imp", edge2->inputs_[1]->path());
+  EXPECT_EQ("dd", edge2->inputs_[2]->path());
+  EXPECT_EQ(1u, edge2->implicit_deps_);
+  EXPECT_EQ(1u, edge2->order_only_deps_);
+  EXPECT_TRUE(edge2->GetBindingBool("restat"));
+  Node* in2imp = GetNode("in2imp");
+  ASSERT_EQ(1u, in2imp->out_edges().size());
+  EXPECT_EQ(edge2, in2imp->out_edges()[0]);
+}
+
+TEST_F(GraphTest, DyndepFileMissing) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r || dd\n"
+"  dyndep = dd\n"
+  );
+
+  string err;
+  EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+  ASSERT_EQ("loading 'dd': No such file or directory", err);
+}
+
+TEST_F(GraphTest, DyndepFileError) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+  );
+
+  string err;
+  EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+  ASSERT_EQ("'out' not mentioned in its dyndep file 'dd'", err);
+}
+
+TEST_F(GraphTest, DyndepImplicitInputNewer) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | in\n"
+  );
+  fs_.Create("out", "");
+  fs_.Tick();
+  fs_.Create("in", "");
+
+  string err;
+  EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+  ASSERT_EQ("", err);
+
+  EXPECT_FALSE(GetNode("in")->dirty());
+  EXPECT_FALSE(GetNode("dd")->dirty());
+
+  // "out" is dirty due to dyndep-specified implicit input
+  EXPECT_TRUE(GetNode("out")->dirty());
+}
+
+TEST_F(GraphTest, DyndepFileReady) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build dd: r dd-in\n"
+"build out: r || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd-in", "");
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | in\n"
+  );
+  fs_.Create("out", "");
+  fs_.Tick();
+  fs_.Create("in", "");
+
+  string err;
+  EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+  ASSERT_EQ("", err);
+
+  EXPECT_FALSE(GetNode("in")->dirty());
+  EXPECT_FALSE(GetNode("dd")->dirty());
+  EXPECT_TRUE(GetNode("dd")->in_edge()->outputs_ready());
+
+  // "out" is dirty due to dyndep-specified implicit input
+  EXPECT_TRUE(GetNode("out")->dirty());
+}
+
+TEST_F(GraphTest, DyndepFileNotClean) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build dd: r dd-in\n"
+"build out: r || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd", "this-should-not-be-loaded");
+  fs_.Tick();
+  fs_.Create("dd-in", "");
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+  ASSERT_EQ("", err);
+
+  EXPECT_TRUE(GetNode("dd")->dirty());
+  EXPECT_FALSE(GetNode("dd")->in_edge()->outputs_ready());
+
+  // "out" is clean but not ready since "dd" is not ready
+  EXPECT_FALSE(GetNode("out")->dirty());
+  EXPECT_FALSE(GetNode("out")->in_edge()->outputs_ready());
+}
+
+TEST_F(GraphTest, DyndepFileNotReady) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build tmp: r\n"
+"build dd: r dd-in || tmp\n"
+"build out: r || dd\n"
+"  dyndep = dd\n"
+  );
+  fs_.Create("dd", "this-should-not-be-loaded");
+  fs_.Create("dd-in", "");
+  fs_.Tick();
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+  ASSERT_EQ("", err);
+
+  EXPECT_FALSE(GetNode("dd")->dirty());
+  EXPECT_FALSE(GetNode("dd")->in_edge()->outputs_ready());
+  EXPECT_FALSE(GetNode("out")->dirty());
+  EXPECT_FALSE(GetNode("out")->in_edge()->outputs_ready());
+}
+
+TEST_F(GraphTest, DyndepFileSecondNotReady) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build dd1: r dd1-in\n"
+"build dd2-in: r || dd1\n"
+"  dyndep = dd1\n"
+"build dd2: r dd2-in\n"
+"build out: r || dd2\n"
+"  dyndep = dd2\n"
+  );
+  fs_.Create("dd1", "");
+  fs_.Create("dd2", "");
+  fs_.Create("dd2-in", "");
+  fs_.Tick();
+  fs_.Create("dd1-in", "");
+  fs_.Create("out", "");
+
+  string err;
+  EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+  ASSERT_EQ("", err);
+
+  EXPECT_TRUE(GetNode("dd1")->dirty());
+  EXPECT_FALSE(GetNode("dd1")->in_edge()->outputs_ready());
+  EXPECT_FALSE(GetNode("dd2")->dirty());
+  EXPECT_FALSE(GetNode("dd2")->in_edge()->outputs_ready());
+  EXPECT_FALSE(GetNode("out")->dirty());
+  EXPECT_FALSE(GetNode("out")->in_edge()->outputs_ready());
+}
+
+TEST_F(GraphTest, DyndepFileCircular) {
+  AssertParse(&state_,
+"rule r\n"
+"  command = unused\n"
+"build out: r in || dd\n"
+"  depfile = out.d\n"
+"  dyndep = dd\n"
+"build in: r circ\n"
+  );
+  fs_.Create("out.d", "out: inimp\n");
+  fs_.Create("dd",
+"ninja_dyndep_version = 1\n"
+"build out | circ: dyndep\n"
+  );
+  fs_.Create("out", "");
+
+  Edge* edge = GetNode("out")->in_edge();
+  string err;
+  EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+  EXPECT_EQ("dependency cycle: circ -> in -> circ", err);
+
+  // Verify that "out.d" was loaded exactly once despite
+  // circular reference discovered from dyndep file.
+  ASSERT_EQ(3u, edge->inputs_.size());
+  EXPECT_EQ("in", edge->inputs_[0]->path());
+  EXPECT_EQ("inimp", edge->inputs_[1]->path());
+  EXPECT_EQ("dd", edge->inputs_[2]->path());
+  EXPECT_EQ(1u, edge->implicit_deps_);
+  EXPECT_EQ(1u, edge->order_only_deps_);
+}
diff --git a/src/graphviz.cc b/src/graphviz.cc
index dce8b32..0d07251 100644
--- a/src/graphviz.cc
+++ b/src/graphviz.cc
@@ -17,6 +17,7 @@
 #include <stdio.h>
 #include <algorithm>
 
+#include "dyndep.h"
 #include "graph.h"
 
 void GraphViz::AddTarget(Node* node) {
@@ -40,6 +41,13 @@
     return;
   visited_edges_.insert(edge);
 
+  if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
+    std::string err;
+    if (!dyndep_loader_.LoadDyndeps(edge->dyndep_, &err)) {
+      Warning("%s\n", err.c_str());
+    }
+  }
+
   if (edge->inputs_.size() == 1 && edge->outputs_.size() == 1) {
     // Can draw simply.
     // Note extra space before label text -- this is cosmetic and feels
diff --git a/src/graphviz.h b/src/graphviz.h
index 408496d..601c9b2 100644
--- a/src/graphviz.h
+++ b/src/graphviz.h
@@ -17,15 +17,22 @@
 
 #include <set>
 
+#include "dyndep.h"
+
+struct DiskInterface;
 struct Node;
 struct Edge;
+struct State;
 
 /// Runs the process of creating GraphViz .dot file output.
 struct GraphViz {
+  GraphViz(State* state, DiskInterface* disk_interface)
+      : dyndep_loader_(state, disk_interface) {}
   void Start();
   void AddTarget(Node* node);
   void Finish();
 
+  DyndepLoader dyndep_loader_;
   std::set<Node*> visited_nodes_;
   std::set<Edge*> visited_edges_;
 };
diff --git a/src/inline.sh b/src/inline.sh
index fa282fa..b64e8ca 100755
--- a/src/inline.sh
+++ b/src/inline.sh
@@ -20,6 +20,6 @@
 
 varname="$1"
 echo "const char $varname[] ="
-od -t x1 -A n -v | sed -e 's|[ \t]||g; s|..|\\x&|g; s|^|"|; s|$|"|'
+od -t x1 -A n -v | sed -e 's|^[\t ]\{0,\}$||g; s|[\t ]\{1,\}| |g; s| \{1,\}$||g; s| |\\x|g; s|^|"|; s|$|"|'
 echo ";"
 
diff --git a/src/line_printer.cc b/src/line_printer.cc
index 1f8eee1..c93173e 100644
--- a/src/line_printer.cc
+++ b/src/line_printer.cc
@@ -31,18 +31,22 @@
 #include "util.h"
 
 LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
-#ifndef _WIN32
   const char* term = getenv("TERM");
+#ifndef _WIN32
   smart_terminal_ = isatty(1) && term && string(term) != "dumb";
 #else
   // Disable output buffer.  It'd be nice to use line buffering but
   // MSDN says: "For some systems, [_IOLBF] provides line
   // buffering. However, for Win32, the behavior is the same as _IOFBF
   // - Full Buffering."
-  setvbuf(stdout, NULL, _IONBF, 0);
-  console_ = GetStdHandle(STD_OUTPUT_HANDLE);
-  CONSOLE_SCREEN_BUFFER_INFO csbi;
-  smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
+  if (term && string(term) == "dumb") {
+    smart_terminal_ = false;
+  } else {
+    setvbuf(stdout, NULL, _IONBF, 0);
+    console_ = GetStdHandle(STD_OUTPUT_HANDLE);
+    CONSOLE_SCREEN_BUFFER_INFO csbi;
+    smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
+  }
 #endif
   supports_color_ = smart_terminal_;
   if (!supports_color_) {
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index 27c423b..2011368 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -18,41 +18,18 @@
 #include <stdlib.h>
 #include <vector>
 
-#include "disk_interface.h"
 #include "graph.h"
-#include "metrics.h"
 #include "state.h"
 #include "util.h"
 #include "version.h"
 
 ManifestParser::ManifestParser(State* state, FileReader* file_reader,
                                ManifestParserOptions options)
-    : state_(state), file_reader_(file_reader),
+    : Parser(state, file_reader),
       options_(options), quiet_(false) {
   env_ = &state->bindings_;
 }
 
-bool ManifestParser::Load(const string& filename, string* err, Lexer* parent) {
-  METRIC_RECORD(".ninja parse");
-  string contents;
-  string read_err;
-  if (file_reader_->ReadFile(filename, &contents, &read_err) != FileReader::Okay) {
-    *err = "loading '" + filename + "': " + read_err;
-    if (parent)
-      parent->Error(string(*err), err);
-    return false;
-  }
-
-  // The lexer needs a nul byte at the end of its input, to know when it's done.
-  // It takes a StringPiece, and StringPiece's string constructor uses
-  // string::data().  data()'s return value isn't guaranteed to be
-  // null-terminated (although in practice - libc++, libstdc++, msvc's stl --
-  // it is, and C++11 demands that too), so add an explicit nul byte.
-  contents.resize(contents.size() + 1);
-
-  return Parse(filename, contents, err);
-}
-
 bool ManifestParser::Parse(const string& filename, const string& input,
                            string* err) {
   lexer_.Start(filename, input);
@@ -410,6 +387,23 @@
                         err);
   }
 
+  // Lookup, validate, and save any dyndep binding.  It will be used later
+  // to load generated dependency information dynamically, but it must
+  // be one of our manifest-specified inputs.
+  string dyndep = edge->GetUnescapedDyndep();
+  if (!dyndep.empty()) {
+    uint64_t slash_bits;
+    if (!CanonicalizePath(&dyndep, &slash_bits, err))
+      return false;
+    edge->dyndep_ = state_->GetNode(dyndep, slash_bits);
+    edge->dyndep_->set_dyndep_pending(true);
+    vector<Node*>::iterator dgi =
+      std::find(edge->inputs_.begin(), edge->inputs_.end(), edge->dyndep_);
+    if (dgi == edge->inputs_.end()) {
+      return lexer_.Error("dyndep '" + dyndep + "' is not an input", err);
+    }
+  }
+
   return true;
 }
 
@@ -434,14 +428,3 @@
 
   return true;
 }
-
-bool ManifestParser::ExpectToken(Lexer::Token expected, string* err) {
-  Lexer::Token token = lexer_.ReadToken();
-  if (token != expected) {
-    string message = string("expected ") + Lexer::TokenName(expected);
-    message += string(", got ") + Lexer::TokenName(token);
-    message += Lexer::TokenErrorHint(expected);
-    return lexer_.Error(message, err);
-  }
-  return true;
-}
diff --git a/src/manifest_parser.h b/src/manifest_parser.h
index 2136018..e14d069 100644
--- a/src/manifest_parser.h
+++ b/src/manifest_parser.h
@@ -15,16 +15,10 @@
 #ifndef NINJA_MANIFEST_PARSER_H_
 #define NINJA_MANIFEST_PARSER_H_
 
-#include <string>
-
-using namespace std;
-
-#include "lexer.h"
+#include "parser.h"
 
 struct BindingEnv;
 struct EvalString;
-struct FileReader;
-struct State;
 
 enum DupeEdgeAction {
   kDupeEdgeActionWarn,
@@ -45,13 +39,10 @@
 };
 
 /// Parses .ninja files.
-struct ManifestParser {
+struct ManifestParser : public Parser {
   ManifestParser(State* state, FileReader* file_reader,
                  ManifestParserOptions options = ManifestParserOptions());
 
-  /// Load and parse a file.
-  bool Load(const string& filename, string* err, Lexer* parent = NULL);
-
   /// Parse a text string of input.  Used by tests.
   bool ParseTest(const string& input, string* err) {
     quiet_ = true;
@@ -72,14 +63,7 @@
   /// Parse either a 'subninja' or 'include' line.
   bool ParseFileInclude(bool new_scope, string* err);
 
-  /// If the next token is not \a expected, produce an error string
-  /// saying "expectd foo, got bar".
-  bool ExpectToken(Lexer::Token expected, string* err);
-
-  State* state_;
   BindingEnv* env_;
-  FileReader* file_reader_;
-  Lexer lexer_;
   ManifestParserOptions options_;
   bool quiet_;
 };
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
index c91d8d1..f2b7467 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -1085,3 +1085,73 @@
       "  description = YAY!\r\n",
       &err));
 }
+
+TEST_F(ParserTest, DyndepNotSpecified) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n"
+"  command = cat $in > $out\n"
+"build result: cat in\n"));
+  Edge* edge = state.GetNode("result", 0)->in_edge();
+  ASSERT_FALSE(edge->dyndep_);
+}
+
+TEST_F(ParserTest, DyndepNotInput) {
+  State lstate;
+  ManifestParser parser(&lstate, NULL);
+  string err;
+  EXPECT_FALSE(parser.ParseTest(
+"rule touch\n"
+"  command = touch $out\n"
+"build result: touch\n"
+"  dyndep = notin\n",
+                               &err));
+  EXPECT_EQ("input:5: dyndep 'notin' is not an input\n", err);
+}
+
+TEST_F(ParserTest, DyndepExplicitInput) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n"
+"  command = cat $in > $out\n"
+"build result: cat in\n"
+"  dyndep = in\n"));
+  Edge* edge = state.GetNode("result", 0)->in_edge();
+  ASSERT_TRUE(edge->dyndep_);
+  EXPECT_TRUE(edge->dyndep_->dyndep_pending());
+  EXPECT_EQ(edge->dyndep_->path(), "in");
+}
+
+TEST_F(ParserTest, DyndepImplicitInput) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n"
+"  command = cat $in > $out\n"
+"build result: cat in | dd\n"
+"  dyndep = dd\n"));
+  Edge* edge = state.GetNode("result", 0)->in_edge();
+  ASSERT_TRUE(edge->dyndep_);
+  EXPECT_TRUE(edge->dyndep_->dyndep_pending());
+  EXPECT_EQ(edge->dyndep_->path(), "dd");
+}
+
+TEST_F(ParserTest, DyndepOrderOnlyInput) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n"
+"  command = cat $in > $out\n"
+"build result: cat in || dd\n"
+"  dyndep = dd\n"));
+  Edge* edge = state.GetNode("result", 0)->in_edge();
+  ASSERT_TRUE(edge->dyndep_);
+  EXPECT_TRUE(edge->dyndep_->dyndep_pending());
+  EXPECT_EQ(edge->dyndep_->path(), "dd");
+}
+
+TEST_F(ParserTest, DyndepRuleInput) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n"
+"  command = cat $in > $out\n"
+"  dyndep = $in\n"
+"build result: cat in\n"));
+  Edge* edge = state.GetNode("result", 0)->in_edge();
+  ASSERT_TRUE(edge->dyndep_);
+  EXPECT_TRUE(edge->dyndep_->dyndep_pending());
+  EXPECT_EQ(edge->dyndep_->path(), "in");
+}
diff --git a/src/ninja.cc b/src/ninja.cc
index b608426..c24f09d 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -126,6 +126,7 @@
   int ToolCompilationDatabase(const Options* options, int argc, char* argv[]);
   int ToolRecompact(const Options* options, int argc, char* argv[]);
   int ToolUrtle(const Options* options, int argc, char** argv);
+  int ToolRules(const Options* options, int argc, char* argv[]);
 
   /// Open the build log.
   /// @return false on error.
@@ -338,7 +339,7 @@
     return 1;
   }
 
-  GraphViz graph;
+  GraphViz graph(&state_, &disk_interface_);
   graph.Start();
   for (vector<Node*>::const_iterator n = nodes.begin(); n != nodes.end(); ++n)
     graph.AddTarget(*n);
@@ -353,6 +354,8 @@
     return 1;
   }
 
+  DyndepLoader dyndep_loader(&state_, &disk_interface_);
+
   for (int i = 0; i < argc; ++i) {
     string err;
     Node* node = CollectTarget(argv[i], &err);
@@ -363,6 +366,11 @@
 
     printf("%s:\n", node->path().c_str());
     if (Edge* edge = node->in_edge()) {
+      if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
+        if (!dyndep_loader.LoadDyndeps(edge->dyndep_, &err)) {
+          Warning("%s\n", err.c_str());
+        }
+      }
       printf("  input: %s\n", edge->rule_->name().c_str());
       for (int in = 0; in < (int)edge->inputs_.size(); in++) {
         const char* label = "";
@@ -554,6 +562,55 @@
   }
 }
 
+int NinjaMain::ToolRules(const Options* options, int argc, char* argv[]) {
+  // Parse options.
+
+  // The rules tool uses getopt, and expects argv[0] to contain the name of
+  // the tool, i.e. "rules".
+  argc++;
+  argv--;
+
+  bool print_description = false;
+
+  optind = 1;
+  int opt;
+  while ((opt = getopt(argc, argv, const_cast<char*>("hd"))) != -1) {
+    switch (opt) {
+    case 'd':
+      print_description = true;
+      break;
+    case 'h':
+    default:
+      printf("usage: ninja -t rules [options]\n"
+             "\n"
+             "options:\n"
+             "  -d     also print the description of the rule\n"
+             "  -h     print this message\n"
+             );
+    return 1;
+    }
+  }
+  argv += optind;
+  argc -= optind;
+
+  // Print rules
+
+  typedef map<string, const Rule*> Rules;
+  const Rules& rules = state_.bindings_.GetRules();
+  for (Rules::const_iterator i = rules.begin(); i != rules.end(); ++i) {
+    printf("%s", i->first.c_str());
+    if (print_description) {
+      const Rule* rule = i->second;
+      const EvalString* description = rule->GetBinding("description");
+      if (description != NULL) {
+        printf(": %s", description->Unparse().c_str());
+      }
+    }
+    printf("\n");
+  }
+  return 0;
+}
+
 enum PrintCommandMode { PCM_Single, PCM_All };
 void PrintCommands(Edge* edge, set<Edge*>* seen, PrintCommandMode mode) {
   if (!edge)
@@ -651,7 +708,7 @@
     return 1;
   }
 
-  Cleaner cleaner(&state_, config_);
+  Cleaner cleaner(&state_, config_, &disk_interface_);
   if (argc >= 1) {
     if (clean_rules)
       return cleaner.CleanRules(argc, argv);
@@ -675,7 +732,8 @@
   ECM_NORMAL,
   ECM_EXPAND_RSPFILE
 };
-string EvaluateCommandWithRspfile(Edge* edge, EvaluateCommandMode mode) {
+std::string EvaluateCommandWithRspfile(const Edge* edge,
+                                       const EvaluateCommandMode mode) {
   string command = edge->EvaluateCommand();
   if (mode == ECM_NORMAL)
     return command;
@@ -699,6 +757,19 @@
   return command;
 }
 
+void printCompdb(const char* const directory, const Edge* const edge,
+                 const EvaluateCommandMode eval_mode) {
+  printf("\n  {\n    \"directory\": \"");
+  EncodeJSONString(directory);
+  printf("\",\n    \"command\": \"");
+  EncodeJSONString(EvaluateCommandWithRspfile(edge, eval_mode).c_str());
+  printf("\",\n    \"file\": \"");
+  EncodeJSONString(edge->inputs_[0]->path().c_str());
+  printf("\",\n    \"output\": \"");
+  EncodeJSONString(edge->outputs_[0]->path().c_str());
+  printf("\"\n  }");
+}
+
 int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
                                        char* argv[]) {
   // The compdb tool uses getopt, and expects argv[0] to contain the name of
@@ -747,22 +818,21 @@
        e != state_.edges_.end(); ++e) {
     if ((*e)->inputs_.empty())
       continue;
-    for (int i = 0; i != argc; ++i) {
-      if ((*e)->rule_->name() == argv[i]) {
-        if (!first)
-          putchar(',');
-
-        printf("\n  {\n    \"directory\": \"");
-        EncodeJSONString(&cwd[0]);
-        printf("\",\n    \"command\": \"");
-        EncodeJSONString(EvaluateCommandWithRspfile(*e, eval_mode).c_str());
-        printf("\",\n    \"file\": \"");
-        EncodeJSONString((*e)->inputs_[0]->path().c_str());
-        printf("\",\n    \"output\": \"");
-        EncodeJSONString((*e)->outputs_[0]->path().c_str());
-        printf("\"\n  }");
-
-        first = false;
+    if (argc == 0) {
+      if (!first) {
+        putchar(',');
+      }
+      printCompdb(&cwd[0], *e, eval_mode);
+      first = false;
+    } else {
+      for (int i = 0; i != argc; ++i) {
+        if ((*e)->rule_->name() == argv[i]) {
+          if (!first) {
+            putchar(',');
+          }
+          printCompdb(&cwd[0], *e, eval_mode);
+          first = false;
+        }
       }
     }
   }
@@ -834,6 +904,8 @@
       Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCompilationDatabase },
     { "recompact",  "recompacts ninja-internal data structures",
       Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRecompact },
+    { "rules",  "list all rules",
+      Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRules },
     { "urtle", NULL,
       Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolUrtle },
     { NULL, NULL, Tool::RUN_AFTER_FLAGS, NULL }
diff --git a/src/parser.cc b/src/parser.cc
new file mode 100644
index 0000000..745c532
--- /dev/null
+++ b/src/parser.cc
@@ -0,0 +1,51 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "parser.h"
+
+#include "disk_interface.h"
+#include "metrics.h"
+
+bool Parser::Load(const string& filename, string* err, Lexer* parent) {
+  METRIC_RECORD(".ninja parse");
+  string contents;
+  string read_err;
+  if (file_reader_->ReadFile(filename, &contents, &read_err) !=
+      FileReader::Okay) {
+    *err = "loading '" + filename + "': " + read_err;
+    if (parent)
+      parent->Error(string(*err), err);
+    return false;
+  }
+
+  // The lexer needs a nul byte at the end of its input, to know when it's done.
+  // It takes a StringPiece, and StringPiece's string constructor uses
+  // string::data().  data()'s return value isn't guaranteed to be
+  // null-terminated (although in practice - libc++, libstdc++, msvc's stl --
+  // it is, and C++11 demands that too), so add an explicit nul byte.
+  contents.resize(contents.size() + 1);
+
+  return Parse(filename, contents, err);
+}
+
+bool Parser::ExpectToken(Lexer::Token expected, string* err) {
+  Lexer::Token token = lexer_.ReadToken();
+  if (token != expected) {
+    string message = string("expected ") + Lexer::TokenName(expected);
+    message += string(", got ") + Lexer::TokenName(token);
+    message += Lexer::TokenErrorHint(expected);
+    return lexer_.Error(message, err);
+  }
+  return true;
+}
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 0000000..e2d2b97
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,50 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NINJA_PARSER_H_
+#define NINJA_PARSER_H_
+
+#include <string>
+
+using namespace std;
+
+#include "lexer.h"
+
+struct FileReader;
+struct State;
+
+/// Base class for parsers.
+struct Parser {
+  Parser(State* state, FileReader* file_reader)
+      : state_(state), file_reader_(file_reader) {}
+
+  /// Load and parse a file.
+  bool Load(const string& filename, string* err, Lexer* parent = NULL);
+
+protected:
+  /// If the next token is not \a expected, produce an error string
+  /// saying "expected foo, got bar".
+  bool ExpectToken(Lexer::Token expected, string* err);
+
+  State* state_;
+  FileReader* file_reader_;
+  Lexer lexer_;
+
+private:
+  /// Parse a file, given its contents as a string.
+  virtual bool Parse(const string& filename, const string& input,
+                     string* err) = 0;
+};
+
+#endif  // NINJA_PARSER_H_
diff --git a/src/state.cc b/src/state.cc
index 9b3c7e1..74cf4c1 100644
--- a/src/state.cc
+++ b/src/state.cc
@@ -186,6 +186,7 @@
     i->second->ResetState();
   for (vector<Edge*>::iterator e = edges_.begin(); e != edges_.end(); ++e) {
     (*e)->outputs_ready_ = false;
+    (*e)->deps_loaded_ = false;
     (*e)->mark_ = Edge::VisitNone;
   }
 }
diff --git a/src/util.cc b/src/util.cc
index 47a5de2..ee810d6 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -485,6 +485,15 @@
   GetNativeSystemInfo(&info);
   return info.dwNumberOfProcessors;
 #else
+#ifdef CPU_COUNT
+  // The number of exposed processors might not represent the actual number of
+  // processors threads can run on. This happens when a CPU set limitation is
+  // active, see https://github.com/ninja-build/ninja/issues/1278
+  cpu_set_t set;
+  if (sched_getaffinity(getpid(), sizeof(set), &set) == 0) {
+    return CPU_COUNT(&set);
+  }
+#endif
   return sysconf(_SC_NPROCESSORS_ONLN);
 #endif
 }
diff --git a/src/version.cc b/src/version.cc
index 1b6cfac..1c906ae 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -18,7 +18,7 @@
 
 #include "util.h"
 
-const char* kNinjaVersion = "1.8.2.git";
+const char* kNinjaVersion = "1.9.0.git";
 
 void ParseVersion(const string& version, int* major, int* minor) {
   size_t end = version.find('.');