version 1.1.0
diff --git a/.gitignore b/.gitignore
index 19a08ee..3cee921 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,8 @@
 /doc/manual.html
 /doc/doxygen
 /gtest-1.6.0
+
+# Eclipse project files
+.project
+.cproject
+
diff --git a/HACKING.md b/HACKING.md
index a777b57..885d2d7 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -159,8 +159,15 @@
 
 ### Via mingw on Linux (not well supported)
 
+Setup on Ubuntu Lucid:
 * `sudo apt-get install gcc-mingw32 wine`
 * `export CC=i586-mingw32msvc-cc CXX=i586-mingw32msvc-c++ AR=i586-mingw32msvc-ar`
+
+Setup on Ubuntu Precise:
+* `sudo apt-get install gcc-mingw-w64-i686 g++-mingw-w64-i686 wine`
+* `export CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ AR=i686-w64-mingw32-ar`
+
+Then run:
 * `./configure.py --platform=mingw --host=linux`
 * Build `ninja.exe` using a Linux ninja binary: `/path/to/linux/ninja`
 * Run: `./ninja.exe`  (implicitly runs through wine(!))
diff --git a/bootstrap.py b/bootstrap.py
index 3032a9b..a847df9 100755
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import print_function
+
 from optparse import OptionParser
 import sys
 import os
@@ -29,6 +31,10 @@
                   help='enable verbose build',)
 parser.add_option('--x64', action='store_true',
                   help='force 64-bit build (Windows)',)
+# TODO: make this --platform to match configure.py.
+parser.add_option('--windows', action='store_true',
+                  help='force native Windows build (when using Cygwin Python)',
+                  default=sys.platform.startswith('win32'))
 (options, conf_args) = parser.parse_args()
 
 def run(*args, **kwargs):
@@ -44,11 +50,12 @@
     cflags.append('-I/usr/local/include')
     ldflags.append('-L/usr/local/lib')
 
-print 'Building ninja manually...'
+print('Building ninja manually...')
 
 try:
     os.mkdir('build')
-except OSError, e:
+except OSError:
+    e = sys.exc_info()[1]
     if e.errno != errno.EEXIST:
         raise
 
@@ -63,7 +70,7 @@
     if filename == 'browse.cc':  # Depends on generated header.
         continue
 
-    if sys.platform.startswith('win32'):
+    if options.windows:
         if src.endswith('-posix.cc'):
             continue
     else:
@@ -72,7 +79,7 @@
 
     sources.append(src)
 
-if sys.platform.startswith('win32'):
+if options.windows:
     sources.append('src/getopt.c')
 
 vcdir = os.environ.get('VCINSTALLDIR')
@@ -87,14 +94,14 @@
     cflags.extend(['-Wno-deprecated',
                    '-DNINJA_PYTHON="' + sys.executable + '"',
                    '-DNINJA_BOOTSTRAP'])
-    if sys.platform.startswith('win32'):
+    if options.windows:
         cflags.append('-D_WIN32_WINNT=0x0501')
     if options.x64:
         cflags.append('-m64')
 args.extend(cflags)
 args.extend(ldflags)
 binary = 'ninja.bootstrap'
-if sys.platform.startswith('win32'):
+if options.windows:
     binary = 'ninja.bootstrap.exe'
 args.extend(sources)
 if vcdir:
@@ -103,16 +110,20 @@
     args.extend(['-o', binary])
 
 if options.verbose:
-    print ' '.join(args)
+    print(' '.join(args))
 
-run(args)
+try:
+    run(args)
+except:
+    print('Failure running:', args)
+    raise
 
 verbose = []
 if options.verbose:
     verbose = ['-v']
 
-if sys.platform.startswith('win32'):
-    print 'Building ninja using itself...'
+if options.windows:
+    print('Building ninja using itself...')
     run([sys.executable, 'configure.py', '--with-ninja=%s' % binary] +
         conf_args)
     run(['./' + binary] + verbose)
@@ -124,17 +135,17 @@
     for obj in glob.glob('*.obj'):
         os.unlink(obj)
 
-    print """
+    print("""
 Done!
 
 Note: to work around Windows file locking, where you can't rebuild an
 in-use binary, to run ninja after making any changes to build ninja itself
 you should run ninja.bootstrap instead.  Your build is also configured to
 use ninja.bootstrap.exe as the MSVC helper; see the --with-ninja flag of
-the --help output of configure.py."""
+the --help output of configure.py.""")
 else:
-    print 'Building ninja using itself...'
+    print('Building ninja using itself...')
     run([sys.executable, 'configure.py'] + conf_args)
     run(['./' + binary] + verbose)
     os.unlink(binary)
-    print 'Done!'
+    print('Done!')
diff --git a/configure.py b/configure.py
index 98274e6..9391a68 100755
--- a/configure.py
+++ b/configure.py
@@ -19,6 +19,8 @@
 Projects that use ninja themselves should either write a similar script
 or use a meta-build system that supports Ninja output."""
 
+from __future__ import print_function
+
 from optparse import OptionParser
 import os
 import sys
@@ -50,7 +52,7 @@
                   default="ninja")
 (options, args) = parser.parse_args()
 if args:
-    print 'ERROR: extra unparsed command-line arguments:', args
+    print('ERROR: extra unparsed command-line arguments:', args)
     sys.exit(1)
 
 platform = options.platform
@@ -140,6 +142,7 @@
               '-fno-rtti',
               '-fno-exceptions',
               '-fvisibility=hidden', '-pipe',
+              '-Wno-missing-field-initializers',
               '-DNINJA_PYTHON="%s"' % options.with_python]
     if options.debug:
         cflags += ['-D_GLIBCXX_DEBUG', '-D_GLIBCXX_DEBUG_PEDANTIC']
@@ -168,7 +171,9 @@
         libs.append('-lprofiler')
 
 def shell_escape(str):
-    """Escape str such that it's interpreted as a single argument by the shell."""
+    """Escape str such that it's interpreted as a single argument by
+    the shell."""
+
     # This isn't complete, but it's just enough to make NINJA_PYTHON work.
     if platform in ('windows', 'mingw'):
       return str
@@ -255,7 +260,7 @@
     n.build(src('depfile_parser.cc'), 're2c', src('depfile_parser.in.cc'))
     n.build(src('lexer.cc'), 're2c', src('lexer.in.cc'))
 else:
-    print ("warning: A compatible version of re2c (>= 0.11.3) was not found; "
+    print("warning: A compatible version of re2c (>= 0.11.3) was not found; "
            "changes to src/*.in.cc will not affect your build.")
 n.newline()
 
@@ -277,11 +282,12 @@
              'util']:
     objs += cxx(name)
 if platform in ('mingw', 'windows'):
-    objs += cxx('subprocess-win32')
+    for name in ['subprocess-win32',
+                 'includes_normalize-win32',
+                 'msvc_helper-win32',
+                 'msvc_helper_main-win32']:
+        objs += cxx(name)
     if platform == 'windows':
-        objs += cxx('includes_normalize-win32')
-        objs += cxx('msvc_helper-win32')
-        objs += cxx('msvc_helper_main-win32')
         objs += cxx('minidump-win32')
     objs += cc('getopt')
 else:
@@ -309,7 +315,7 @@
 n.comment('Tests all build into ninja_test executable.')
 
 variables = []
-test_cflags = None
+test_cflags = cflags[:]
 test_ldflags = None
 test_libs = libs
 objs = []
@@ -328,13 +334,17 @@
                     os.path.join(path, 'src', 'gtest_main.cc'),
                     variables=[('cflags', gtest_cflags)])
 
-    test_cflags = cflags + ['-DGTEST_HAS_RTTI=0',
-                            '-I%s' % os.path.join(path, 'include')]
+    test_cflags.append('-I%s' % os.path.join(path, 'include'))
 elif platform == 'windows':
     test_libs.extend(['gtest_main.lib', 'gtest.lib'])
 else:
+    test_cflags.append('-DGTEST_HAS_RTTI=0')
     test_libs.extend(['-lgtest_main', '-lgtest'])
 
+if test_cflags == cflags:
+    test_cflags = None
+
+n.variable('test_cflags', test_cflags)
 for name in ['build_log_test',
              'build_test',
              'clean_test',
@@ -348,8 +358,8 @@
              'subprocess_test',
              'test',
              'util_test']:
-    objs += cxx(name, variables=[('cflags', test_cflags)])
-if platform == 'windows':
+    objs += cxx(name, variables=[('cflags', '$test_cflags')])
+if platform in ('windows', 'mingw'):
     for name in ['includes_normalize_test', 'msvc_helper_test']:
         objs += cxx(name, variables=[('cflags', test_cflags)])
 
@@ -362,7 +372,7 @@
 all_targets += ninja_test
 
 
-n.comment('Ancilliary executables.')
+n.comment('Ancillary executables.')
 objs = cxx('parser_perftest')
 all_targets += n.build(binary('parser_perftest'), 'link', objs,
                        implicit=ninja_lib, variables=[('libs', libs)])
@@ -427,23 +437,11 @@
 if host == 'linux':
     n.comment('Packaging')
     n.rule('rpmbuild',
-           command="rpmbuild \
-           --define 'ver git' \
-           --define \"rel `git rev-parse --short HEAD`\" \
-           --define '_topdir %(pwd)/rpm-build' \
-           --define '_builddir %{_topdir}' \
-           --define '_rpmdir %{_topdir}' \
-           --define '_srcrpmdir %{_topdir}' \
-           --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \
-           --define '_specdir %{_topdir}' \
-           --define '_sourcedir  %{_topdir}' \
-           --quiet \
-           -bb misc/packaging/ninja.spec",
-           description='Building RPM..')
-    n.build('rpm', 'rpmbuild',
-            implicit=['ninja','README', 'COPYING', doc('manual.html')])
+           command="misc/packaging/rpmbuild.sh",
+           description='Building rpms..')
+    n.build('rpm', 'rpmbuild')
     n.newline()
 
 n.build('all', 'phony', all_targets)
 
-print 'wrote %s.' % BUILD_FILENAME
+print('wrote %s.' % BUILD_FILENAME)
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index 03d27df..42e5452 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -215,6 +215,7 @@
 Several placeholders are available:
 * `%s`: The number of started edges.
 * `%t`: The total number of edges that must be run to complete the build.
+* `%p`: The percentage of started edges.
 * `%r`: The number of currently running edges.
 * `%u`: The number of remaining edges to start.
 * `%f`: The number of finished edges.
@@ -420,6 +421,59 @@
 statement and it is out of date, Ninja will rebuild and reload it
 before building the targets requested by the user.
 
+Pools
+~~~~~
+
+Pools allow you to allocate one or more rules or edges a finite number
+of concurrent jobs which is more tightly restricted than the default
+parallelism.
+
+This can be useful, for example, to restrict a particular expensive rule
+(like link steps for huge executables), or to restrict particular build
+statements which you know perform poorly when run concurrently.
+
+Each pool has a `depth` variable which is specified in the build file.
+The pool is then referred to with the `pool` variable on either a rule
+or a build statement.
+
+No matter what pools you specify, ninja will never run more concurrent jobs
+than the default parallelism, or the number of jobs specified on the command
+line (with -j).
+
+----------------
+# No more than 4 links at a time.
+pool link_pool
+  depth = 4
+
+# No more than 1 heavy object at a time.
+pool heavy_object_pool
+  depth = 1
+
+rule link
+  ...
+  pool = link_pool
+
+rule cc
+  ...
+
+# The link_pool is used here. Only 4 links will run concurrently.
+build foo.exe: link input.obj
+
+# A build statement can be exempted from its rule's pool by setting an
+# empty pool. This effectively puts the build statement back into the default
+# pool, which has infinite depth.
+build other.exe: link input.obj
+  pool =
+
+# A build statement can specify a pool directly.
+# Only one of these builds will run at a time.
+build heavy_object1.obj: cc heavy_obj1.cc
+  pool = heavy_object_pool
+build heavy_object2.obj: cc heavy_obj2.cc
+  pool = heavy_object_pool
+
+----------------
+
 
 Generating Ninja files from code
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/misc/bash-completion b/misc/bash-completion
index ac4d051..b40136e 100644
--- a/misc/bash-completion
+++ b/misc/bash-completion
@@ -16,9 +16,17 @@
 #   . path/to/ninja/misc/bash-completion
 
 _ninja_target() {
-    local cur targets
+    local cur targets dir line targets_command OPTIND
     cur="${COMP_WORDS[COMP_CWORD]}"
-    targets=$((ninja -t targets all 2>/dev/null) | awk -F: '{print $1}')
+    dir="."
+    line=$(echo ${COMP_LINE} | cut -d" " -f 2-)
+    while getopts C: opt "${line[@]}"; do
+      case $opt in
+        C) dir="$OPTARG" ;;
+      esac
+    done;
+    targets_command="ninja -C ${dir} -t targets all"
+    targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}')
     COMPREPLY=($(compgen -W "$targets" -- "$cur"))
     return 0
 }
diff --git a/misc/ninja-mode.el b/misc/ninja-mode.el
index 44fc82b..d939206 100644
--- a/misc/ninja-mode.el
+++ b/misc/ninja-mode.el
@@ -18,7 +18,8 @@
 (setq ninja-keywords
       (list
        '("^#.*" . font-lock-comment-face)
-       (cons (concat "^" (regexp-opt '("rule" "build" "subninja" "include")
+       (cons (concat "^" (regexp-opt '("rule" "build" "subninja" "include"
+                                       "pool" "default")
                                      'words))
              font-lock-keyword-face)
        '("\\([[:alnum:]_]+\\) =" . (1 font-lock-variable-name-face))
diff --git a/misc/ninja.vim b/misc/ninja.vim
index 6f0e48d..841902f 100644
--- a/misc/ninja.vim
+++ b/misc/ninja.vim
@@ -1,8 +1,8 @@
 " ninja build file syntax.
 " Language: ninja build file as described at
 "           http://martine.github.com/ninja/manual.html
-" Version: 1.2
-" Last Change: 2012/06/01
+" Version: 1.3
+" Last Change: 2012/12/14
 " Maintainer: Nicolas Weber <nicolasweber@gmx.de>
 " Version 1.2 of this script is in the upstream vim repository and will be
 " included in the next vim release. If you change this, please send your change
@@ -25,6 +25,7 @@
 " lexer.in.cc, ReadToken() and manifest_parser.cc, Parse()
 syn match ninjaKeyword "^build\>"
 syn match ninjaKeyword "^rule\>"
+syn match ninjaKeyword "^pool\>"
 syn match ninjaKeyword "^default\>"
 syn match ninjaKeyword "^include\>"
 syn match ninjaKeyword "^subninja\>"
@@ -35,7 +36,11 @@
 " let assignments.
 " manifest_parser.cc, ParseRule()
 syn region ninjaRule start="^rule" end="^\ze\S" contains=ALL transparent
-syn keyword ninjaRuleCommand contained command depfile description generator restat
+syn keyword ninjaRuleCommand contained command depfile description generator
+                                     \ pool restat rspfile rspfile_content
+
+syn region ninjaPool start="^pool" end="^\ze\S" contains=ALL transparent
+syn keyword ninjaPoolCommand contained depth
 
 " Strings are parsed as follows:
 " lexer.in.cc, ReadEvalString()
@@ -61,6 +66,7 @@
 hi def link ninjaComment Comment
 hi def link ninjaKeyword Keyword
 hi def link ninjaRuleCommand Statement
+hi def link ninjaPoolCommand Statement
 hi def link ninjaWrapLineOperator ninjaOperator
 hi def link ninjaOperator Operator
 hi def link ninjaSimpleVar ninjaVar
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index 66babbe..ece7eb5 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -32,8 +32,13 @@
             value = ' '.join(filter(None, value))  # Filter out empty strings.
         self._line('%s = %s' % (key, value), indent)
 
+    def pool(self, name, depth):
+        self._line('pool %s' % name)
+        self.variable('depth', depth, indent=1)
+
     def rule(self, name, command, description=None, depfile=None,
-             generator=False, restat=False, rspfile=None, rspfile_content=None):
+             generator=False, pool=None, restat=False, rspfile=None,
+             rspfile_content=None):
         self._line('rule %s' % name)
         self.variable('command', command, indent=1)
         if description:
@@ -42,6 +47,8 @@
             self.variable('depfile', depfile, indent=1)
         if generator:
             self.variable('generator', '1', indent=1)
+        if pool:
+            self.variable('pool', pool, indent=1)
         if restat:
             self.variable('restat', '1', indent=1)
         if rspfile:
@@ -65,13 +72,12 @@
             all_inputs.append('||')
             all_inputs.extend(order_only)
 
-        self._line('build %s: %s %s' % (' '.join(out_outputs),
-                                        rule,
-                                        ' '.join(all_inputs)))
+        self._line('build %s: %s' % (' '.join(out_outputs),
+                                        ' '.join([rule] + all_inputs)))
 
         if variables:
             if isinstance(variables, dict):
-                iterator = variables.iteritems()
+                iterator = iter(variables.items())
             else:
                 iterator = iter(variables)
 
diff --git a/misc/ninja_test.py b/misc/ninja_test.py
index b56033e..2aef7ff 100755
--- a/misc/ninja_test.py
+++ b/misc/ninja_test.py
@@ -15,7 +15,11 @@
 # limitations under the License.
 
 import unittest
-from StringIO import StringIO
+
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
 
 import ninja_syntax
 
diff --git a/misc/packaging/ninja.spec b/misc/packaging/ninja.spec
index d513c6d..2f009f6 100644
--- a/misc/packaging/ninja.spec
+++ b/misc/packaging/ninja.spec
@@ -5,6 +5,8 @@
 Group: Development/Tools
 License: Apache 2.0
 URL: https://github.com/martine/ninja
+Source0: %{name}-%{version}-%{release}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}
 
 %description
 Ninja is yet another build system. It takes as input the interdependencies of files (typically source code and output executables) and
@@ -14,20 +16,25 @@
 which has over 30,000 source files and whose other build systems (including one built from custom non-recursive Makefiles) can take ten
 seconds to start building after changing one file. Ninja is under a second.
 
+%prep
+%setup -q -n %{name}-%{version}-%{release}
+
 %build
-# Assuming we've bootstrapped already..
-../ninja manual ninja -C ..
+echo Building..
+./bootstrap.py
+./ninja manual
 
 %install
 mkdir -p %{buildroot}%{_bindir} %{buildroot}%{_docdir}
-cp -p ../ninja %{buildroot}%{_bindir}/
-git log --oneline --pretty=format:'%h: %s (%an, %cd)' --abbrev-commit --all > GITLOG
+cp -p ninja %{buildroot}%{_bindir}/
 
 %files
 %defattr(-, root, root)
-%doc GITLOG ../COPYING ../README ../doc/manual.html
+%doc COPYING README doc/manual.html
 %{_bindir}/*
 
 %clean
-mv %{_topdir}/*.rpm ..
-rm -rf %{_topdir}
+rm -rf %{buildroot}
+
+#The changelog is built automatically from Git history
+%changelog
diff --git a/misc/packaging/rpmbuild.sh b/misc/packaging/rpmbuild.sh
new file mode 100755
index 0000000..9b74c65
--- /dev/null
+++ b/misc/packaging/rpmbuild.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+echo Building ninja RPMs..
+GITROOT=$(git rev-parse --show-toplevel)
+cd $GITROOT
+
+VER=1.0
+REL=$(git rev-parse --short HEAD)git
+RPMTOPDIR=$GITROOT/rpm-build
+echo "Ver: $VER, Release: $REL"
+
+# Create tarball
+mkdir -p $RPMTOPDIR/{SOURCES,SPECS}
+git archive --format=tar --prefix=ninja-${VER}-${REL}/ HEAD | gzip -c > $RPMTOPDIR/SOURCES/ninja-${VER}-${REL}.tar.gz
+
+# Convert git log to RPM's ChangeLog format (shown with rpm -qp --changelog <rpm file>)
+sed -e "s/%{ver}/$VER/" -e "s/%{rel}/$REL/" misc/packaging/ninja.spec > $RPMTOPDIR/SPECS/ninja.spec
+git log --format="* %cd %aN%n- (%h) %s%d%n" --date=local | sed -r 's/[0-9]+:[0-9]+:[0-9]+ //' >> $RPMTOPDIR/SPECS/ninja.spec
+
+# Build SRC and binary RPMs
+rpmbuild    --quiet                       \
+            --define "_topdir $RPMTOPDIR" \
+            --define "_rpmdir $PWD"       \
+            --define "_srcrpmdir $PWD"    \
+            --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \
+            -ba $RPMTOPDIR/SPECS/ninja.spec &&
+
+rm -rf $RPMTOPDIR &&
+echo Done
diff --git a/src/browse.py b/src/browse.py
index 17e67cf..7f15e50 100755
--- a/src/browse.py
+++ b/src/browse.py
@@ -20,7 +20,12 @@
 it when needed.
 """
 
-import BaseHTTPServer
+from __future__ import print_function
+
+try:
+    import http.server as httpserver
+except ImportError:
+    import BaseHTTPServer as httpserver
 import subprocess
 import sys
 import webbrowser
@@ -55,12 +60,12 @@
     outputs = []
 
     try:
-        target = lines.next()[:-1]  # strip trailing colon
+        target = next(lines)[:-1]  # strip trailing colon
 
-        line = lines.next()
+        line = next(lines)
         (match, rule) = match_strip(line, '  input: ')
         if match:
-            (match, line) = match_strip(lines.next(), '    ')
+            (match, line) = match_strip(next(lines), '    ')
             while match:
                 type = None
                 (match, line) = match_strip(line, '| ')
@@ -70,21 +75,21 @@
                 if match:
                     type = 'order-only'
                 inputs.append((line, type))
-                (match, line) = match_strip(lines.next(), '    ')
+                (match, line) = match_strip(next(lines), '    ')
 
         match, _ = match_strip(line, '  outputs:')
         if match:
-            (match, line) = match_strip(lines.next(), '    ')
+            (match, line) = match_strip(next(lines), '    ')
             while match:
                 outputs.append(line)
-                (match, line) = match_strip(lines.next(), '    ')
+                (match, line) = match_strip(next(lines), '    ')
     except StopIteration:
         pass
 
     return Node(inputs, rule, target, outputs)
 
-def generate_html(node):
-    print '''<!DOCTYPE html>
+def create_page(body):
+    return '''<!DOCTYPE html>
 <style>
 body {
     font-family: sans;
@@ -108,34 +113,42 @@
 .filelist {
   -webkit-columns: auto 2;
 }
-</style>'''
+</style>
+''' + body
 
-    print '<h1><tt>%s</tt></h1>' % node.target
+def generate_html(node):
+    document = ['<h1><tt>%s</tt></h1>' % node.target]
 
     if node.inputs:
-        print '<h2>target is built using rule <tt>%s</tt> of</h2>' % node.rule
+        document.append('<h2>target is built using rule <tt>%s</tt> of</h2>' %
+                        node.rule)
         if len(node.inputs) > 0:
-            print '<div class=filelist>'
+            document.append('<div class=filelist>')
             for input, type in sorted(node.inputs):
                 extra = ''
                 if type:
                     extra = ' (%s)' % type
-                print '<tt><a href="?%s">%s</a>%s</tt><br>' % (input, input, extra)
-            print '</div>'
+                document.append('<tt><a href="?%s">%s</a>%s</tt><br>' %
+                                (input, input, extra))
+            document.append('</div>')
 
     if node.outputs:
-        print '<h2>dependent edges build:</h2>'
-        print '<div class=filelist>'
+        document.append('<h2>dependent edges build:</h2>')
+        document.append('<div class=filelist>')
         for output in sorted(node.outputs):
-            print '<tt><a href="?%s">%s</a></tt><br>' % (output, output)
-        print '</div>'
+            document.append('<tt><a href="?%s">%s</a></tt><br>' %
+                            (output, output))
+        document.append('</div>')
+
+    return '\n'.join(document)
 
 def ninja_dump(target):
     proc = subprocess.Popen([sys.argv[1], '-t', 'query', target],
-                            stdout=subprocess.PIPE)
-    return proc.communicate()[0]
+                            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                            universal_newlines=True)
+    return proc.communicate() + (proc.returncode,)
 
-class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+class RequestHandler(httpserver.BaseHTTPRequestHandler):
     def do_GET(self):
         assert self.path[0] == '/'
         target = self.path[1:]
@@ -152,28 +165,28 @@
             return
         target = target[1:]
 
-        input = ninja_dump(target)
+        ninja_output, ninja_error, exit_code = ninja_dump(target)
+        if exit_code == 0:
+            page_body = generate_html(parse(ninja_output.strip()))
+        else:
+            # Relay ninja's error message.
+            page_body = '<h1><tt>%s</tt></h1>' % ninja_error
 
         self.send_response(200)
         self.end_headers()
-        stdout = sys.stdout
-        sys.stdout = self.wfile
-        try:
-            generate_html(parse(input.strip()))
-        finally:
-            sys.stdout = stdout
+        self.wfile.write(create_page(page_body).encode('utf-8'))
 
     def log_message(self, format, *args):
         pass  # Swallow console spam.
 
 port = 8000
-httpd = BaseHTTPServer.HTTPServer(('',port), RequestHandler)
+httpd = httpserver.HTTPServer(('',port), RequestHandler)
 try:
-    print 'Web server running on port %d, ctl-C to abort...' % port
+    print('Web server running on port %d, ctl-C to abort...' % port)
     webbrowser.open_new('http://localhost:%s' % port)
     httpd.serve_forever()
 except KeyboardInterrupt:
-    print
+    print()
     pass  # Swallow console spam.
 
 
diff --git a/src/build.cc b/src/build.cc
index e1aaad1..b4229c4 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -17,6 +17,7 @@
 #include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <functional>
 
 #ifdef _WIN32
 #include <windows.h>
@@ -37,13 +38,50 @@
 #include "subprocess.h"
 #include "util.h"
 
+namespace {
+
+/// A CommandRunner that doesn't actually run the commands.
+struct DryRunCommandRunner : public CommandRunner {
+  virtual ~DryRunCommandRunner() {}
+
+  // Overridden from CommandRunner:
+  virtual bool CanRunMore();
+  virtual bool StartCommand(Edge* edge);
+  virtual Edge* WaitForCommand(ExitStatus* status, string* /* output */);
+
+ private:
+  queue<Edge*> finished_;
+};
+
+bool DryRunCommandRunner::CanRunMore() {
+  return true;
+}
+
+bool DryRunCommandRunner::StartCommand(Edge* edge) {
+  finished_.push(edge);
+  return true;
+}
+
+Edge* DryRunCommandRunner::WaitForCommand(ExitStatus* status,
+                                          string* /*output*/) {
+   if (finished_.empty()) {
+     *status = ExitFailure;
+     return NULL;
+   }
+   *status = ExitSuccess;
+   Edge* edge = finished_.front();
+   finished_.pop();
+   return edge;
+}
+
+}  // namespace
+
 BuildStatus::BuildStatus(const BuildConfig& config)
     : config_(config),
       start_time_millis_(GetTimeMillis()),
       started_edges_(0), finished_edges_(0), total_edges_(0),
       have_blank_line_(true), progress_status_format_(NULL),
-      overall_rate_(), current_rate_(),
-      current_rate_average_count_(config.parallelism) {
+      overall_rate_(), current_rate_(config.parallelism) {
 #ifndef _WIN32
   const char* term = getenv("TERM");
   smart_terminal_ = isatty(1) && term && string(term) != "dumb";
@@ -136,9 +174,11 @@
     printf("\n");
 }
 
-string BuildStatus::FormatProgressStatus(const char* progress_status_format) const {
+string BuildStatus::FormatProgressStatus(
+    const char* progress_status_format) const {
   string out;
   char buf[32];
+  int percent;
   for (const char* s = progress_status_format; *s != '\0'; ++s) {
     if (*s == '%') {
       ++s;
@@ -177,30 +217,31 @@
         out += buf;
         break;
 
-      // Overall finished edges per second.
+        // Overall finished edges per second.
       case 'o':
-        overall_rate_.UpdateRate(finished_edges_, finished_edges_);
-        overall_rate_.snprinfRate(buf, "%.1f");
+        overall_rate_.UpdateRate(finished_edges_);
+        snprinfRate(overall_rate_.rate(), buf, "%.1f");
         out += buf;
         break;
 
-      // Current rate, average over the last '-j' jobs.
+        // Current rate, average over the last '-j' jobs.
       case 'c':
-        // TODO use sliding window?
-        if (finished_edges_ > current_rate_.last_update() &&
-            finished_edges_ - current_rate_.last_update() == current_rate_average_count_) {
-          current_rate_.UpdateRate(current_rate_average_count_, finished_edges_);
-          current_rate_.Restart();
-        }
-        current_rate_.snprinfRate(buf, "%.0f");
+        current_rate_.UpdateRate(finished_edges_);
+        snprinfRate(current_rate_.rate(), buf, "%.1f");
         out += buf;
         break;
 
-      default: {
+        // Percentage
+      case 'p':
+        percent = (100 * started_edges_) / total_edges_;
+        snprintf(buf, sizeof(buf), "%3i%%", percent);
+        out += buf;
+        break;
+
+      default:
         Fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *s);
         return "";
       }
-      }
     } else {
       out.push_back(*s);
     }
@@ -325,7 +366,7 @@
     want = true;
     ++wanted_edges_;
     if (edge->AllInputsReady())
-      ready_.insert(edge);
+      ScheduleWork(edge);
     if (!edge->is_phony())
       ++command_edges_;
   }
@@ -374,6 +415,22 @@
   return edge;
 }
 
+void Plan::ScheduleWork(Edge* edge) {
+  Pool* pool = edge->pool();
+  if (pool->ShouldDelayEdge()) {
+    pool->DelayEdge(edge);
+    pool->RetrieveReadyEdges(&ready_);
+  } else {
+    pool->EdgeScheduled(*edge);
+    ready_.insert(edge);
+  }
+}
+
+void Plan::ResumeDelayedJobs(Edge* edge) {
+  edge->pool()->EdgeFinished(*edge);
+  edge->pool()->RetrieveReadyEdges(&ready_);
+}
+
 void Plan::EdgeFinished(Edge* edge) {
   map<Edge*, bool>::iterator i = want_.find(edge);
   assert(i != want_.end());
@@ -382,6 +439,9 @@
   want_.erase(i);
   edge->outputs_ready_ = true;
 
+  // See if this job frees up any delayed jobs
+  ResumeDelayedJobs(edge);
+
   // Check off any nodes we were waiting for with this edge.
   for (vector<Node*>::iterator i = edge->outputs_.begin();
        i != edge->outputs_.end(); ++i) {
@@ -400,7 +460,7 @@
     // See if the edge is now ready.
     if ((*i)->AllInputsReady()) {
       if (want_i->second) {
-        ready_.insert(*i);
+        ScheduleWork(*i);
       } else {
         // We do not need to build this edge, but we might need to build one of
         // its dependents.
@@ -422,8 +482,9 @@
 
     // If all non-order-only inputs for this edge are now clean,
     // we might have changed the dirty state of the outputs.
-    vector<Node*>::iterator begin = (*ei)->inputs_.begin(),
-                            end = (*ei)->inputs_.end() - (*ei)->order_only_deps_;
+    vector<Node*>::iterator
+        begin = (*ei)->inputs_.begin(),
+        end = (*ei)->inputs_.end() - (*ei)->order_only_deps_;
     if (find_if(begin, end, mem_fun(&Node::dirty)) == end) {
       // Recompute most_recent_input and command.
       Node* most_recent_input = NULL;
@@ -533,30 +594,6 @@
   return edge;
 }
 
-/// A CommandRunner that doesn't actually run the commands.
-struct DryRunCommandRunner : public CommandRunner {
-  virtual ~DryRunCommandRunner() {}
-  virtual bool CanRunMore() {
-    return true;
-  }
-  virtual bool StartCommand(Edge* edge) {
-    finished_.push(edge);
-    return true;
-  }
-  virtual Edge* WaitForCommand(ExitStatus* status, string* /* output */) {
-    if (finished_.empty()) {
-      *status = ExitFailure;
-      return NULL;
-    }
-    *status = ExitSuccess;
-    Edge* edge = finished_.front();
-    finished_.pop();
-    return edge;
-  }
-
-  queue<Edge*> finished_;
-};
-
 Builder::Builder(State* state, const BuildConfig& config,
                  BuildLog* log, DiskInterface* disk_interface)
     : state_(state), config_(config), disk_interface_(disk_interface),
@@ -718,6 +755,7 @@
 }
 
 bool Builder::StartEdge(Edge* edge, string* err) {
+  METRIC_RECORD("StartEdge");
   if (edge->is_phony())
     return true;
 
@@ -734,8 +772,10 @@
   // Create response file, if needed
   // XXX: this may also block; do we care?
   if (edge->HasRspFile()) {
-    if (!disk_interface_->WriteFile(edge->GetRspFile(), edge->GetRspFileContent()))
+    if (!disk_interface_->WriteFile(edge->GetRspFile(),
+                                    edge->GetRspFileContent())) {
       return false;
+    }
   }
 
   // start command computing and run it
@@ -748,6 +788,7 @@
 }
 
 void Builder::FinishEdge(Edge* edge, bool success, const string& output) {
+  METRIC_RECORD("FinishEdge");
   TimeStamp restat_mtime = 0;
 
   if (success) {
@@ -777,7 +818,8 @@
         }
 
         if (restat_mtime != 0 && !edge->rule().depfile().empty()) {
-          TimeStamp depfile_mtime = disk_interface_->Stat(edge->EvaluateDepFile());
+          TimeStamp depfile_mtime =
+              disk_interface_->Stat(edge->EvaluateDepFile());
           if (depfile_mtime > restat_mtime)
             restat_mtime = depfile_mtime;
         }
diff --git a/src/build.h b/src/build.h
index 3e7a144..23f653e 100644
--- a/src/build.h
+++ b/src/build.h
@@ -15,13 +15,13 @@
 #ifndef NINJA_BUILD_H_
 #define NINJA_BUILD_H_
 
+#include <cstdio>
 #include <map>
+#include <memory>
+#include <queue>
 #include <set>
 #include <string>
-#include <queue>
 #include <vector>
-#include <memory>
-#include <cstdio>
 
 #include "graph.h"  // XXX needed for DependencyScan; should rearrange.
 #include "exit_status.h"
@@ -70,6 +70,16 @@
   bool CheckDependencyCycle(Node* node, vector<Node*>* stack, string* err);
   void NodeFinished(Node* node);
 
+  /// 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.
+  void ScheduleWork(Edge* edge);
+
+  /// Allows jobs blocking on |edge| to potentially resume.
+  /// For example, if |edge| is a member of a pool, calling this may schedule
+  /// previously pending jobs in that pool.
+  void ResumeDelayedJobs(Edge* edge);
+
   /// Keep track of which edges we want to build in this plan.  If this map does
   /// not contain an entry for an edge, we do not want to build the entry or its
   /// dependents.  If an entry maps to false, we do not want to build it, but we
@@ -175,7 +185,7 @@
   /// Format the progress status string by replacing the placeholders.
   /// See the user manual for more information about the available
   /// placeholders.
-  /// @param progress_status_format_ The format of the progress status.
+  /// @param progress_status_format The format of the progress status.
   string FormatProgressStatus(const char* progress_status_format) const;
 
  private:
@@ -200,38 +210,56 @@
   /// The custom progress status format to use.
   const char* progress_status_format_;
 
+  template<size_t S>
+  void snprinfRate(double rate, char(&buf)[S], const char* format) const {
+    if (rate == -1) snprintf(buf, S, "?");
+    else            snprintf(buf, S, format, rate);
+  }
+
   struct RateInfo {
-    RateInfo() : last_update_(0), rate_(-1) {}
+    RateInfo() : rate_(-1) {}
 
-    double rate() const { return rate_; }
-    int last_update() const { return last_update_; }
-    void Restart() { return stopwatch_.Restart(); }
+    void Restart() { stopwatch_.Restart(); }
+    double rate() { return rate_; }
 
-    double UpdateRate(int edges, int update_hint) {
-      if (update_hint != last_update_) {
-        rate_ = edges / stopwatch_.Elapsed() + 0.5;
-        last_update_ = update_hint;
-      }
-      return rate_;
-    }
-
-    template<class T>
-    void snprinfRate(T buf, const char* format) {
-      if (rate_ == -1)
-        snprintf(buf, sizeof(buf), "?");
-      else
-        snprintf(buf, sizeof(buf), format, rate_);
+    void UpdateRate(int edges) {
+      if (edges && stopwatch_.Elapsed())
+        rate_ = edges / stopwatch_.Elapsed();
     }
 
   private:
-    Stopwatch stopwatch_;
-    int last_update_;
     double rate_;
+    Stopwatch stopwatch_;
+  };
+
+  struct SlidingRateInfo {
+    SlidingRateInfo(int n) : rate_(-1), N(n), last_update_(-1) {}
+
+    void Restart() { stopwatch_.Restart(); }
+    double rate() { return rate_; }
+
+    void UpdateRate(int update_hint) {
+      if (update_hint == last_update_)
+        return;
+      last_update_ = update_hint;
+
+      if (times_.size() == N)
+        times_.pop();
+      times_.push(stopwatch_.Elapsed());
+      if (times_.back() != times_.front())
+        rate_ = times_.size() / (times_.back() - times_.front());
+    }
+
+  private:
+    double rate_;
+    Stopwatch stopwatch_;
+    const size_t N;
+    std::queue<double> times_;
+    int last_update_;
   };
 
   mutable RateInfo overall_rate_;
-  mutable RateInfo current_rate_;
-  const int current_rate_average_count_;
+  mutable SlidingRateInfo current_rate_;
 
 #ifdef _WIN32
   void* console_;
diff --git a/src/build_log.cc b/src/build_log.cc
index a633892..6b73002 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -56,7 +56,7 @@
   uint64_t h = seed ^ (len * m);
   const uint64_t * data = (const uint64_t *)key;
   const uint64_t * end = data + (len/8);
-  while(data != end) {
+  while (data != end) {
     uint64_t k = *data++;
     k *= m;
     k ^= k >> r;
@@ -65,7 +65,7 @@
     h *= m;
   }
   const unsigned char* data2 = (const unsigned char*)data;
-  switch(len & 7)
+  switch (len & 7)
   {
   case 7: h ^= uint64_t(data2[6]) << 48;
   case 6: h ^= uint64_t(data2[5]) << 40;
@@ -91,6 +91,15 @@
   return MurmurHash64A(command.str_, command.len_);
 }
 
+BuildLog::LogEntry::LogEntry(const string& output)
+  : output(output) {}
+
+BuildLog::LogEntry::LogEntry(const string& output, uint64_t command_hash,
+  int start_time, int end_time, TimeStamp restat_mtime)
+  : output(output), command_hash(command_hash),
+    start_time(start_time), end_time(end_time), restat_mtime(restat_mtime)
+{}
+
 BuildLog::BuildLog()
   : log_file_(NULL), needs_recompaction_(false) {}
 
@@ -130,6 +139,7 @@
 void BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
                              TimeStamp restat_mtime) {
   string command = edge->EvaluateCommand(true);
+  uint64_t command_hash = LogEntry::HashCommand(command);
   for (vector<Node*>::iterator out = edge->outputs_.begin();
        out != edge->outputs_.end(); ++out) {
     const string& path = (*out)->path();
@@ -138,11 +148,10 @@
     if (i != entries_.end()) {
       log_entry = i->second;
     } else {
-      log_entry = new LogEntry;
-      log_entry->output = path;
+      log_entry = new LogEntry(path);
       entries_.insert(Entries::value_type(log_entry->output, log_entry));
     }
-    log_entry->command_hash = LogEntry::HashCommand(command);
+    log_entry->command_hash = command_hash;
     log_entry->start_time = start_time;
     log_entry->end_time = end_time;
     log_entry->restat_mtime = restat_mtime;
@@ -158,8 +167,7 @@
   log_file_ = NULL;
 }
 
-class LineReader {
- public:
+struct LineReader {
   explicit LineReader(FILE* file)
     : file_(file), buf_end_(buf_), line_start_(buf_), line_end_(NULL) {
       memset(buf_, 0, sizeof(buf_));
@@ -287,8 +295,7 @@
     if (i != entries_.end()) {
       entry = i->second;
     } else {
-      entry = new LogEntry;
-      entry->output = output;
+      entry = new LogEntry(output);
       entries_.insert(Entries::value_type(entry->output, entry));
       ++unique_entry_count;
     }
@@ -341,6 +348,7 @@
 }
 
 bool BuildLog::Recompact(const string& path, string* err) {
+  METRIC_RECORD(".ninja_log recompact");
   printf("Recompacting log...\n");
 
   string temp_path = path + ".recompact";
diff --git a/src/build_log.h b/src/build_log.h
index 4141ff3..231bfd9 100644
--- a/src/build_log.h
+++ b/src/build_log.h
@@ -60,6 +60,10 @@
           start_time == o.start_time && end_time == o.end_time &&
           restat_mtime == o.restat_mtime;
     }
+
+    explicit LogEntry(const string& output);
+    LogEntry(const string& output, uint64_t command_hash,
+             int start_time, int end_time, TimeStamp restat_mtime);
   };
 
   /// Lookup a previously-run command by its output path.
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index a6c2a86..2dd6500 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -26,6 +26,8 @@
 #include <unistd.h>
 #endif
 
+namespace {
+
 const char kTestFilename[] = "BuildLogTest-tempfile";
 
 struct BuildLogTest : public StateTestWithBuiltinRules {
@@ -145,7 +147,8 @@
     ASSERT_EQ(0, truncate(kTestFilename, size));
 #else
     int fh;
-    fh = _sopen(kTestFilename, _O_RDWR | _O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+    fh = _sopen(kTestFilename, _O_RDWR | _O_CREAT, _SH_DENYNO,
+                _S_IREAD | _S_IWRITE);
     ASSERT_EQ(0, _chsize(fh, size));
     _close(fh);
 #endif
@@ -245,3 +248,25 @@
   ASSERT_EQ(789, e->restat_mtime);
   ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
 }
+
+TEST_F(BuildLogTest, MultiTargetEdge) {
+  AssertParse(&state_,
+"build out out.d: cat\n");
+
+  BuildLog log;
+  log.RecordCommand(state_.edges_[0], 21, 22);
+
+  ASSERT_EQ(2u, log.entries().size());
+  BuildLog::LogEntry* e1 = log.LookupByOutput("out");
+  ASSERT_TRUE(e1);
+  BuildLog::LogEntry* e2 = log.LookupByOutput("out.d");
+  ASSERT_TRUE(e2);
+  ASSERT_EQ("out", e1->output);
+  ASSERT_EQ("out.d", e2->output);
+  ASSERT_EQ(21, e1->start_time);
+  ASSERT_EQ(21, e2->start_time);
+  ASSERT_EQ(22, e2->end_time);
+  ASSERT_EQ(22, e2->end_time);
+}
+
+}  // anonymous namespace
diff --git a/src/build_test.cc b/src/build_test.cc
index 859e758..59c4c53 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -176,6 +176,132 @@
   ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err);
 }
 
+TEST_F(PlanTest, PoolWithDepthOne) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"pool foobar\n"
+"  depth = 1\n"
+"rule poolcat\n"
+"  command = cat $in > $out\n"
+"  pool = foobar\n"
+"build out1: poolcat in\n"
+"build out2: poolcat in\n"));
+  GetNode("out1")->MarkDirty();
+  GetNode("out2")->MarkDirty();
+  string err;
+  EXPECT_TRUE(plan_.AddTarget(GetNode("out1"), &err));
+  ASSERT_EQ("", err);
+  EXPECT_TRUE(plan_.AddTarget(GetNode("out2"), &err));
+  ASSERT_EQ("", err);
+  ASSERT_TRUE(plan_.more_to_do());
+
+  Edge* edge = plan_.FindWork();
+  ASSERT_TRUE(edge);
+  ASSERT_EQ("in",  edge->inputs_[0]->path());
+  ASSERT_EQ("out1", edge->outputs_[0]->path());
+
+  // This will be false since poolcat is serialized
+  ASSERT_FALSE(plan_.FindWork());
+
+  plan_.EdgeFinished(edge);
+
+  edge = plan_.FindWork();
+  ASSERT_TRUE(edge);
+  ASSERT_EQ("in", edge->inputs_[0]->path());
+  ASSERT_EQ("out2", edge->outputs_[0]->path());
+
+  ASSERT_FALSE(plan_.FindWork());
+
+  plan_.EdgeFinished(edge);
+
+  ASSERT_FALSE(plan_.more_to_do());
+  edge = plan_.FindWork();
+  ASSERT_EQ(0, edge);
+}
+
+TEST_F(PlanTest, PoolsWithDepthTwo) {
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"pool foobar\n"
+"  depth = 2\n"
+"pool bazbin\n"
+"  depth = 2\n"
+"rule foocat\n"
+"  command = cat $in > $out\n"
+"  pool = foobar\n"
+"rule bazcat\n"
+"  command = cat $in > $out\n"
+"  pool = bazbin\n"
+"build out1: foocat in\n"
+"build out2: foocat in\n"
+"build out3: foocat in\n"
+"build outb1: bazcat in\n"
+"build outb2: bazcat in\n"
+"build outb3: bazcat in\n"
+"  pool =\n"
+"build allTheThings: cat out1 out2 out3 outb1 outb2 outb3\n"
+));
+  // Mark all the out* nodes dirty
+  for (int i = 0; i < 3; ++i) {
+    GetNode("out" + string(1, '1' + i))->MarkDirty();
+    GetNode("outb" + string(1, '1' + i))->MarkDirty();
+  }
+  GetNode("allTheThings")->MarkDirty();
+
+  string err;
+  EXPECT_TRUE(plan_.AddTarget(GetNode("allTheThings"), &err));
+  ASSERT_EQ("", err);
+
+  // Grab the first 4 edges, out1 out2 outb1 outb2
+  deque<Edge*> edges;
+  for (int i = 0; i < 4; ++i) {
+    ASSERT_TRUE(plan_.more_to_do());
+    Edge* edge = plan_.FindWork();
+    ASSERT_TRUE(edge);
+    ASSERT_EQ("in",  edge->inputs_[0]->path());
+    string base_name(i < 2 ? "out" : "outb");
+    ASSERT_EQ(base_name + string(1, '1' + (i % 2)), edge->outputs_[0]->path());
+    edges.push_back(edge);
+  }
+
+  // outb3 is exempt because it has an empty pool
+  ASSERT_TRUE(plan_.more_to_do());
+  Edge* edge = plan_.FindWork();
+  ASSERT_TRUE(edge);
+  ASSERT_EQ("in",  edge->inputs_[0]->path());
+  ASSERT_EQ("outb3", edge->outputs_[0]->path());
+  edges.push_back(edge);
+
+  ASSERT_FALSE(plan_.FindWork());
+
+  // finish out1
+  plan_.EdgeFinished(edges.front());
+  edges.pop_front();
+
+  // out3 should be available
+  Edge* out3 = plan_.FindWork();
+  ASSERT_TRUE(out3);
+  ASSERT_EQ("in",  out3->inputs_[0]->path());
+  ASSERT_EQ("out3", out3->outputs_[0]->path());
+
+  ASSERT_FALSE(plan_.FindWork());
+
+  plan_.EdgeFinished(out3);
+
+  ASSERT_FALSE(plan_.FindWork());
+
+  for (deque<Edge*>::iterator it = edges.begin(); it != edges.end(); ++it) {
+    plan_.EdgeFinished(*it);
+  }
+
+  Edge* final = plan_.FindWork();
+  ASSERT_TRUE(final);
+  ASSERT_EQ("allTheThings", final->outputs_[0]->path());
+
+  plan_.EdgeFinished(final);
+
+  ASSERT_FALSE(plan_.more_to_do());
+  ASSERT_FALSE(plan_.FindWork());
+}
+
 struct BuildTest : public StateTestWithBuiltinRules,
                    public CommandRunner {
   BuildTest() : config_(MakeConfig()),
@@ -447,10 +573,12 @@
   string err;
 
 #ifdef _WIN32
-  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build subdir\\dir2\\file: cat in1\n"));
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+                                      "build subdir\\dir2\\file: cat in1\n"));
   EXPECT_TRUE(builder_.AddTarget("subdir\\dir2\\file", &err));
 #else
-  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build subdir/dir2/file: cat in1\n"));
+  ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+                                      "build subdir/dir2/file: cat in1\n"));
   EXPECT_TRUE(builder_.AddTarget("subdir/dir2/file", &err));
 #endif
 
@@ -869,7 +997,7 @@
   // Create all necessary files
   fs_.Create("in", now_, "");
 
-  // The implicit dependencies and the depfile itself 
+  // The implicit dependencies and the depfile itself
   // are newer than the output
   TimeStamp restat_mtime = ++now_;
   fs_.Create("out1.d", now_, "out1: will.be.deleted restat.file\n");
@@ -889,10 +1017,10 @@
   ASSERT_TRUE(NULL != log_entry);
   ASSERT_EQ(restat_mtime, log_entry->restat_mtime);
 
-  // Now remove a file, referenced from depfile, so that target becomes 
+  // Now remove a file, referenced from depfile, so that target becomes
   // dirty, but the output does not change
   fs_.RemoveFile("will.be.deleted");
-  
+
   // Trigger the build again - only out1 gets built
   commands_ran_.clear();
   state_.Reset();
@@ -943,7 +1071,7 @@
 }
 
 // Test that RSP files are created when & where appropriate and deleted after
-// succesful execution.
+// successful execution.
 TEST_F(BuildTest, RspFileSuccess)
 {
   ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
@@ -1135,3 +1263,4 @@
   EXPECT_EQ("[%/s0/t0/r0/u0/f0]",
             status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]"));
 }
+
diff --git a/src/clean.cc b/src/clean.cc
index 3fe23ec..0b8476b 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -29,6 +29,7 @@
   : state_(state),
     config_(config),
     removed_(),
+    cleaned_(),
     cleaned_files_count_(0),
     disk_interface_(new RealDiskInterface),
     status_(0) {
@@ -40,6 +41,7 @@
   : state_(state),
     config_(config),
     removed_(),
+    cleaned_(),
     cleaned_files_count_(0),
     disk_interface_(disk_interface),
     status_(0) {
@@ -80,6 +82,16 @@
   return (i != removed_.end());
 }
 
+void Cleaner::RemoveEdgeFiles(Edge* edge) {
+  string depfile = edge->EvaluateDepFile();
+  if (!depfile.empty())
+    Remove(depfile);
+
+  string rspfile = edge->GetRspFile();
+  if (!rspfile.empty())
+    Remove(rspfile);
+}
+
 void Cleaner::PrintHeader() {
   if (config_.verbosity == BuildConfig::QUIET)
     return;
@@ -111,12 +123,8 @@
          out_node != (*e)->outputs_.end(); ++out_node) {
       Remove((*out_node)->path());
     }
-    // Remove the depfile
-    if (!(*e)->rule().depfile().empty())
-      Remove((*e)->EvaluateDepFile());
-    // Remove the response file
-    if ((*e)->HasRspFile()) 
-      Remove((*e)->GetRspFile());      
+
+    RemoveEdgeFiles(*e);
   }
   PrintFooter();
   return status_;
@@ -127,16 +135,20 @@
     // Do not try to remove phony targets
     if (!e->is_phony()) {
       Remove(target->path());
-      if (!target->in_edge()->rule().depfile().empty())
-        Remove(target->in_edge()->EvaluateDepFile());
-      if (e->HasRspFile())
-        Remove(e->GetRspFile());
+      RemoveEdgeFiles(e);
     }
     for (vector<Node*>::iterator n = e->inputs_.begin(); n != e->inputs_.end();
          ++n) {
-      DoCleanTarget(*n);
+      Node* next = *n;
+      // call DoCleanTarget recursively if this node has not been visited
+      if (cleaned_.count(next) == 0) {
+        DoCleanTarget(next);
+      }
     }
   }
+
+  // mark this target to be cleaned already
+  cleaned_.insert(target);
 }
 
 int Cleaner::CleanTarget(Node* target) {
@@ -191,10 +203,7 @@
       for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
            out_node != (*e)->outputs_.end(); ++out_node) {
         Remove((*out_node)->path());
-        if (!(*e)->rule().depfile().empty())
-          Remove((*e)->EvaluateDepFile());
-        if ((*e)->HasRspFile()) 
-          Remove((*e)->GetRspFile());
+        RemoveEdgeFiles(*e);
       }
     }
   }
@@ -249,4 +258,5 @@
   status_ = 0;
   cleaned_files_count_ = 0;
   removed_.clear();
+  cleaned_.clear();
 }
diff --git a/src/clean.h b/src/clean.h
index 5938dff..19432ab 100644
--- a/src/clean.h
+++ b/src/clean.h
@@ -27,8 +27,7 @@
 struct Rule;
 struct DiskInterface;
 
-class Cleaner {
- public:
+struct Cleaner {
   /// Build a cleaner object with a real disk interface.
   Cleaner(State* state, const BuildConfig& config);
 
@@ -81,10 +80,14 @@
   /// @returns whether the file @a path exists.
   bool FileExists(const string& path);
   void Report(const string& path);
+
   /// Remove the given @a path file only if it has not been already removed.
   void Remove(const string& path);
   /// @return whether the given @a path has already been removed.
   bool IsAlreadyRemoved(const string& path);
+  /// Remove the depfile and rspfile for an Edge.
+  void RemoveEdgeFiles(Edge* edge);
+
   /// Helper recursive method for CleanTarget().
   void DoCleanTarget(Node* target);
   void PrintHeader();
@@ -95,6 +98,7 @@
   State* state_;
   const BuildConfig& config_;
   set<string> removed_;
+  set<Node*> cleaned_;
   int cleaned_files_count_;
   DiskInterface* disk_interface_;
   int status_;
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index 515ff59..7c557cd 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -80,7 +80,8 @@
   // MSDN: "Naming Files, Paths, and Namespaces"
   // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
   if (!path.empty() && path[0] != '\\' && path.size() > MAX_PATH) {
-    Error("Stat(%s): Filename longer than %i characters", path.c_str(), MAX_PATH);
+    Error("Stat(%s): Filename longer than %i characters",
+          path.c_str(), MAX_PATH);
     return -1;
   }
   WIN32_FILE_ATTRIBUTE_DATA attrs;
@@ -116,18 +117,21 @@
 bool RealDiskInterface::WriteFile(const string& path, const string& contents) {
   FILE * fp = fopen(path.c_str(), "w");
   if (fp == NULL) {
-    Error("WriteFile(%s): Unable to create file. %s", path.c_str(), strerror(errno));
+    Error("WriteFile(%s): Unable to create file. %s",
+          path.c_str(), strerror(errno));
     return false;
   }
 
   if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length())  {
-    Error("WriteFile(%s): Unable to write to the file. %s", path.c_str(), strerror(errno));
+    Error("WriteFile(%s): Unable to write to the file. %s",
+          path.c_str(), strerror(errno));
     fclose(fp);
     return false;
   }
 
   if (fclose(fp) == EOF) {
-    Error("WriteFile(%s): Unable to close the file. %s", path.c_str(), strerror(errno));
+    Error("WriteFile(%s): Unable to close the file. %s",
+          path.c_str(), strerror(errno));
     return false;
   }
 
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index 32fe9cb..c2315c7 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -25,8 +25,7 @@
 
 namespace {
 
-class DiskInterfaceTest : public testing::Test {
- public:
+struct DiskInterfaceTest : public testing::Test {
   virtual void SetUp() {
     // These tests do real disk accesses, so create a temp dir.
     temp_dir_.CreateAndEnter("Ninja-DiskInterfaceTest");
diff --git a/src/graph.cc b/src/graph.cc
index 6ae324d..f9b9c6f 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -145,9 +145,10 @@
     if (edge->rule_->restat() && build_log() &&
         (entry = build_log()->LookupByOutput(output->path()))) {
       if (entry->restat_mtime < most_recent_stamp) {
-        EXPLAIN("restat of output %s older than most recent input %s (%d vs %d)",
-            output->path().c_str(), most_recent_input->path().c_str(),
-            entry->restat_mtime, most_recent_stamp);
+        EXPLAIN("restat of output %s older than most recent input %s "
+                "(%d vs %d)",
+                output->path().c_str(), most_recent_input->path().c_str(),
+                entry->restat_mtime, most_recent_stamp);
         return true;
       }
     } else {
@@ -192,7 +193,7 @@
   virtual string LookupVariable(const string& var);
 
   /// Given a span of Nodes, construct a list of paths suitable for a command
-  /// line.  XXX here is where shell-escaping of e.g spaces should happen.
+  /// line.
   string MakePathList(vector<Node*>::iterator begin,
                       vector<Node*>::iterator end,
                       char sep);
@@ -214,7 +215,6 @@
   } else if (edge_->env_) {
     return edge_->env_->LookupVariable(var);
   } else {
-    // XXX should we warn here?
     return string();
   }
 }
@@ -241,7 +241,7 @@
 string Edge::EvaluateCommand(bool incl_rsp_file) {
   EdgeEnv env(this);
   string command = rule_->command().Evaluate(&env);
-  if (incl_rsp_file && HasRspFile()) 
+  if (incl_rsp_file && HasRspFile())
     command += ";rspfile=" + GetRspFileContent();
   return command;
 }
@@ -317,7 +317,8 @@
     // create one; this makes us not abort if the input is missing,
     // but instead will rebuild in that circumstance.
     if (!node->in_edge()) {
-      Edge* phony_edge = state_->AddEdge(&State::kPhonyRule);
+      Edge* phony_edge = state_->AddEdge(&State::kPhonyRule,
+                                         &State::kDefaultPool);
       node->set_in_edge(phony_edge);
       phony_edge->outputs_.push_back(node);
 
@@ -345,6 +346,13 @@
        i != outputs_.end() && *i != NULL; ++i) {
     printf("%s ", (*i)->path().c_str());
   }
+  if (pool_) {
+    if (!pool_->name().empty()) {
+      printf("(in pool '%s')", pool_->name().c_str());
+    }
+  } else {
+    printf("(null pool?)");
+  }
   printf("] 0x%p\n", this);
 }
 
@@ -353,18 +361,18 @@
 }
 
 void Node::Dump(const char* prefix) const {
-    printf("%s <%s 0x%p> mtime: %d%s, (:%s), ",
-           prefix, path().c_str(), this,
-           mtime(), mtime()?"":" (:missing)",
-           dirty()?" dirty":" clean");
-    if (in_edge()) {
-        in_edge()->Dump("in-edge: ");
-    }else{
-        printf("no in-edge\n");
-    }
-    printf(" out edges:\n");
-    for (vector<Edge*>::const_iterator e = out_edges().begin();
-         e != out_edges().end() && *e != NULL; ++e) {
-        (*e)->Dump(" +- ");
-    }
+  printf("%s <%s 0x%p> mtime: %d%s, (:%s), ",
+         prefix, path().c_str(), this,
+         mtime(), mtime() ? "" : " (:missing)",
+         dirty() ? " dirty" : " clean");
+  if (in_edge()) {
+    in_edge()->Dump("in-edge: ");
+  } else {
+    printf("no in-edge\n");
+  }
+  printf(" out edges:\n");
+  for (vector<Edge*>::const_iterator e = out_edges().begin();
+       e != out_edges().end() && *e != NULL; ++e) {
+    (*e)->Dump(" +- ");
+  }
 }
diff --git a/src/graph.h b/src/graph.h
index 272fcb9..3c31e19 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -131,6 +131,7 @@
   EvalString command_;
   EvalString description_;
   EvalString depfile_;
+  EvalString pool_;
   EvalString rspfile_;
   EvalString rspfile_content_;
 };
@@ -138,6 +139,7 @@
 struct BuildLog;
 struct Node;
 struct State;
+struct Pool;
 
 /// An edge in the dependency graph; links between Nodes using Rules.
 struct Edge {
@@ -150,7 +152,7 @@
   /// 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);  // XXX move to env, take env ptr
+  string EvaluateCommand(bool incl_rsp_file = false);
   string EvaluateDepFile();
   string GetDescription();
 
@@ -166,25 +168,25 @@
   void Dump(const char* prefix="") const;
 
   const Rule* rule_;
+  Pool* pool_;
   vector<Node*> inputs_;
   vector<Node*> outputs_;
   Env* env_;
   bool outputs_ready_;
 
   const Rule& rule() const { return *rule_; }
+  Pool* pool() const { return pool_; }
+  int weight() const { return 1; }
   bool outputs_ready() const { return outputs_ready_; }
 
-  // XXX There are three types of inputs.
+  // There are three types of inputs.
   // 1) explicit deps, which show up as $in on the command line;
   // 2) implicit deps, which the target depends on implicitly (e.g. C headers),
   //                   and changes in them cause the target to rebuild;
   // 3) order-only deps, which are needed before the target builds but which
   //                     don't cause the target to rebuild.
-  // Currently we stuff all of these into inputs_ and keep counts of #2 and #3
-  // when we need to compute subsets.  This is suboptimal; should think of a
-  // better representation.  (Could make each pointer into a pair of a pointer
-  // and a type of input, or if memory matters could use the low bits of the
-  // pointer...)
+  // These are stored in inputs_ in that order, and we keep counts of
+  // #2 and #3 when we need to access the various subsets.
   int implicit_deps_;
   int order_only_deps_;
   bool is_implicit(size_t index) {
diff --git a/src/hash_map.h b/src/hash_map.h
index 9904fb8..076f6c0 100644
--- a/src/hash_map.h
+++ b/src/hash_map.h
@@ -25,7 +25,7 @@
   const int r = 24;
   unsigned int h = seed ^ len;
   const unsigned char * data = (const unsigned char *)key;
-  while(len >= 4) {
+  while (len >= 4) {
     unsigned int k = *(unsigned int *)data;
     k *= m;
     k ^= k >> r;
@@ -35,7 +35,7 @@
     data += 4;
     len -= 4;
   }
-  switch(len) {
+  switch (len) {
   case 3: h ^= data[2] << 16;
   case 2: h ^= data[1] << 8;
   case 1: h ^= data[0];
diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc
index 77b5b3b..29e6755 100644
--- a/src/includes_normalize_test.cc
+++ b/src/includes_normalize_test.cc
@@ -40,7 +40,8 @@
 TEST(IncludesNormalize, WithRelative) {
   string currentdir = IncludesNormalize::ToLower(GetCurDir());
   EXPECT_EQ("c", IncludesNormalize::Normalize("a/b/c", "a/b"));
-  EXPECT_EQ("a", IncludesNormalize::Normalize(IncludesNormalize::AbsPath("a"), NULL));
+  EXPECT_EQ("a", IncludesNormalize::Normalize(IncludesNormalize::AbsPath("a"),
+                                              NULL));
   EXPECT_EQ(string("..\\") + currentdir + string("\\a"),
             IncludesNormalize::Normalize("a", "../b"));
   EXPECT_EQ(string("..\\") + currentdir + string("\\a\\b"),
@@ -69,16 +70,21 @@
 }
 
 TEST(IncludesNormalize, Split) {
-  EXPECT_EQ("", IncludesNormalize::Join(IncludesNormalize::Split("", '/'), ':'));
-  EXPECT_EQ("a", IncludesNormalize::Join(IncludesNormalize::Split("a", '/'), ':'));
-  EXPECT_EQ("a:b:c", IncludesNormalize::Join(IncludesNormalize::Split("a/b/c", '/'), ':'));
+  EXPECT_EQ("", IncludesNormalize::Join(IncludesNormalize::Split("", '/'),
+                                        ':'));
+  EXPECT_EQ("a", IncludesNormalize::Join(IncludesNormalize::Split("a", '/'),
+                                         ':'));
+  EXPECT_EQ("a:b:c",
+            IncludesNormalize::Join(
+                IncludesNormalize::Split("a/b/c", '/'), ':'));
 }
 
 TEST(IncludesNormalize, ToLower) {
   EXPECT_EQ("", IncludesNormalize::ToLower(""));
   EXPECT_EQ("stuff", IncludesNormalize::ToLower("Stuff"));
   EXPECT_EQ("stuff and things", IncludesNormalize::ToLower("Stuff AND thINGS"));
-  EXPECT_EQ("stuff 3and thin43gs", IncludesNormalize::ToLower("Stuff 3AND thIN43GS"));
+  EXPECT_EQ("stuff 3and thin43gs",
+            IncludesNormalize::ToLower("Stuff 3AND thIN43GS"));
 }
 
 TEST(IncludesNormalize, DifferentDrive) {
diff --git a/src/lexer.cc b/src/lexer.cc
index 5d7d185..685fe81 100644
--- a/src/lexer.cc
+++ b/src/lexer.cc
@@ -83,6 +83,7 @@
   case NEWLINE:  return "newline";
   case PIPE2:    return "'||'";
   case PIPE:     return "'|'";
+  case POOL:     return "'pool'";
   case RULE:     return "'rule'";
   case SUBNINJA: return "'subninja'";
   case TEOF:     return "eof";
@@ -162,63 +163,71 @@
 	};
 
 	yych = *p;
-	if (yych <= 'Z') {
+	if (yych <= '^') {
 		if (yych <= ',') {
 			if (yych <= 0x1F) {
-				if (yych <= 0x00) goto yy21;
+				if (yych <= 0x00) goto yy22;
 				if (yych == '\n') goto yy6;
-				goto yy23;
+				goto yy24;
 			} else {
 				if (yych <= ' ') goto yy2;
 				if (yych == '#') goto yy4;
-				goto yy23;
+				goto yy24;
 			}
 		} else {
 			if (yych <= ':') {
-				if (yych == '/') goto yy23;
-				if (yych <= '9') goto yy20;
-				goto yy14;
+				if (yych == '/') goto yy24;
+				if (yych <= '9') goto yy21;
+				goto yy15;
 			} else {
-				if (yych == '=') goto yy12;
-				if (yych <= '@') goto yy23;
-				goto yy20;
+				if (yych <= '=') {
+					if (yych <= '<') goto yy24;
+					goto yy13;
+				} else {
+					if (yych <= '@') goto yy24;
+					if (yych <= 'Z') goto yy21;
+					goto yy24;
+				}
 			}
 		}
 	} else {
-		if (yych <= 'h') {
-			if (yych <= 'a') {
-				if (yych == '_') goto yy20;
-				if (yych <= '`') goto yy23;
-				goto yy20;
+		if (yych <= 'i') {
+			if (yych <= 'b') {
+				if (yych == '`') goto yy24;
+				if (yych <= 'a') goto yy21;
+				goto yy8;
 			} else {
-				if (yych <= 'b') goto yy8;
-				if (yych == 'd') goto yy11;
-				goto yy20;
+				if (yych == 'd') goto yy12;
+				if (yych <= 'h') goto yy21;
+				goto yy19;
 			}
 		} else {
-			if (yych <= 's') {
-				if (yych <= 'i') goto yy18;
-				if (yych <= 'q') goto yy20;
-				if (yych <= 'r') goto yy10;
-				goto yy19;
+			if (yych <= 'r') {
+				if (yych == 'p') goto yy10;
+				if (yych <= 'q') goto yy21;
+				goto yy11;
 			} else {
-				if (yych <= 'z') goto yy20;
-				if (yych == '|') goto yy16;
-				goto yy23;
+				if (yych <= 'z') {
+					if (yych <= 's') goto yy20;
+					goto yy21;
+				} else {
+					if (yych == '|') goto yy17;
+					goto yy24;
+				}
 			}
 		}
 	}
 yy2:
 	yyaccept = 0;
 	yych = *(q = ++p);
-	goto yy65;
+	goto yy70;
 yy3:
 	{ token = INDENT;   break; }
 yy4:
 	yyaccept = 1;
 	yych = *(q = ++p);
 	if (yych <= 0x00) goto yy5;
-	if (yych != '\r') goto yy60;
+	if (yych != '\r') goto yy65;
 yy5:
 	{ token = ERROR;    break; }
 yy6:
@@ -227,159 +236,173 @@
 	{ token = NEWLINE;  break; }
 yy8:
 	++p;
-	if ((yych = *p) == 'u') goto yy54;
-	goto yy25;
+	if ((yych = *p) == 'u') goto yy59;
+	goto yy26;
 yy9:
 	{ token = IDENT;    break; }
 yy10:
 	yych = *++p;
-	if (yych == 'u') goto yy50;
-	goto yy25;
+	if (yych == 'o') goto yy55;
+	goto yy26;
 yy11:
 	yych = *++p;
-	if (yych == 'e') goto yy43;
-	goto yy25;
+	if (yych == 'u') goto yy51;
+	goto yy26;
 yy12:
+	yych = *++p;
+	if (yych == 'e') goto yy44;
+	goto yy26;
+yy13:
 	++p;
 	{ token = EQUALS;   break; }
-yy14:
+yy15:
 	++p;
 	{ token = COLON;    break; }
-yy16:
+yy17:
 	++p;
-	if ((yych = *p) == '|') goto yy41;
+	if ((yych = *p) == '|') goto yy42;
 	{ token = PIPE;     break; }
-yy18:
-	yych = *++p;
-	if (yych == 'n') goto yy34;
-	goto yy25;
 yy19:
 	yych = *++p;
-	if (yych == 'u') goto yy26;
-	goto yy25;
+	if (yych == 'n') goto yy35;
+	goto yy26;
 yy20:
 	yych = *++p;
-	goto yy25;
+	if (yych == 'u') goto yy27;
+	goto yy26;
 yy21:
+	yych = *++p;
+	goto yy26;
+yy22:
 	++p;
 	{ token = TEOF;     break; }
-yy23:
+yy24:
 	yych = *++p;
 	goto yy5;
-yy24:
+yy25:
 	++p;
 	yych = *p;
-yy25:
+yy26:
 	if (yybm[0+yych] & 32) {
-		goto yy24;
+		goto yy25;
 	}
 	goto yy9;
-yy26:
+yy27:
 	yych = *++p;
-	if (yych != 'b') goto yy25;
+	if (yych != 'b') goto yy26;
 	yych = *++p;
-	if (yych != 'n') goto yy25;
+	if (yych != 'n') goto yy26;
 	yych = *++p;
-	if (yych != 'i') goto yy25;
+	if (yych != 'i') goto yy26;
 	yych = *++p;
-	if (yych != 'n') goto yy25;
+	if (yych != 'n') goto yy26;
 	yych = *++p;
-	if (yych != 'j') goto yy25;
+	if (yych != 'j') goto yy26;
 	yych = *++p;
-	if (yych != 'a') goto yy25;
+	if (yych != 'a') goto yy26;
 	++p;
 	if (yybm[0+(yych = *p)] & 32) {
-		goto yy24;
+		goto yy25;
 	}
 	{ token = SUBNINJA; break; }
-yy34:
+yy35:
 	yych = *++p;
-	if (yych != 'c') goto yy25;
+	if (yych != 'c') goto yy26;
 	yych = *++p;
-	if (yych != 'l') goto yy25;
+	if (yych != 'l') goto yy26;
 	yych = *++p;
-	if (yych != 'u') goto yy25;
+	if (yych != 'u') goto yy26;
 	yych = *++p;
-	if (yych != 'd') goto yy25;
+	if (yych != 'd') goto yy26;
 	yych = *++p;
-	if (yych != 'e') goto yy25;
+	if (yych != 'e') goto yy26;
 	++p;
 	if (yybm[0+(yych = *p)] & 32) {
-		goto yy24;
+		goto yy25;
 	}
 	{ token = INCLUDE;  break; }
-yy41:
+yy42:
 	++p;
 	{ token = PIPE2;    break; }
-yy43:
+yy44:
 	yych = *++p;
-	if (yych != 'f') goto yy25;
+	if (yych != 'f') goto yy26;
 	yych = *++p;
-	if (yych != 'a') goto yy25;
+	if (yych != 'a') goto yy26;
 	yych = *++p;
-	if (yych != 'u') goto yy25;
+	if (yych != 'u') goto yy26;
 	yych = *++p;
-	if (yych != 'l') goto yy25;
+	if (yych != 'l') goto yy26;
 	yych = *++p;
-	if (yych != 't') goto yy25;
+	if (yych != 't') goto yy26;
 	++p;
 	if (yybm[0+(yych = *p)] & 32) {
-		goto yy24;
+		goto yy25;
 	}
 	{ token = DEFAULT;  break; }
-yy50:
+yy51:
 	yych = *++p;
-	if (yych != 'l') goto yy25;
+	if (yych != 'l') goto yy26;
 	yych = *++p;
-	if (yych != 'e') goto yy25;
+	if (yych != 'e') goto yy26;
 	++p;
 	if (yybm[0+(yych = *p)] & 32) {
-		goto yy24;
+		goto yy25;
 	}
 	{ token = RULE;     break; }
-yy54:
+yy55:
 	yych = *++p;
-	if (yych != 'i') goto yy25;
+	if (yych != 'o') goto yy26;
 	yych = *++p;
-	if (yych != 'l') goto yy25;
-	yych = *++p;
-	if (yych != 'd') goto yy25;
+	if (yych != 'l') goto yy26;
 	++p;
 	if (yybm[0+(yych = *p)] & 32) {
-		goto yy24;
+		goto yy25;
+	}
+	{ token = POOL;     break; }
+yy59:
+	yych = *++p;
+	if (yych != 'i') goto yy26;
+	yych = *++p;
+	if (yych != 'l') goto yy26;
+	yych = *++p;
+	if (yych != 'd') goto yy26;
+	++p;
+	if (yybm[0+(yych = *p)] & 32) {
+		goto yy25;
 	}
 	{ token = BUILD;    break; }
-yy59:
+yy64:
 	++p;
 	yych = *p;
-yy60:
+yy65:
 	if (yybm[0+yych] & 64) {
-		goto yy59;
+		goto yy64;
 	}
-	if (yych <= 0x00) goto yy61;
-	if (yych <= '\f') goto yy62;
-yy61:
+	if (yych <= 0x00) goto yy66;
+	if (yych <= '\f') goto yy67;
+yy66:
 	p = q;
 	if (yyaccept <= 0) {
 		goto yy3;
 	} else {
 		goto yy5;
 	}
-yy62:
+yy67:
 	++p;
 	{ continue; }
-yy64:
+yy69:
 	yyaccept = 0;
 	q = ++p;
 	yych = *p;
-yy65:
+yy70:
 	if (yybm[0+yych] & 128) {
-		goto yy64;
+		goto yy69;
 	}
-	if (yych == '\n') goto yy66;
-	if (yych == '#') goto yy59;
+	if (yych == '\n') goto yy71;
+	if (yych == '#') goto yy64;
 	goto yy3;
-yy66:
+yy71:
 	++p;
 	yych = *p;
 	goto yy7;
@@ -445,39 +468,39 @@
 	};
 	yych = *p;
 	if (yych <= ' ') {
-		if (yych <= 0x00) goto yy73;
-		if (yych <= 0x1F) goto yy75;
+		if (yych <= 0x00) goto yy78;
+		if (yych <= 0x1F) goto yy80;
 	} else {
-		if (yych == '$') goto yy71;
-		goto yy75;
+		if (yych == '$') goto yy76;
+		goto yy80;
 	}
 	++p;
 	yych = *p;
-	goto yy79;
-yy70:
-	{ continue; }
-yy71:
-	++p;
-	if ((yych = *p) == '\n') goto yy76;
-yy72:
-	{ break; }
-yy73:
-	++p;
-	{ break; }
+	goto yy84;
 yy75:
-	yych = *++p;
-	goto yy72;
+	{ continue; }
 yy76:
 	++p;
-	{ continue; }
+	if ((yych = *p) == '\n') goto yy81;
+yy77:
+	{ break; }
 yy78:
 	++p;
+	{ break; }
+yy80:
+	yych = *++p;
+	goto yy77;
+yy81:
+	++p;
+	{ continue; }
+yy83:
+	++p;
 	yych = *p;
-yy79:
+yy84:
 	if (yybm[0+yych] & 128) {
-		goto yy78;
+		goto yy83;
 	}
-	goto yy70;
+	goto yy75;
 }
 
   }
@@ -527,40 +550,40 @@
 	yych = *p;
 	if (yych <= '@') {
 		if (yych <= '.') {
-			if (yych <= ',') goto yy84;
+			if (yych <= ',') goto yy89;
 		} else {
-			if (yych <= '/') goto yy84;
-			if (yych >= ':') goto yy84;
+			if (yych <= '/') goto yy89;
+			if (yych >= ':') goto yy89;
 		}
 	} else {
 		if (yych <= '_') {
-			if (yych <= 'Z') goto yy82;
-			if (yych <= '^') goto yy84;
+			if (yych <= 'Z') goto yy87;
+			if (yych <= '^') goto yy89;
 		} else {
-			if (yych <= '`') goto yy84;
-			if (yych >= '{') goto yy84;
+			if (yych <= '`') goto yy89;
+			if (yych >= '{') goto yy89;
 		}
 	}
-yy82:
+yy87:
 	++p;
 	yych = *p;
-	goto yy87;
-yy83:
+	goto yy92;
+yy88:
 	{
       out->assign(start, p - start);
       break;
     }
-yy84:
+yy89:
 	++p;
 	{ return false; }
-yy86:
+yy91:
 	++p;
 	yych = *p;
-yy87:
+yy92:
 	if (yybm[0+yych] & 128) {
-		goto yy86;
+		goto yy91;
 	}
-	goto yy83;
+	goto yy88;
 }
 
   }
@@ -615,29 +638,29 @@
 	yych = *p;
 	if (yych <= ' ') {
 		if (yych <= '\n') {
-			if (yych <= 0x00) goto yy96;
-			if (yych >= '\n') goto yy92;
+			if (yych <= 0x00) goto yy101;
+			if (yych >= '\n') goto yy97;
 		} else {
-			if (yych == '\r') goto yy98;
-			if (yych >= ' ') goto yy92;
+			if (yych == '\r') goto yy103;
+			if (yych >= ' ') goto yy97;
 		}
 	} else {
 		if (yych <= '9') {
-			if (yych == '$') goto yy94;
+			if (yych == '$') goto yy99;
 		} else {
-			if (yych <= ':') goto yy92;
-			if (yych == '|') goto yy92;
+			if (yych <= ':') goto yy97;
+			if (yych == '|') goto yy97;
 		}
 	}
 	++p;
 	yych = *p;
-	goto yy121;
-yy91:
+	goto yy126;
+yy96:
 	{
       eval->AddText(StringPiece(start, p - start));
       continue;
     }
-yy92:
+yy97:
 	++p;
 	{
       if (path) {
@@ -650,137 +673,137 @@
         continue;
       }
     }
-yy94:
+yy99:
 	++p;
 	if ((yych = *p) <= '/') {
 		if (yych <= ' ') {
-			if (yych == '\n') goto yy110;
-			if (yych <= 0x1F) goto yy99;
-			goto yy101;
+			if (yych == '\n') goto yy115;
+			if (yych <= 0x1F) goto yy104;
+			goto yy106;
 		} else {
 			if (yych <= '$') {
-				if (yych <= '#') goto yy99;
-				goto yy103;
+				if (yych <= '#') goto yy104;
+				goto yy108;
 			} else {
-				if (yych == '-') goto yy105;
-				goto yy99;
+				if (yych == '-') goto yy110;
+				goto yy104;
 			}
 		}
 	} else {
 		if (yych <= '^') {
 			if (yych <= ':') {
-				if (yych <= '9') goto yy105;
-				goto yy107;
+				if (yych <= '9') goto yy110;
+				goto yy112;
 			} else {
-				if (yych <= '@') goto yy99;
-				if (yych <= 'Z') goto yy105;
-				goto yy99;
+				if (yych <= '@') goto yy104;
+				if (yych <= 'Z') goto yy110;
+				goto yy104;
 			}
 		} else {
 			if (yych <= '`') {
-				if (yych <= '_') goto yy105;
-				goto yy99;
+				if (yych <= '_') goto yy110;
+				goto yy104;
 			} else {
-				if (yych <= 'z') goto yy105;
-				if (yych <= '{') goto yy109;
-				goto yy99;
+				if (yych <= 'z') goto yy110;
+				if (yych <= '{') goto yy114;
+				goto yy104;
 			}
 		}
 	}
-yy95:
+yy100:
 	{
       last_token_ = start;
       return Error(DescribeLastError(), err);
     }
-yy96:
+yy101:
 	++p;
 	{
       last_token_ = start;
       return Error("unexpected EOF", err);
     }
-yy98:
+yy103:
 	yych = *++p;
-	goto yy95;
-yy99:
+	goto yy100;
+yy104:
 	++p;
-yy100:
+yy105:
 	{
       last_token_ = start;
       return Error("bad $-escape (literal $ must be written as $$)", err);
     }
-yy101:
+yy106:
 	++p;
 	{
       eval->AddText(StringPiece(" ", 1));
       continue;
     }
-yy103:
+yy108:
 	++p;
 	{
       eval->AddText(StringPiece("$", 1));
       continue;
     }
-yy105:
+yy110:
 	++p;
 	yych = *p;
-	goto yy119;
-yy106:
+	goto yy124;
+yy111:
 	{
       eval->AddSpecial(StringPiece(start + 1, p - start - 1));
       continue;
     }
-yy107:
+yy112:
 	++p;
 	{
       eval->AddText(StringPiece(":", 1));
       continue;
     }
-yy109:
+yy114:
 	yych = *(q = ++p);
 	if (yybm[0+yych] & 32) {
-		goto yy113;
+		goto yy118;
 	}
-	goto yy100;
-yy110:
+	goto yy105;
+yy115:
 	++p;
 	yych = *p;
 	if (yybm[0+yych] & 16) {
-		goto yy110;
+		goto yy115;
 	}
 	{
       continue;
     }
-yy113:
-	++p;
-	yych = *p;
-	if (yybm[0+yych] & 32) {
-		goto yy113;
-	}
-	if (yych == '}') goto yy116;
-	p = q;
-	goto yy100;
-yy116:
-	++p;
-	{
-      eval->AddSpecial(StringPiece(start + 2, p - start - 3));
-      continue;
-    }
 yy118:
 	++p;
 	yych = *p;
-yy119:
-	if (yybm[0+yych] & 64) {
+	if (yybm[0+yych] & 32) {
 		goto yy118;
 	}
-	goto yy106;
-yy120:
+	if (yych == '}') goto yy121;
+	p = q;
+	goto yy105;
+yy121:
+	++p;
+	{
+      eval->AddSpecial(StringPiece(start + 2, p - start - 3));
+      continue;
+    }
+yy123:
 	++p;
 	yych = *p;
-yy121:
-	if (yybm[0+yych] & 128) {
-		goto yy120;
+yy124:
+	if (yybm[0+yych] & 64) {
+		goto yy123;
 	}
-	goto yy91;
+	goto yy111;
+yy125:
+	++p;
+	yych = *p;
+yy126:
+	if (yybm[0+yych] & 128) {
+		goto yy125;
+	}
+	goto yy96;
 }
 
   }
diff --git a/src/lexer.h b/src/lexer.h
index 03c59f2..f366556 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -41,6 +41,7 @@
     NEWLINE,
     PIPE,
     PIPE2,
+    POOL,
     RULE,
     SUBNINJA,
     TEOF,
diff --git a/src/lexer.in.cc b/src/lexer.in.cc
index 7ae9c61..93d5540 100644
--- a/src/lexer.in.cc
+++ b/src/lexer.in.cc
@@ -82,6 +82,7 @@
   case NEWLINE:  return "newline";
   case PIPE2:    return "'||'";
   case PIPE:     return "'|'";
+  case POOL:     return "'pool'";
   case RULE:     return "'rule'";
   case SUBNINJA: return "'subninja'";
   case TEOF:     return "eof";
@@ -135,6 +136,7 @@
     [ ]*[\n]   { token = NEWLINE;  break; }
     [ ]+       { token = INDENT;   break; }
     "build"    { token = BUILD;    break; }
+    "pool"     { token = POOL;     break; }
     "rule"     { token = RULE;     break; }
     "default"  { token = DEFAULT;  break; }
     "="        { token = EQUALS;   break; }
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index 405e244..2d052b5 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -47,6 +47,10 @@
   for (;;) {
     Lexer::Token token = lexer_.ReadToken();
     switch (token) {
+    case Lexer::POOL:
+      if (!ParsePool(err))
+        return false;
+      break;
     case Lexer::BUILD:
       if (!ParseEdge(err))
         return false;
@@ -91,6 +95,44 @@
   return false;  // not reached
 }
 
+
+bool ManifestParser::ParsePool(string* err) {
+  string name;
+  if (!lexer_.ReadIdent(&name))
+    return lexer_.Error("expected pool name", err);
+
+  if (!ExpectToken(Lexer::NEWLINE, err))
+    return false;
+
+  if (state_->LookupPool(name) != NULL)
+    return lexer_.Error("duplicate pool '" + name + "'", err);
+
+  int depth = -1;
+
+  while (lexer_.PeekToken(Lexer::INDENT)) {
+    string key;
+    EvalString value;
+    if (!ParseLet(&key, &value, err))
+      return false;
+
+    if (key == "depth") {
+      string depth_string = value.Evaluate(env_);
+      depth = atol(depth_string.c_str());
+      if (depth < 0)
+        return lexer_.Error("invalid pool depth", err);
+    } else {
+      return lexer_.Error("unexpected variable '" + key + "'", err);
+    }
+  }
+
+  if (depth < 0)
+    return lexer_.Error("expected 'depth =' line", err);
+
+  state_->AddPool(new Pool(name, depth));
+  return true;
+}
+
+
 bool ManifestParser::ParseRule(string* err) {
   string name;
   if (!lexer_.ReadIdent(&name))
@@ -126,6 +168,8 @@
       rule->rspfile_ = value;
     } else if (key == "rspfile_content") {
       rule->rspfile_content_ = value;
+    } else if (key == "pool") {
+      rule->pool_ = value;
     } else {
       // Die on other keyvals for now; revisit if we want to add a
       // scope here.
@@ -133,8 +177,10 @@
     }
   }
 
-  if (rule->rspfile_.empty() != rule->rspfile_content_.empty())
-    return lexer_.Error("rspfile and rspfile_content need to be both specified", err);
+  if (rule->rspfile_.empty() != rule->rspfile_content_.empty()) {
+    return lexer_.Error("rspfile and rspfile_content need to be both specified",
+                        err);
+  }
 
   if (rule->command_.empty())
     return lexer_.Error("expected 'command =' line", err);
@@ -252,6 +298,7 @@
 
   // Default to using outer env.
   BindingEnv* env = env_;
+  Pool* pool = NULL;
 
   // But create and fill a nested env if there are variables in scope.
   if (lexer_.PeekToken(Lexer::INDENT)) {
@@ -262,11 +309,28 @@
       EvalString val;
       if (!ParseLet(&key, &val, err))
         return false;
-      env->AddBinding(key, val.Evaluate(env_));
+      if (key == "pool") {
+        string pool_name = val.Evaluate(env_);
+        pool = state_->LookupPool(pool_name);
+        if (pool == NULL)
+          return lexer_.Error("undefined pool '" + pool_name + "'", err);
+      } else {
+        env->AddBinding(key, val.Evaluate(env_));
+      }
     } while (lexer_.PeekToken(Lexer::INDENT));
   }
 
-  Edge* edge = state_->AddEdge(rule);
+  if (pool == NULL) {
+    if (!rule->pool_.empty()) {
+      pool = state_->LookupPool(rule->pool_.Evaluate(env_));
+      if (pool == NULL)
+        return lexer_.Error("cannot resolve pool for this edge.", err);
+    } else {
+      pool = &State::kDefaultPool;
+    }
+  }
+
+  Edge* edge = state_->AddEdge(rule, pool);
   edge->env_ = env;
   for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) {
     string path = i->Evaluate(env);
diff --git a/src/manifest_parser.h b/src/manifest_parser.h
index a2c6c93..a08e5af 100644
--- a/src/manifest_parser.h
+++ b/src/manifest_parser.h
@@ -50,6 +50,7 @@
   bool Parse(const string& filename, const string& input, string* err);
 
   /// Parse various statement types.
+  bool ParsePool(string* err);
   bool ParseRule(string* err);
   bool ParseLet(string* key, EvalString* val, string* err);
   bool ParseEdge(string* err);
diff --git a/src/metrics.h b/src/metrics.h
index f5ac0de..b6da859 100644
--- a/src/metrics.h
+++ b/src/metrics.h
@@ -59,29 +59,27 @@
 };
 
 /// Get the current time as relative to some epoch.
-/// Epoch varies between platforms; only useful for measuring elapsed
-/// time.
+/// Epoch varies between platforms; only useful for measuring elapsed time.
 int64_t GetTimeMillis();
 
-
-/// A simple stopwatch which retruns the time
-// in seconds since Restart() was called
-class Stopwatch
-{
-public:
+/// A simple stopwatch which returns the time
+/// in seconds since Restart() was called.
+struct Stopwatch {
+ public:
   Stopwatch() : started_(0) {}
 
-  /// Seconds since Restart() call
-  double Elapsed() const { return 1e-6 * static_cast<double>(Now() - started_); }
+  /// Seconds since Restart() call.
+  double Elapsed() const {
+    return 1e-6 * static_cast<double>(Now() - started_);
+  }
 
   void Restart() { started_ = Now(); }
 
-private:
+ private:
   uint64_t started_;
   uint64_t Now() const;
 };
 
-
 /// The primary interface to metrics.  Use METRIC_RECORD("foobar") at the top
 /// of a function to get timing stats recorded for each call of the function.
 #define METRIC_RECORD(name)                                             \
diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc
index 8e440fe..fd9b671 100644
--- a/src/msvc_helper-win32.cc
+++ b/src/msvc_helper-win32.cc
@@ -14,6 +14,7 @@
 
 #include "msvc_helper.h"
 
+#include <stdio.h>
 #include <string.h>
 #include <windows.h>
 
@@ -28,6 +29,21 @@
           input.substr(input.size() - needle.size()) == needle);
 }
 
+string Replace(const string& input, const string& find, const string& replace) {
+  string result = input;
+  size_t start_pos = 0;
+  while ((start_pos = result.find(find, start_pos)) != string::npos) {
+    result.replace(start_pos, find.length(), replace);
+    start_pos += replace.length();
+  }
+  return result;
+}
+
+string EscapeForDepfile(const string& path) {
+  // Depfiles don't escape single \.
+  return Replace(path, " ", "\\ ");
+}
+
 }  // anonymous namespace
 
 // static
@@ -125,7 +141,7 @@
       if (!include.empty()) {
         include = IncludesNormalize::Normalize(include, NULL);
         if (!IsSystemInclude(include))
-          includes_.push_back(include);
+          includes_.insert(include);
       } else if (FilterInputFilename(line)) {
         // Drop it.
         // TODO: if we support compiling multiple output files in a single
@@ -162,3 +178,11 @@
 
   return exit_code;
 }
+
+vector<string> CLWrapper::GetEscapedResult() {
+  vector<string> result;
+  for (set<string>::iterator i = includes_.begin(); i != includes_.end(); ++i) {
+    result.push_back(EscapeForDepfile(*i));
+  }
+  return result;
+}
diff --git a/src/msvc_helper.h b/src/msvc_helper.h
index f623520..102201b 100644
--- a/src/msvc_helper.h
+++ b/src/msvc_helper.h
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <string>
+#include <set>
 #include <vector>
 using namespace std;
 
@@ -49,6 +50,10 @@
   /// Exposed for testing.
   static bool FilterInputFilename(const string& line);
 
+  /// Fill a vector with the unique'd headers, escaped for output as a .d
+  /// file.
+  vector<string> GetEscapedResult();
+
   void* env_block_;
-  vector<string> includes_;
+  set<string> includes_;
 };
diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc
index 0c8db37..152450e 100644
--- a/src/msvc_helper_main-win32.cc
+++ b/src/msvc_helper_main-win32.cc
@@ -14,6 +14,7 @@
 
 #include "msvc_helper.h"
 
+#include <stdio.h>
 #include <windows.h>
 
 #include "util.h"
@@ -27,7 +28,6 @@
 "usage: ninja -t msvc [options] -- cl.exe /showIncludes /otherArgs\n"
 "options:\n"
 "  -e ENVFILE load environment block from ENVFILE as environment\n"
-"  -r BASE    normalize paths and make relative to BASE before output\n"
 "  -o FILE    write output dependency information to FILE.d\n"
          );
 }
@@ -48,7 +48,6 @@
 
 int MSVCHelperMain(int argc, char** argv) {
   const char* output_filename = NULL;
-  const char* relative_to = NULL;
   const char* envfile = NULL;
 
   const option kLongOptions[] = {
@@ -56,7 +55,7 @@
     { NULL, 0, NULL, 0 }
   };
   int opt;
-  while ((opt = getopt_long(argc, argv, "e:o:r:h", kLongOptions, NULL)) != -1) {
+  while ((opt = getopt_long(argc, argv, "e:o:h", kLongOptions, NULL)) != -1) {
     switch (opt) {
       case 'e':
         envfile = optarg;
@@ -64,9 +63,6 @@
       case 'o':
         output_filename = optarg;
         break;
-      case 'r':
-        relative_to = optarg;
-        break;
       case 'h':
       default:
         Usage();
@@ -105,8 +101,8 @@
     Fatal("opening %s: %s", depfile.c_str(), GetLastErrorString().c_str());
   }
   fprintf(output, "%s: ", output_filename);
-  for (vector<string>::iterator i = cl.includes_.begin();
-       i != cl.includes_.end(); ++i) {
+  vector<string> headers = cl.GetEscapedResult();
+  for (vector<string>::iterator i = headers.begin(); i != headers.end(); ++i) {
     fprintf(output, "%s\n", i->c_str());
   }
   fclose(output);
diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc
index 29fefd4..7730425 100644
--- a/src/msvc_helper_test.cc
+++ b/src/msvc_helper_test.cc
@@ -48,7 +48,7 @@
          &output);
   ASSERT_EQ("foo\nbar\n", output);
   ASSERT_EQ(1u, cl.includes_.size());
-  ASSERT_EQ("foo.h", cl.includes_[0]);
+  ASSERT_EQ("foo.h", *cl.includes_.begin());
 }
 
 TEST(MSVCHelperTest, RunFilenameFilter) {
@@ -70,7 +70,7 @@
   // system headers.
   ASSERT_EQ("", output);
   ASSERT_EQ(1u, cl.includes_.size());
-  ASSERT_EQ("path.h", cl.includes_[0]);
+  ASSERT_EQ("path.h", *cl.includes_.begin());
 }
 
 TEST(MSVCHelperTest, EnvBlock) {
@@ -81,3 +81,38 @@
   cl.Run("cmd /c \"echo foo is %foo%", &output);
   ASSERT_EQ("foo is bar\n", output);
 }
+
+TEST(MSVCHelperTest, DuplicatedHeader) {
+  CLWrapper cl;
+  string output;
+  cl.Run("cmd /c \"echo Note: including file: foo.h&&"
+         "echo Note: including file: bar.h&&"
+         "echo Note: including file: foo.h\"",
+         &output);
+  // We should have dropped one copy of foo.h.
+  ASSERT_EQ("", output);
+  ASSERT_EQ(2u, cl.includes_.size());
+}
+
+TEST(MSVCHelperTest, DuplicatedHeaderPathConverted) {
+  CLWrapper cl;
+  string output;
+  cl.Run("cmd /c \"echo Note: including file: sub/foo.h&&"
+         "echo Note: including file: bar.h&&"
+         "echo Note: including file: sub\\foo.h\"",
+         &output);
+  // We should have dropped one copy of foo.h.
+  ASSERT_EQ("", output);
+  ASSERT_EQ(2u, cl.includes_.size());
+}
+
+TEST(MSVCHelperTest, SpacesInFilename) {
+  CLWrapper cl;
+  string output;
+  cl.Run("cmd /c \"echo Note: including file: sub\\some sdk\\foo.h",
+         &output);
+  ASSERT_EQ("", output);
+  vector<string> headers = cl.GetEscapedResult();
+  ASSERT_EQ(1u, headers.size());
+  ASSERT_EQ("sub\\some\\ sdk\\foo.h", headers[0]);
+}
diff --git a/src/ninja.cc b/src/ninja.cc
index 5a3c530..08d4b14 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -49,7 +49,7 @@
 
 /// The version number of the current Ninja release.  This will always
 /// be "git" on trunk.
-const char* kVersion = "1.0.0";
+const char* kVersion = "1.1.0";
 
 /// Global information passed into subtools.
 struct Globals {
@@ -140,7 +140,7 @@
 /// An implementation of ManifestParser::FileReader that actually reads
 /// the file.
 struct RealFileReader : public ManifestParser::FileReader {
-  bool ReadFile(const string& path, string* content, string* err) {
+  virtual bool ReadFile(const string& path, string* content, string* err) {
     return ::ReadFile(path, content, err) == 0;
   }
 };
@@ -168,6 +168,50 @@
   return node->dirty();
 }
 
+Node* CollectTarget(State* state, const char* cpath, string* err) {
+  string path = cpath;
+  if (!CanonicalizePath(&path, err))
+    return NULL;
+
+  // Special syntax: "foo.cc^" means "the first output of foo.cc".
+  bool first_dependent = false;
+  if (!path.empty() && path[path.size() - 1] == '^') {
+    path.resize(path.size() - 1);
+    first_dependent = true;
+  }
+
+  Node* node = state->LookupNode(path);
+  if (node) {
+    if (first_dependent) {
+      if (node->out_edges().empty()) {
+        *err = "'" + path + "' has no out edge";
+        return NULL;
+      }
+      Edge* edge = node->out_edges()[0];
+      if (edge->outputs_.empty()) {
+        edge->Dump();
+        Fatal("edge has no outputs");
+      }
+      node = edge->outputs_[0];
+    }
+    return node;
+  } else {
+    *err = "unknown target '" + path + "'";
+
+    if (path == "clean") {
+      *err += ", did you mean 'ninja -t clean'?";
+    } else if (path == "help") {
+      *err += ", did you mean 'ninja -h'?";
+    } else {
+      Node* suggestion = state->SpellcheckNode(path);
+      if (suggestion) {
+        *err += ", did you mean '" + suggestion->path() + "'?";
+      }
+    }
+    return NULL;
+  }
+}
+
 bool CollectTargetsFromArgs(State* state, int argc, char* argv[],
                             vector<Node*>* targets, string* err) {
   if (argc == 0) {
@@ -176,47 +220,10 @@
   }
 
   for (int i = 0; i < argc; ++i) {
-    string path = argv[i];
-    if (!CanonicalizePath(&path, err))
+    Node* node = CollectTarget(state, argv[i], err);
+    if (node == NULL)
       return false;
-
-    // Special syntax: "foo.cc^" means "the first output of foo.cc".
-    bool first_dependent = false;
-    if (!path.empty() && path[path.size() - 1] == '^') {
-      path.resize(path.size() - 1);
-      first_dependent = true;
-    }
-
-    Node* node = state->LookupNode(path);
-    if (node) {
-      if (first_dependent) {
-        if (node->out_edges().empty()) {
-          *err = "'" + path + "' has no out edge";
-          return false;
-        }
-        Edge* edge = node->out_edges()[0];
-        if (edge->outputs_.empty()) {
-          edge->Dump();
-          Fatal("edge has no outputs");
-        }
-        node = edge->outputs_[0];
-      }
-      targets->push_back(node);
-    } else {
-      *err = "unknown target '" + path + "'";
-
-      if (path == "clean") {
-        *err += ", did you mean 'ninja -t clean'?";
-      } else if (path == "help") {
-        *err += ", did you mean 'ninja -h'?";
-      } else {
-        Node* suggestion = state->SpellcheckNode(path);
-        if (suggestion) {
-          *err += ", did you mean '" + suggestion->path() + "'?";
-        }
-      }
-      return false;
-    }
+    targets->push_back(node);
   }
   return true;
 }
@@ -244,19 +251,14 @@
     return 1;
   }
   for (int i = 0; i < argc; ++i) {
-    Node* node = globals->state->LookupNode(argv[i]);
+    string err;
+    Node* node = CollectTarget(globals->state, argv[i], &err);
     if (!node) {
-      Node* suggestion = globals->state->SpellcheckNode(argv[i]);
-      if (suggestion) {
-        printf("%s unknown, did you mean %s?\n",
-               argv[i], suggestion->path().c_str());
-      } else {
-        printf("%s unknown\n", argv[i]);
-      }
+      Error("%s", err.c_str());
       return 1;
     }
 
-    printf("%s:\n", argv[i]);
+    printf("%s:\n", node->path().c_str());
     if (Edge* edge = node->in_edge()) {
       printf("  input: %s\n", edge->rule_->name().c_str());
       for (int in = 0; in < (int)edge->inputs_.size(); in++) {
@@ -292,7 +294,7 @@
 }
 #endif  // _WIN32
 
-#if defined(WIN32)
+#if defined(_WIN32)
 int ToolMSVC(Globals* globals, int argc, char* argv[]) {
   // Reset getopt: push one argument onto the front of argv, reset optind.
   argc++;
@@ -537,7 +539,7 @@
     { "browse", "browse dependency graph in a web browser",
       Tool::RUN_AFTER_LOAD, ToolBrowse },
 #endif
-#if defined(WIN32)
+#if defined(_WIN32)
     { "msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)",
       Tool::RUN_AFTER_FLAGS, ToolMSVC },
 #endif
@@ -682,6 +684,9 @@
 
   if (!builder->Build(&err)) {
     printf("ninja: build stopped: %s.\n", err.c_str());
+    if (err.find("interrupted by user") != string::npos) {
+    	return 2;
+    }
     return 1;
   }
 
@@ -740,7 +745,7 @@
 
   int opt;
   while (tool_name.empty() &&
-         (opt = getopt_long(argc, argv, "d:f:hj:k:l:nt:vC:V", kLongOptions,
+         (opt = getopt_long(argc, argv, "d:f:j:k:l:nt:vC:h", kLongOptions,
                             NULL)) != -1) {
     switch (opt) {
       case 'd':
@@ -753,14 +758,6 @@
       case 'j':
         config.parallelism = atoi(optarg);
         break;
-      case 'l': {
-        char* end;
-        double value = strtod(optarg, &end);
-        if (end == optarg)
-          Fatal("-l parameter not numeric: did you mean -l 0.0?");
-        config.max_load_average = value;
-        break;
-      }
       case 'k': {
         char* end;
         int value = strtol(optarg, &end, 10);
@@ -773,15 +770,23 @@
         config.failures_allowed = value > 0 ? value : INT_MAX;
         break;
       }
+      case 'l': {
+        char* end;
+        double value = strtod(optarg, &end);
+        if (end == optarg)
+          Fatal("-l parameter not numeric: did you mean -l 0.0?");
+        config.max_load_average = value;
+        break;
+      }
       case 'n':
         config.dry_run = true;
         break;
-      case 'v':
-        config.verbosity = BuildConfig::VERBOSE;
-        break;
       case 't':
         tool_name = optarg;
         break;
+      case 'v':
+        config.verbosity = BuildConfig::VERBOSE;
+        break;
       case 'C':
         working_dir = optarg;
         break;
@@ -825,7 +830,6 @@
   bool rebuilt_manifest = false;
 
 reload:
-  RealDiskInterface disk_interface;
   RealFileReader file_reader;
   ManifestParser parser(globals.state, &file_reader);
   string err;
@@ -838,6 +842,7 @@
     return tool->func(&globals, argc, argv);
 
   BuildLog build_log;
+  RealDiskInterface disk_interface;
   if (!OpenLog(&build_log, &globals, &disk_interface))
     return 1;
 
diff --git a/src/state.cc b/src/state.cc
index 4c7168b..bb0cc15 100644
--- a/src/state.cc
+++ b/src/state.cc
@@ -22,10 +22,49 @@
 #include "metrics.h"
 #include "util.h"
 
+
+void Pool::EdgeScheduled(const Edge& edge) {
+  if (depth_ != 0)
+    current_use_ += edge.weight();
+}
+
+void Pool::EdgeFinished(const Edge& edge) {
+  if (depth_ != 0)
+    current_use_ -= edge.weight();
+}
+
+void Pool::DelayEdge(Edge* edge) {
+  assert(depth_ != 0);
+  delayed_.push_back(edge);
+}
+
+void Pool::RetrieveReadyEdges(set<Edge*>* ready_queue) {
+  while (!delayed_.empty()) {
+    Edge* edge = delayed_.front();
+    if (current_use_ + edge->weight() > depth_)
+      break;
+    delayed_.pop_front();
+    ready_queue->insert(edge);
+    EdgeScheduled(*edge);
+  }
+}
+
+void Pool::Dump() const {
+  printf("%s (%d/%d) ->\n", name_.c_str(), current_use_, depth_);
+  for (deque<Edge*>::const_iterator it = delayed_.begin();
+       it != delayed_.end(); ++it)
+  {
+    printf("\t");
+    (*it)->Dump();
+  }
+}
+
+Pool State::kDefaultPool("", 0);
 const Rule State::kPhonyRule("phony");
 
 State::State() {
   AddRule(&kPhonyRule);
+  AddPool(&kDefaultPool);
 }
 
 void State::AddRule(const Rule* rule) {
@@ -40,9 +79,22 @@
   return i->second;
 }
 
-Edge* State::AddEdge(const Rule* rule) {
+void State::AddPool(Pool* pool) {
+  assert(LookupPool(pool->name()) == NULL);
+  pools_[pool->name()] = pool;
+}
+
+Pool* State::LookupPool(const string& pool_name) {
+  map<string, Pool*>::iterator i = pools_.find(pool_name);
+  if (i == pools_.end())
+    return NULL;
+  return i->second;
+}
+
+Edge* State::AddEdge(const Rule* rule, Pool* pool) {
   Edge* edge = new Edge();
   edge->rule_ = rule;
+  edge->pool_ = pool;
   edge->env_ = &bindings_;
   edges_.push_back(edge);
   return edge;
@@ -146,4 +198,14 @@
            node->status_known() ? (node->dirty() ? "dirty" : "clean")
                                 : "unknown");
   }
+  if (!pools_.empty()) {
+    printf("resource_pools:\n");
+    for (map<string, Pool*>::const_iterator it = pools_.begin();
+         it != pools_.end(); ++it)
+    {
+      if (!it->second->name().empty()) {
+        it->second->Dump();
+      }
+    }
+  }
 }
diff --git a/src/state.h b/src/state.h
index 026acf3..918fe09 100644
--- a/src/state.h
+++ b/src/state.h
@@ -16,6 +16,8 @@
 #define NINJA_STATE_H_
 
 #include <map>
+#include <deque>
+#include <set>
 #include <string>
 #include <vector>
 using namespace std;
@@ -27,8 +29,59 @@
 struct Node;
 struct Rule;
 
+/// A pool for delayed edges.
+/// Pools are scoped to a State. Edges within a State will share Pools. A Pool
+/// will keep a count of the total 'weight' of the currently scheduled edges. If
+/// a Plan attempts to schedule an Edge which would cause the total weight to
+/// exceed the depth of the Pool, the Pool will enque the Edge instead of
+/// allowing the Plan to schedule it. The Pool will relinquish queued Edges when
+/// the total scheduled weight diminishes enough (i.e. when a scheduled edge
+/// completes).
+struct Pool {
+  explicit Pool(const string& name, int depth)
+    : name_(name), current_use_(0), depth_(depth) { }
+
+  // A depth of 0 is infinite
+  bool is_valid() const { return depth_ >= 0; }
+  int depth() const { return depth_; }
+  const string& name() const { return name_; }
+
+  /// true if the Pool might delay this edge
+  bool ShouldDelayEdge() const { return depth_ != 0; }
+
+  /// informs this Pool that the given edge is committed to be run.
+  /// Pool will count this edge as using resources from this pool.
+  void EdgeScheduled(const Edge& edge);
+
+  /// informs this Pool that the given edge is no longer runnable, and should
+  /// relinquish its resources back to the pool
+  void EdgeFinished(const Edge& edge);
+
+  /// adds the given edge to this Pool to be delayed.
+  void DelayEdge(Edge* edge);
+
+  /// Pool will add zero or more edges to the ready_queue
+  void RetrieveReadyEdges(set<Edge*>* ready_queue);
+
+  /// Dump the Pool and its edges (useful for debugging).
+  void Dump() const;
+
+private:
+  int UnitsWaiting() { return delayed_.size(); }
+
+  string name_;
+
+  /// |current_use_| is the total of the weights of the edges which are
+  /// currently scheduled in the Plan (i.e. the edges in Plan::ready_).
+  int current_use_;
+  int depth_;
+
+  deque<Edge*> delayed_;
+};
+
 /// Global state (file status, loaded rules) for a single run.
 struct State {
+  static Pool kDefaultPool;
   static const Rule kPhonyRule;
 
   State();
@@ -36,7 +89,10 @@
   void AddRule(const Rule* rule);
   const Rule* LookupRule(const string& rule_name);
 
-  Edge* AddEdge(const Rule* rule);
+  void AddPool(Pool* pool);
+  Pool* LookupPool(const string& pool_name);
+
+  Edge* AddEdge(const Rule* rule, Pool* pool);
 
   Node* GetNode(StringPiece path);
   Node* LookupNode(StringPiece path);
@@ -50,7 +106,7 @@
   /// state where we haven't yet examined the disk for dirty state.
   void Reset();
 
-  /// Dump the nodes (useful for debugging).
+  /// Dump the nodes and Pools (useful for debugging).
   void Dump();
 
   /// @return the root node(s) of the graph. (Root nodes have no output edges).
@@ -65,6 +121,9 @@
   /// All the rules used in the graph.
   map<string, const Rule*> rules_;
 
+  /// All the pools used in the graph.
+  map<string, Pool*> pools_;
+
   /// All the edges of the graph.
   vector<Edge*> edges_;
 
diff --git a/src/state_test.cc b/src/state_test.cc
index bc24edd..26177ff 100644
--- a/src/state_test.cc
+++ b/src/state_test.cc
@@ -32,7 +32,7 @@
   rule->set_command(command);
   state.AddRule(rule);
 
-  Edge* edge = state.AddEdge(rule);
+  Edge* edge = state.AddEdge(rule, &State::kDefaultPool);
   state.AddIn(edge, "in1");
   state.AddIn(edge, "in2");
   state.AddOut(edge, "out");
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index 1c47fd1..8f1a04e 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -76,7 +76,7 @@
         break;
 
       // Open /dev/null over stdin.
-      int devnull = open("/dev/null", O_WRONLY);
+      int devnull = open("/dev/null", O_RDONLY);
       if (devnull < 0)
         break;
       if (dup2(devnull, 0) < 0)
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index 4b103a5..1b230b6 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -101,14 +101,17 @@
                       NULL, NULL,
                       &startup_info, &process_info)) {
     DWORD error = GetLastError();
-    if (error == ERROR_FILE_NOT_FOUND) { // file (program) not found error is treated as a normal build action failure
+    if (error == ERROR_FILE_NOT_FOUND) {
+      // File (program) not found error is treated as a normal build
+      // action failure.
       if (child_pipe)
         CloseHandle(child_pipe);
       CloseHandle(pipe_);
       CloseHandle(nul);
       pipe_ = NULL;
       // child_ is already NULL;
-      buf_ = "CreateProcess failed: The system cannot find the file specified.\n";
+      buf_ = "CreateProcess failed: The system cannot find the file "
+          "specified.\n";
       return true;
     } else {
       Win32Fatal("CreateProcess");    // pass all other errors to Win32Fatal
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index d89525e..c3175da 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -64,7 +64,8 @@
   EXPECT_EQ(ExitFailure, subproc->Finish());
   EXPECT_NE("", subproc->GetOutput());
 #ifdef _WIN32
-  ASSERT_EQ("CreateProcess failed: The system cannot find the file specified.\n", subproc->GetOutput());
+  ASSERT_EQ("CreateProcess failed: The system cannot find the file "
+            "specified.\n", subproc->GetOutput());
 #endif
 }
 
@@ -179,3 +180,18 @@
   ASSERT_EQ(kNumProcs, subprocs_.finished_.size());
 }
 #endif  // linux
+
+// TODO: this test could work on Windows, just not sure how to simply
+// read stdin.
+#ifndef _WIN32
+// Verify that a command that attempts to read stdin correctly thinks
+// that stdin is closed.
+TEST_F(SubprocessTest, ReadStdin) {
+  Subprocess* subproc = subprocs_.Add("cat -");
+  while (!subproc->Done()) {
+    subprocs_.DoWork();
+  }
+  ASSERT_EQ(ExitSuccess, subproc->Finish());
+  ASSERT_EQ(1u, subprocs_.finished_.size());
+}
+#endif  // _WIN32
diff --git a/src/util.cc b/src/util.cc
index 0feb99d..4b2900f 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -155,7 +155,7 @@
     }
 
     if (component_count == kMaxPathComponents)
-      Fatal("path has too many components");
+      Fatal("path has too many components : %s", path);
     components[component_count] = dst;
     ++component_count;
 
diff --git a/src/util.h b/src/util.h
index 6c142c6..2b59283 100644
--- a/src/util.h
+++ b/src/util.h
@@ -49,7 +49,8 @@
 
 /// Given a misspelled string and a list of correct spellings, returns
 /// the closest match or NULL if there is no close enough match.
-const char* SpellcheckStringV(const string& text, const vector<const char*>& words);
+const char* SpellcheckStringV(const string& text,
+                              const vector<const char*>& words);
 
 /// Like SpellcheckStringV, but takes a NULL-terminated list.
 const char* SpellcheckString(const string& text, ...);