Merge branch 'master' into release
diff --git a/.clang-format b/.clang-format
index 1841c03..b8e9225 100644
--- a/.clang-format
+++ b/.clang-format
@@ -23,3 +23,4 @@
 ConstructorInitializerAllOnOneLineOrOnePerLine: false
 Cpp11BracedListStyle: false
 IndentCaseLabels: false
+DerivePointerBinding: false
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 2febee2..71cd06e 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -12,25 +12,29 @@
     container:
       image: centos:7
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
     - name: Install dependencies
       run: |
-        curl -L -O https://github.com/Kitware/CMake/releases/download/v3.16.2/cmake-3.16.2-Linux-x86_64.sh
-        chmod +x cmake-3.16.2-Linux-x86_64.sh
-        ./cmake-3.16.2-Linux-x86_64.sh --skip-license --prefix=/usr/local
+        curl -L -O https://github.com/Kitware/CMake/releases/download/v3.16.4/cmake-3.16.4-Linux-x86_64.sh
+        chmod +x cmake-3.16.4-Linux-x86_64.sh
+        ./cmake-3.16.4-Linux-x86_64.sh --skip-license --prefix=/usr/local
         curl -L -O https://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/7/x86_64/Packages/p/p7zip-16.02-10.el7.x86_64.rpm
         curl -L -O https://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/7/x86_64/Packages/p/p7zip-plugins-16.02-10.el7.x86_64.rpm
         rpm -U --quiet p7zip-16.02-10.el7.x86_64.rpm
         rpm -U --quiet p7zip-plugins-16.02-10.el7.x86_64.rpm
         yum install -y make gcc-c++
+
     - name: Build ninja
       shell: bash
       run: |
-        mkdir build && cd build
-        cmake -DCMAKE_BUILD_TYPE=Release ..
-        cmake --build . --parallel --config Release
-        ctest -vv
-        strip ninja
+        cmake -DCMAKE_BUILD_TYPE=Release -B build
+        cmake --build build --parallel --config Release
+        strip build/ninja
+
+    - name: Test ninja
+      run: ./ninja_test
+      working-directory: build
+
     - name: Create ninja archive
       run: |
         mkdir artifact
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 2a7c100..411cfe1 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -11,18 +11,22 @@
     runs-on: macOS-latest
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
 
     - name: Install dependencies
       run: brew install re2c p7zip cmake
 
     - name: Build ninja
       shell: bash
+      env:
+        MACOSX_DEPLOYMENT_TARGET: 10.12
       run: |
-        mkdir build && cd build
-        cmake -DCMAKE_BUILD_TYPE=Release ..
-        cmake --build . --parallel --config Release
-        ctest -vv
+        cmake -DCMAKE_BUILD_TYPE=Release -B build
+        cmake --build build --parallel --config Release
+
+    - name: Test ninja
+      run: ctest -vv
+      working-directory: build
 
     - name: Create ninja archive
       shell: bash
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index bdec6c9..04fc2f6 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -11,7 +11,7 @@
     runs-on: windows-latest
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
 
     - name: Install dependencies
       run: choco install re2c
@@ -19,10 +19,12 @@
     - name: Build ninja
       shell: bash
       run: |
-        mkdir build && cd build
-        cmake -DCMAKE_BUILD_TYPE=Release ..
-        cmake --build . --parallel --config Release
-        ctest -vv
+        cmake -DCMAKE_BUILD_TYPE=Release -B build
+        cmake --build build --parallel --config Release
+
+    - name: Test ninja
+      run: .\ninja_test.exe
+      working-directory: build/Release
 
     - name: Create ninja archive
       shell: bash
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 60fd8a1..65e42a4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,9 +1,8 @@
 cmake_minimum_required(VERSION 3.15)
-cmake_policy(SET CMP0091 NEW)
 project(ninja)
 
+# --- optional link-time optimization
 if(CMAKE_BUILD_TYPE MATCHES "Release")
-	cmake_policy(SET CMP0069 NEW)
 	include(CheckIPOSupported)
 	check_ipo_supported(RESULT lto_supported OUTPUT error)
 
@@ -15,13 +14,23 @@
 	endif()
 endif()
 
+# --- compiler flags
 if(MSVC)
 	set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
-	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /GR- /Zc:__cplusplus")
+	string(APPEND CMAKE_CXX_FLAGS " /W4 /GR- /Zc:__cplusplus")
 else()
-	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated -fdiagnostics-color")
+	include(CheckCXXCompilerFlag)
+	check_cxx_compiler_flag(-Wno-deprecated flag_no_deprecated)
+	if(flag_no_deprecated)
+		string(APPEND CMAKE_CXX_FLAGS " -Wno-deprecated")
+	endif()
+	check_cxx_compiler_flag(-fdiagnostics-color flag_color_diag)
+	if(flag_color_diag)
+		string(APPEND CMAKE_CXX_FLAGS " -fdiagnostics-color")
+	endif()
 endif()
 
+# --- optional re2c
 find_program(RE2C re2c)
 if(RE2C)
 	# the depfile parser and ninja lexers are generated using re2c.
@@ -30,9 +39,9 @@
 			COMMAND ${RE2C} -b -i --no-generation-date -o ${OUT} ${IN}
 		)
 	endfunction()
-	re2c(${CMAKE_SOURCE_DIR}/src/depfile_parser.in.cc ${CMAKE_BINARY_DIR}/depfile_parser.cc)
-	re2c(${CMAKE_SOURCE_DIR}/src/lexer.in.cc ${CMAKE_BINARY_DIR}/lexer.cc)
-	add_library(libninja-re2c OBJECT ${CMAKE_BINARY_DIR}/depfile_parser.cc ${CMAKE_BINARY_DIR}/lexer.cc)
+	re2c(${PROJECT_SOURCE_DIR}/src/depfile_parser.in.cc ${PROJECT_BINARY_DIR}/depfile_parser.cc)
+	re2c(${PROJECT_SOURCE_DIR}/src/lexer.in.cc ${PROJECT_BINARY_DIR}/lexer.cc)
+	add_library(libninja-re2c OBJECT ${PROJECT_BINARY_DIR}/depfile_parser.cc ${PROJECT_BINARY_DIR}/lexer.cc)
 else()
 	message(WARNING "re2c was not found; changes to src/*.in.cc will not affect your build.")
 	add_library(libninja-re2c OBJECT src/depfile_parser.cc src/lexer.cc)
@@ -127,3 +136,5 @@
 
 enable_testing()
 add_test(NinjaTest ninja_test)
+
+install(TARGETS ninja DESTINATION bin)
diff --git a/README.md b/README.md
index 3326f81..d11fd33 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@
 completion and Emacs and Vim editing modes, some files in misc/ must be
 copied to appropriate locations.
 
-If you're interested in making changes to Ninja, read CONTRIBUTING.md first.
+If you're interested in making changes to Ninja, read
+[CONTRIBUTING.md](CONTRIBUTING.md) first.
 
 ## Building Ninja itself
 
@@ -31,7 +32,7 @@
 ```
 
 This will generate the `ninja` binary and a `build.ninja` file you can now use
-to built Ninja with itself.
+to build Ninja with itself.
 
 ### CMake
 
diff --git a/bootstrap.py b/bootstrap.py
deleted file mode 100755
index 56eab64..0000000
--- a/bootstrap.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2011 Google Inc. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from __future__ import print_function
-
-import subprocess
-import sys
-
-print('DEPRECATED: this script will be deleted.')
-print('use "configure.py --bootstrap" instead.')
-subprocess.check_call([sys.executable, 'configure.py', '--bootstrap'])
diff --git a/configure.py b/configure.py
index 7d8ce90..8eef7e6 100755
--- a/configure.py
+++ b/configure.py
@@ -649,7 +649,7 @@
        command='$doxygen_mainpage_generator $in > $out',
        description='DOXYGEN_MAINPAGE $out')
 mainpage = n.build(built('doxygen_mainpage'), 'doxygen_mainpage',
-                   ['README', 'COPYING'],
+                   ['README.md', 'COPYING'],
                    implicit=['$doxygen_mainpage_generator'])
 n.build('doxygen', 'doxygen', doc('doxygen.config'),
         implicit=mainpage)
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index 37e1fb5..23f23e0 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -273,7 +273,7 @@
 implies +-v+).
 
 `cleandead`:: remove files produced by previous builds that are no longer in the
-manifest. _Available since Ninja 1.10._
+build file. _Available since Ninja 1.10._
 
 `compdb`:: given a list of rules, each of which is expected to be a
 C family language compiler rule whose first input is the name of the
@@ -900,7 +900,7 @@
 On Windows, commands are strings, so Ninja passes the `command` string
 directly to `CreateProcess`.  (In the common case of simply executing
 a compiler this means there is less overhead.)  Consequently the
-quoting rules are deterimined by the called program, which on Windows
+quoting rules are determined by the called program, which on Windows
 are usually provided by the C library.  If you need shell
 interpretation of the command (such as the use of `&&` to chain
 multiple commands), make the command execute the Windows shell by
@@ -936,7 +936,7 @@
 
 1. _Explicit dependencies_, as listed in a build line.  These are
    available as the `$in` variable in the rule.  Changes in these files
-   cause the output to be rebuilt; if these file are missing and
+   cause the output to be rebuilt; if these files are missing and
    Ninja doesn't know how to build them, the build is aborted.
 +
 This is the standard form of dependency to be used e.g. for the
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index ebe6490..ab5c0d4 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -1,5 +1,19 @@
 #!/usr/bin/python
 
+# Copyright 2011 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 """Python module for generating .ninja files.
 
 Note that this is emphatically not a required piece of Ninja; it's
diff --git a/misc/output_test.py b/misc/output_test.py
index 3fd9c32..b63520f 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -46,6 +46,7 @@
         final_output += line.replace('\r', '')
     return final_output
 
+@unittest.skipIf(platform.system() == 'Windows', 'These test methods do not work on Windows')
 class Output(unittest.TestCase):
     def test_issue_1418(self):
         self.assertEqual(run(
@@ -107,5 +108,8 @@
         self.assertEqual(run('', flags='-t recompact'), '')
         self.assertEqual(run('', flags='-t restat'), '')
 
+    def test_status(self):
+        self.assertEqual(run(''), 'ninja: no work to do.\n')
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/misc/packaging/ninja.spec b/misc/packaging/ninja.spec
index 05f5a07..36e5181 100644
--- a/misc/packaging/ninja.spec
+++ b/misc/packaging/ninja.spec
@@ -32,7 +32,7 @@
 
 %files
 %defattr(-, root, root)
-%doc COPYING README doc/manual.html
+%doc COPYING README.md doc/manual.html
 %{_bindir}/*
 
 %clean
diff --git a/src/browse.py b/src/browse.py
index 1c9c39b..653cbe9 100755
--- a/src/browse.py
+++ b/src/browse.py
@@ -29,12 +29,15 @@
     import BaseHTTPServer as httpserver
     import SocketServer as socketserver
 import argparse
-import cgi
 import os
 import socket
 import subprocess
 import sys
 import webbrowser
+if sys.version_info >= (3, 2):
+    from html import escape
+else:
+    from cgi import escape
 try:
     from urllib.request import unquote
 except ImportError:
@@ -62,7 +65,7 @@
     return (True, line[len(prefix):])
 
 def html_escape(text):
-    return cgi.escape(text, quote=True)
+    return escape(text, quote=True)
 
 def parse(text):
     lines = iter(text.split('\n'))
diff --git a/src/build.cc b/src/build.cc
index cd8df4e..db28e65 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -183,7 +183,7 @@
   // it considers a portion of the graph to be out of date.  Normally
   // this is done before the build starts, but our caller is about to
   // load a dyndep file during the build.  Doing so may generate more
-  // exlanation lines (via fprintf directly to stderr), but in an
+  // explanation lines (via fprintf directly to stderr), but in an
   // interactive console the cursor is currently at the end of a status
   // line.  Start a new line so that the first explanation does not
   // append to the status line.  After the explanations are done a
@@ -1033,7 +1033,7 @@
   }
 
   if (!deps_type.empty() && !config_.dry_run) {
-    assert(edge->outputs_.size() >= 1 && "should have been rejected by parser");
+    assert(!edge->outputs_.empty() && "should have been rejected by parser");
     for (std::vector<Node*>::const_iterator o = edge->outputs_.begin();
          o != edge->outputs_.end(); ++o) {
       TimeStamp deps_mtime = disk_interface_->Stat((*o)->path(), err);
diff --git a/src/build_log.cc b/src/build_log.cc
index 98543b6..e5f179c 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -23,6 +23,7 @@
 #include "build_log.h"
 #include "disk_interface.h"
 
+#include <cassert>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
@@ -132,25 +133,9 @@
       return false;
   }
 
-  log_file_ = fopen(path.c_str(), "ab");
-  if (!log_file_) {
-    *err = strerror(errno);
-    return false;
-  }
-  setvbuf(log_file_, NULL, _IOLBF, BUFSIZ);
-  SetCloseOnExec(fileno(log_file_));
-
-  // Opening a file in append mode doesn't set the file pointer to the file's
-  // end on Windows. Do that explicitly.
-  fseek(log_file_, 0, SEEK_END);
-
-  if (ftell(log_file_) == 0) {
-    if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) {
-      *err = strerror(errno);
-      return false;
-    }
-  }
-
+  assert(!log_file_);
+  log_file_path_ = path;  // we don't actually open the file right now, but will
+                          // do so on the first write attempt
   return true;
 }
 
@@ -174,6 +159,9 @@
     log_entry->end_time = end_time;
     log_entry->mtime = mtime;
 
+    if (!OpenForWriteIfNeeded()) {
+      return false;
+    }
     if (log_file_) {
       if (!WriteEntry(log_file_, *log_entry))
         return false;
@@ -186,11 +174,36 @@
 }
 
 void BuildLog::Close() {
+  OpenForWriteIfNeeded();  // create the file even if nothing has been recorded
   if (log_file_)
     fclose(log_file_);
   log_file_ = NULL;
 }
 
+bool BuildLog::OpenForWriteIfNeeded() {
+  if (log_file_path_.empty()) {
+    return true;
+  }
+  log_file_ = fopen(log_file_path_.c_str(), "ab");
+  if (!log_file_) {
+    return false;
+  }
+  setvbuf(log_file_, NULL, _IOLBF, BUFSIZ);
+  SetCloseOnExec(fileno(log_file_));
+
+  // Opening a file in append mode doesn't set the file pointer to the file's
+  // end on Windows. Do that explicitly.
+  fseek(log_file_, 0, SEEK_END);
+
+  if (ftell(log_file_) == 0) {
+    if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) {
+      return false;
+    }
+  }
+  log_file_path_.clear();
+  return true;
+}
+
 struct LineReader {
   explicit LineReader(FILE* file)
     : file_(file), buf_end_(buf_), line_start_(buf_), line_end_(NULL) {
diff --git a/src/build_log.h b/src/build_log.h
index ebe0530..6d060d1 100644
--- a/src/build_log.h
+++ b/src/build_log.h
@@ -45,7 +45,10 @@
   BuildLog();
   ~BuildLog();
 
+  /// Prepares writing to the log file without actually opening it - that will
+  /// happen when/if it's needed
   bool OpenForWrite(const string& path, const BuildLogUser& user, string* err);
+
   bool RecordCommand(Edge* edge, int start_time, int end_time,
                      TimeStamp mtime = 0);
   void Close();
@@ -91,8 +94,13 @@
   const Entries& entries() const { return entries_; }
 
  private:
+  /// Should be called before using log_file_. When false is returned, errno
+  /// will be set.
+  bool OpenForWriteIfNeeded();
+
   Entries entries_;
   FILE* log_file_;
+  std::string log_file_path_;
   bool needs_recompaction_;
 };
 
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index a8b1733..c81f570 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -252,7 +252,7 @@
   ASSERT_EQ(3, e->mtime);
 
   TestDiskInterface testDiskInterface;
-  char out2[] = { 'o', 'u', 't', '2' };
+  char out2[] = { 'o', 'u', 't', '2', 0 };
   char* filter2[] = { out2 };
   EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 1, filter2, &err));
   ASSERT_EQ("", err);
diff --git a/src/build_test.cc b/src/build_test.cc
index 426e825..12c3383 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -672,7 +672,7 @@
     bool verify_active_edge_found = false;
     for (vector<Edge*>::iterator i = active_edges_.begin();
          i != active_edges_.end(); ++i) {
-      if ((*i)->outputs_.size() >= 1 &&
+      if (!(*i)->outputs_.empty() &&
           (*i)->outputs_[0]->path() == verify_active_edge) {
         verify_active_edge_found = true;
       }
diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc
index 90d4a8a..0b7dce1 100644
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -1,4 +1,4 @@
-/* Generated by re2c 1.1.1 */
+/* Generated by re2c 1.3 */
 // Copyright 2011 Google Inc. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -166,22 +166,23 @@
       goto yy5;
 yy13:
       yych = *(yymarker = ++in);
-      if (yych <= 0x1F) {
+      if (yych <= ' ') {
         if (yych <= '\n') {
           if (yych <= 0x00) goto yy5;
           if (yych <= '\t') goto yy16;
           goto yy17;
         } else {
           if (yych == '\r') goto yy19;
-          goto yy16;
+          if (yych <= 0x1F) goto yy16;
+          goto yy21;
         }
       } else {
-        if (yych <= '#') {
-          if (yych <= ' ') goto yy21;
-          if (yych <= '"') goto yy16;
-          goto yy23;
+        if (yych <= '9') {
+          if (yych == '#') goto yy23;
+          goto yy16;
         } else {
-          if (yych == '\\') goto yy25;
+          if (yych <= ':') goto yy25;
+          if (yych == '\\') goto yy27;
           goto yy16;
         }
       }
@@ -231,26 +232,63 @@
       }
 yy25:
       yych = *++in;
-      if (yych <= 0x1F) {
+      if (yych <= '\f') {
+        if (yych <= 0x00) goto yy28;
+        if (yych <= 0x08) goto yy26;
+        if (yych <= '\n') goto yy28;
+      } else {
+        if (yych <= '\r') goto yy28;
+        if (yych == ' ') goto yy28;
+      }
+yy26:
+      {
+        // De-escape colon sign, but preserve other leading backslashes.
+        // Regular expression uses lookahead to make sure that no whitespace
+        // nor EOF follows. In that case it'd be the : at the end of a target
+        int len = (int)(in - start);
+        if (len > 2 && out < start)
+          memset(out, '\\', len - 2);
+        out += len - 2;
+        *out++ = ':';
+        continue;
+      }
+yy27:
+      yych = *++in;
+      if (yych <= ' ') {
         if (yych <= '\n') {
           if (yych <= 0x00) goto yy11;
           if (yych <= '\t') goto yy16;
           goto yy11;
         } else {
           if (yych == '\r') goto yy11;
-          goto yy16;
+          if (yych <= 0x1F) goto yy16;
+          goto yy30;
         }
       } else {
-        if (yych <= '#') {
-          if (yych <= ' ') goto yy26;
-          if (yych <= '"') goto yy16;
-          goto yy23;
+        if (yych <= '9') {
+          if (yych == '#') goto yy23;
+          goto yy16;
         } else {
-          if (yych == '\\') goto yy28;
+          if (yych <= ':') goto yy25;
+          if (yych == '\\') goto yy32;
           goto yy16;
         }
       }
-yy26:
+yy28:
+      ++in;
+      {
+        // Backslash followed by : and whitespace.
+        // It is therefore normal text and not an escaped colon
+        int len = (int)(in - start - 1);
+        // Need to shift it over if we're overwriting backslashes.
+        if (out < start)
+          memmove(out, start, len);
+        out += len;
+        if (*(in - 1) == '\n')
+          have_newline = true;
+        break;
+      }
+yy30:
       ++in;
       {
         // 2N backslashes plus space -> 2N backslashes, end of filename.
@@ -260,24 +298,25 @@
         out += len - 1;
         break;
       }
-yy28:
+yy32:
       yych = *++in;
-      if (yych <= 0x1F) {
+      if (yych <= ' ') {
         if (yych <= '\n') {
           if (yych <= 0x00) goto yy11;
           if (yych <= '\t') goto yy16;
           goto yy11;
         } else {
           if (yych == '\r') goto yy11;
-          goto yy16;
+          if (yych <= 0x1F) goto yy16;
+          goto yy21;
         }
       } else {
-        if (yych <= '#') {
-          if (yych <= ' ') goto yy21;
-          if (yych <= '"') goto yy16;
-          goto yy23;
+        if (yych <= '9') {
+          if (yych == '#') goto yy23;
+          goto yy16;
         } else {
-          if (yych == '\\') goto yy25;
+          if (yych <= ':') goto yy25;
+          if (yych == '\\') goto yy27;
           goto yy16;
         }
       }
diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc
index b32b942..95b4346 100644
--- a/src/depfile_parser.in.cc
+++ b/src/depfile_parser.in.cc
@@ -103,6 +103,29 @@
         *out++ = '#';
         continue;
       }
+      '\\'+ ':' [\x00\x20\r\n\t] {
+        // Backslash followed by : and whitespace.
+        // It is therefore normal text and not an escaped colon
+        int len = (int)(in - start - 1);
+        // Need to shift it over if we're overwriting backslashes.
+        if (out < start)
+          memmove(out, start, len);
+        out += len;
+        if (*(in - 1) == '\n')
+          have_newline = true;
+        break;
+      }
+      '\\'+ ':' {
+        // De-escape colon sign, but preserve other leading backslashes.
+        // Regular expression uses lookahead to make sure that no whitespace
+        // nor EOF follows. In that case it'd be the : at the end of a target
+        int len = (int)(in - start);
+        if (len > 2 && out < start)
+          memset(out, '\\', len - 2);
+        out += len - 2;
+        *out++ = ':';
+        continue;
+      }
       '$$' {
         // De-escape dollar character.
         *out++ = '$';
diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc
index bf1a0bc..8e2cd25 100644
--- a/src/depfile_parser_test.cc
+++ b/src/depfile_parser_test.cc
@@ -142,6 +142,41 @@
   ASSERT_EQ(0u, parser_.ins_.size());
 }
 
+TEST_F(DepfileParserTest, EscapedColons)
+{
+  std::string err;
+  // Tests for correct parsing of depfiles produced on Windows
+  // by both Clang, GCC pre 10 and GCC 10
+  EXPECT_TRUE(Parse(
+"c\\:\\gcc\\x86_64-w64-mingw32\\include\\stddef.o: \\\n"
+" c:\\gcc\\x86_64-w64-mingw32\\include\\stddef.h \n",
+      &err));
+  ASSERT_EQ("", err);
+  ASSERT_EQ(1u, parser_.outs_.size());
+  EXPECT_EQ("c:\\gcc\\x86_64-w64-mingw32\\include\\stddef.o",
+            parser_.outs_[0].AsString());
+  ASSERT_EQ(1u, parser_.ins_.size());
+  EXPECT_EQ("c:\\gcc\\x86_64-w64-mingw32\\include\\stddef.h",
+            parser_.ins_[0].AsString());
+}
+
+TEST_F(DepfileParserTest, EscapedTargetColon)
+{
+  std::string err;
+  EXPECT_TRUE(Parse(
+"foo1\\: x\n"
+"foo1\\:\n"
+"foo1\\:\r\n"
+"foo1\\:\t\n"
+"foo1\\:",
+      &err));
+  ASSERT_EQ("", err);
+  ASSERT_EQ(1u, parser_.outs_.size());
+  EXPECT_EQ("foo1\\", parser_.outs_[0].AsString());
+  ASSERT_EQ(1u, parser_.ins_.size());
+  EXPECT_EQ("x", parser_.ins_[0].AsString());
+}
+
 TEST_F(DepfileParserTest, SpecialChars) {
   // See filenames like istreambuf.iterator_op!= in
   // https://github.com/google/libcxx/tree/master/test/iterators/stream.iterators/istreambuf.iterator/
diff --git a/src/deps_log.cc b/src/deps_log.cc
index cf55194..1fb65ae 100644
--- a/src/deps_log.cc
+++ b/src/deps_log.cc
@@ -49,34 +49,9 @@
       return false;
   }
 
-  file_ = fopen(path.c_str(), "ab");
-  if (!file_) {
-    *err = strerror(errno);
-    return false;
-  }
-  // Set the buffer size to this and flush the file buffer after every record
-  // to make sure records aren't written partially.
-  setvbuf(file_, NULL, _IOFBF, kMaxRecordSize + 1);
-  SetCloseOnExec(fileno(file_));
-
-  // Opening a file in append mode doesn't set the file pointer to the file's
-  // end on Windows. Do that explicitly.
-  fseek(file_, 0, SEEK_END);
-
-  if (ftell(file_) == 0) {
-    if (fwrite(kFileSignature, sizeof(kFileSignature) - 1, 1, file_) < 1) {
-      *err = strerror(errno);
-      return false;
-    }
-    if (fwrite(&kCurrentVersion, 4, 1, file_) < 1) {
-      *err = strerror(errno);
-      return false;
-    }
-  }
-  if (fflush(file_) != 0) {
-    *err = strerror(errno);
-    return false;
-  }
+  assert(!file_);
+  file_path_ = path;  // we don't actually open the file right now, but will do
+                      // so on the first write attempt
   return true;
 }
 
@@ -132,6 +107,10 @@
     errno = ERANGE;
     return false;
   }
+
+  if (!OpenForWriteIfNeeded()) {
+    return false;
+  }
   size |= 0x80000000;  // Deps record: set high bit.
   if (fwrite(&size, 4, 1, file_) < 1)
     return false;
@@ -162,6 +141,7 @@
 }
 
 void DepsLog::Close() {
+  OpenForWriteIfNeeded();  // create the file even if nothing has been recorded
   if (file_)
     fclose(file_);
   file_ = NULL;
@@ -396,10 +376,14 @@
     errno = ERANGE;
     return false;
   }
+
+  if (!OpenForWriteIfNeeded()) {
+    return false;
+  }
   if (fwrite(&size, 4, 1, file_) < 1)
     return false;
   if (fwrite(node->path().data(), path_size, 1, file_) < 1) {
-    assert(node->path().size() > 0);
+    assert(!node->path().empty());
     return false;
   }
   if (padding && fwrite("\0\0", padding, 1, file_) < 1)
@@ -416,3 +400,35 @@
 
   return true;
 }
+
+bool DepsLog::OpenForWriteIfNeeded() {
+  if (file_path_.empty()) {
+    return true;
+  }
+  file_ = fopen(file_path_.c_str(), "ab");
+  if (!file_) {
+    return false;
+  }
+  // Set the buffer size to this and flush the file buffer after every record
+  // to make sure records aren't written partially.
+  setvbuf(file_, NULL, _IOFBF, kMaxRecordSize + 1);
+  SetCloseOnExec(fileno(file_));
+
+  // Opening a file in append mode doesn't set the file pointer to the file's
+  // end on Windows. Do that explicitly.
+  fseek(file_, 0, SEEK_END);
+
+  if (ftell(file_) == 0) {
+    if (fwrite(kFileSignature, sizeof(kFileSignature) - 1, 1, file_) < 1) {
+      return false;
+    }
+    if (fwrite(&kCurrentVersion, 4, 1, file_) < 1) {
+      return false;
+    }
+  }
+  if (fflush(file_) != 0) {
+    return false;
+  }
+  file_path_.clear();
+  return true;
+}
diff --git a/src/deps_log.h b/src/deps_log.h
index e7974a1..c4ada8b 100644
--- a/src/deps_log.h
+++ b/src/deps_log.h
@@ -110,8 +110,13 @@
   // Write a node name record, assigning it an id.
   bool RecordId(Node* node);
 
+  /// Should be called before using file_. When false is returned, errno will
+  /// be set.
+  bool OpenForWriteIfNeeded();
+
   bool needs_recompaction_;
   FILE* file_;
+  std::string file_path_;
 
   /// Maps id -> Node.
   vector<Node*> nodes_;
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index dc297c4..594bc51 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -26,6 +26,8 @@
 #include <sstream>
 #include <windows.h>
 #include <direct.h>  // _mkdir
+#else
+#include <unistd.h>
 #endif
 
 #include "metrics.h"
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index bac515d..866d1d6 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -190,7 +190,7 @@
 
 TEST_F(DiskInterfaceTest, MakeDirs) {
   string path = "path/with/double//slash/";
-  EXPECT_TRUE(disk_.MakeDirs(path.c_str()));
+  EXPECT_TRUE(disk_.MakeDirs(path));
   FILE* f = fopen((path + "a_file").c_str(), "w");
   EXPECT_TRUE(f);
   EXPECT_EQ(0, fclose(f));
diff --git a/src/ninja.cc b/src/ninja.cc
index 1429639..00e3a5c 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -91,7 +91,7 @@
   /// Loaded state (rules, nodes).
   State state_;
 
-  /// Functions for accesssing the disk.
+  /// Functions for accessing the disk.
   RealDiskInterface disk_interface_;
 
   /// The build directory, used for storing the build log etc.
@@ -1050,7 +1050,7 @@
   }
 }
 
-/// Set a warning flag.  Returns false if Ninja should exit instead  of
+/// Set a warning flag.  Returns false if Ninja should exit instead of
 /// continuing.
 bool WarningEnable(const string& name, Options* options) {
   if (name == "list") {
diff --git a/src/string_piece_util.cc b/src/string_piece_util.cc
index 8e1ecfd..69513f5 100644
--- a/src/string_piece_util.cc
+++ b/src/string_piece_util.cc
@@ -39,7 +39,7 @@
 }
 
 string JoinStringPiece(const vector<StringPiece>& list, char sep) {
-  if (list.size() == 0){
+  if (list.empty()) {
     return "";
   }
 
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index fc5543e..74785d1 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -18,13 +18,18 @@
 #include <assert.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <poll.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/wait.h>
 #include <spawn.h>
 
+#if defined(USE_PPOLL)
+#include <poll.h>
+#else
+#include <sys/select.h>
+#endif
+
 extern char** environ;
 
 #include "util.h"
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index a4a7669..d221476 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -124,12 +124,20 @@
       buf_ = "CreateProcess failed: The system cannot find the file "
           "specified.\n";
       return true;
-    } else if (error == ERROR_INVALID_PARAMETER) {
-      // This generally means that the command line was too long. Give extra
-      // context for this case.
-      Win32Fatal("CreateProcess", "is the command line too long?");
     } else {
-      Win32Fatal("CreateProcess");    // pass all other errors to Win32Fatal
+      fprintf(stderr, "\nCreateProcess failed. Command attempted:\n\"%s\"\n",
+              command.c_str());
+      const char* hint = NULL;
+      // ERROR_INVALID_PARAMETER means the command line was formatted
+      // incorrectly. This can be caused by a command line being too long or
+      // leading whitespace in the command. Give extra context for this case.
+      if (error == ERROR_INVALID_PARAMETER) {
+        if (command.length() > 0 && (command[0] == ' ' || command[0] == '\t'))
+          hint = "command contains leading whitespace";
+        else
+          hint = "is the command line too long?";
+      }
+      Win32Fatal("CreateProcess", hint);
     }
   }
 
diff --git a/src/test.cc b/src/test.cc
index 8ba2297..021005e 100644
--- a/src/test.cc
+++ b/src/test.cc
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 #ifdef _WIN32
 #include <windows.h>
+#include <io.h>
 #else
 #include <unistd.h>
 #endif
@@ -45,19 +46,9 @@
 namespace {
 
 #ifdef _WIN32
-#ifndef _mktemp_s
-/// mingw has no mktemp.  Implement one with the same type as the one
-/// found in the Windows API.
-int _mktemp_s(char* templ) {
-  char* ofs = strchr(templ, 'X');
-  sprintf(ofs, "%d", rand() % 1000000);
-  return 0;
-}
-#endif
-
 /// Windows has no mkdtemp.  Implement it in terms of _mktemp_s.
 char* mkdtemp(char* name_template) {
-  int err = _mktemp_s(name_template);
+  int err = _mktemp_s(name_template, strlen(name_template) + 1);
   if (err < 0) {
     perror("_mktemp_s");
     return NULL;
diff --git a/src/version.cc b/src/version.cc
index 08c0ae9..04dcea5 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -18,7 +18,7 @@
 
 #include "util.h"
 
-const char* kNinjaVersion = "1.10.0";
+const char* kNinjaVersion = "1.10.1";
 
 void ParseVersion(const string& version, int* major, int* minor) {
   size_t end = version.find('.');