Merge pull request #1474 from mathstuf/win32-invalid-parameter-help

Win32 invalid parameter help
diff --git a/HACKING.md b/HACKING.md
index 5c2469b..086940b 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -29,15 +29,15 @@
 Visual Studio.
 
 ##### Using Visual Studio
-Assuming that you now have python installed, then the steps for building under
- Windows using Visual Studio are:
- 
+Assuming that you now have Python installed, then the steps for building under
+Windows using Visual Studio are:
+
 Clone and checkout the latest release (or whatever branch you want). You
 can do this in either a command prompt or by opening a git bash prompt:
 
 ```
-    $ git clone git://github.com/ninja-build/ninja.git && cd ninja 
-    $ git checkout release 
+    $ git clone git://github.com/ninja-build/ninja.git && cd ninja
+    $ git checkout release
 ```
 
 Then:
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index db164e7..7b1c3ba 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -593,7 +593,7 @@
    to its stdout.  Ninja then filters these lines from the displayed
    output.  No `depfile` attribute is necessary, but the localized string
    in front of the the header file path. For instance
-   `msvc_deps_prefix = Note: including file: `
+   `msvc_deps_prefix = Note: including file:`
    for a English Visual Studio (the default). Should be globally defined.
 +
 ----
diff --git a/misc/output_test.py b/misc/output_test.py
index 6a5b635..878de19 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -11,11 +11,14 @@
 import tempfile
 import unittest
 
-def run(build_ninja, flags='', pipe=False):
-    env = dict(os.environ)
-    if 'NINJA_STATUS' in env:
-        del env['NINJA_STATUS']
-    env['TERM'] = ''
+default_env = dict(os.environ)
+if 'NINJA_STATUS' in default_env:
+    del default_env['NINJA_STATUS']
+if 'CLICOLOR_FORCE' in default_env:
+    del default_env['CLICOLOR_FORCE']
+default_env['TERM'] = ''
+
+def run(build_ninja, flags='', pipe=False, env=default_env):
     with tempfile.NamedTemporaryFile('w') as f:
         f.write(build_ninja)
         f.flush()
@@ -84,5 +87,13 @@
 red
 ''')
 
+        # CLICOLOR_FORCE=1 can be used to disable escape code stripping.
+        env = default_env.copy()
+        env['CLICOLOR_FORCE'] = '1'
+        self.assertEqual(run(print_red, pipe=True, env=env),
+'''[1/1] echo a
+\x1b[31mred\x1b[0m
+''')
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/src/browse.cc b/src/browse.cc
index 14900f8..c08c9f4 100644
--- a/src/browse.cc
+++ b/src/browse.cc
@@ -14,6 +14,7 @@
 
 #include "browse.h"
 
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -57,7 +58,11 @@
       }
       command.push_back(NULL);
       execvp(command[0], (char**)&command[0]);
-      perror("ninja: execvp");
+      if (errno == ENOENT) {
+        printf("ninja: %s is required for the browse tool\n", NINJA_PYTHON);
+      } else {
+        perror("ninja: execvp");
+      }
     } while (false);
     _exit(1);
   } else {  // Child.
diff --git a/src/browse.py b/src/browse.py
index 64a16f2..1c9c39b 100755
--- a/src/browse.py
+++ b/src/browse.py
@@ -24,8 +24,10 @@
 
 try:
     import http.server as httpserver
+    import socketserver
 except ImportError:
     import BaseHTTPServer as httpserver
+    import SocketServer as socketserver
 import argparse
 import cgi
 import os
@@ -205,10 +207,14 @@
 parser.add_argument('initial_target', default='all', nargs='?',
     help='Initial target to show (default %(default)s)')
 
+class HTTPServer(socketserver.ThreadingMixIn, httpserver.HTTPServer):
+    # terminate server immediately when Python exits.
+    daemon_threads = True
+
 args = parser.parse_args()
 port = args.port
 hostname = args.hostname
-httpd = httpserver.HTTPServer((hostname,port), RequestHandler)
+httpd = HTTPServer((hostname,port), RequestHandler)
 try:
     if hostname == "":
         hostname = socket.gethostname()
diff --git a/src/build.cc b/src/build.cc
index 6b33024..ed219fd 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -154,7 +154,6 @@
     // (Launching subprocesses in pseudo ttys doesn't work because there are
     // only a few hundred available on some systems, and ninja can launch
     // thousands of parallel compile commands.)
-    // TODO: There should be a flag to disable escape code stripping.
     string final_output;
     if (!printer_.supports_color())
       final_output = StripAnsiEscapeCodes(output);
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index 7351715..d4c2fb0 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -35,14 +35,15 @@
 
 string DirName(const string& path) {
 #ifdef _WIN32
-  const char kPathSeparators[] = "\\/";
+  static const char kPathSeparators[] = "\\/";
 #else
-  const char kPathSeparators[] = "/";
+  static const char kPathSeparators[] = "/";
 #endif
+  static const char* const kEnd = kPathSeparators + sizeof(kPathSeparators) - 1;
+
   string::size_type slash_pos = path.find_last_of(kPathSeparators);
   if (slash_pos == string::npos)
     return string();  // Nothing to do.
-  const char* const kEnd = kPathSeparators + strlen(kPathSeparators);
   while (slash_pos > 0 &&
          std::find(kPathSeparators, kEnd, path[slash_pos - 1]) != kEnd)
     --slash_pos;
diff --git a/src/line_printer.cc b/src/line_printer.cc
index a3a551e..953982a 100644
--- a/src/line_printer.cc
+++ b/src/line_printer.cc
@@ -18,6 +18,9 @@
 #include <stdlib.h>
 #ifdef _WIN32
 #include <windows.h>
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4
+#endif
 #else
 #include <unistd.h>
 #include <sys/ioctl.h>
@@ -42,6 +45,19 @@
   smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
 #endif
   supports_color_ = smart_terminal_;
+  if (!supports_color_) {
+    const char* clicolor_force = getenv("CLICOLOR_FORCE");
+    supports_color_ = clicolor_force && string(clicolor_force) != "0";
+  }
+#ifdef _WIN32
+  // Try enabling ANSI escape sequence support on Windows 10 terminals.
+  if (supports_color_) {
+    DWORD mode;
+    if (GetConsoleMode(console_, &mode)) {
+      SetConsoleMode(console_, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+    }
+  }
+#endif
 }
 
 void LinePrinter::Print(string to_print, LineType type) {
diff --git a/src/ninja.cc b/src/ninja.cc
index 8108f21..1267b03 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -206,11 +206,11 @@
 "  -C DIR   change to DIR before doing anything else\n"
 "  -f FILE  specify input build file [default=build.ninja]\n"
 "\n"
-"  -j N     run N jobs in parallel [default=%d, derived from CPUs available]\n"
+"  -j N     run N jobs in parallel (0 means infinity) [default=%d, derived from CPUs available]\n"
 "  -k N     keep going until N jobs fail (0 means infinity) [default=1]\n"
 "  -l N     do not start new jobs if the load average is greater than N\n"
 "  -n       dry run (don't run commands but act like they succeeded)\n"
-"  -v       show all command lines while building\n"
+"  -v, --verbose  show all command lines while building\n"
 "\n"
 "  -d MODE  enable debugging (use '-d list' to list modes)\n"
 "  -t TOOL  run a subtool (use '-t list' to list subtools)\n"
@@ -387,7 +387,12 @@
   // If we get here, the browse failed.
   return 1;
 }
-#endif  // _WIN32
+#else
+int NinjaMain::ToolBrowse(const Options*, int, char**) {
+  Fatal("browse tool not supported on this platform");
+  return 1;
+}
+#endif
 
 #if defined(_MSC_VER)
 int NinjaMain::ToolMSVC(const Options* options, int argc, char* argv[]) {
@@ -803,10 +808,8 @@
 /// Returns a Tool, or NULL if Ninja should exit.
 const Tool* ChooseTool(const string& tool_name) {
   static const Tool kTools[] = {
-#if defined(NINJA_HAVE_BROWSE)
     { "browse", "browse dependency graph in a web browser",
       Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse },
-#endif
 #if defined(_MSC_VER)
     { "msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)",
       Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolMSVC },
@@ -1102,6 +1105,7 @@
   const option kLongOptions[] = {
     { "help", no_argument, NULL, 'h' },
     { "version", no_argument, NULL, OPT_VERSION },
+    { "verbose", no_argument, NULL, 'v' },
     { NULL, 0, NULL, 0 }
   };
 
@@ -1120,9 +1124,12 @@
       case 'j': {
         char* end;
         int value = strtol(optarg, &end, 10);
-        if (*end != 0 || value <= 0)
+        if (*end != 0 || value < 0)
           Fatal("invalid -j parameter");
-        config->parallelism = value;
+
+        // We want to run N jobs in parallel. For N = 0, INT_MAX
+        // is close enough to infinite for most sane builds.
+        config->parallelism = value > 0 ? value : INT_MAX;
         break;
       }
       case 'k': {
diff --git a/src/util.cc b/src/util.cc
index 8e67fd9..47a5de2 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -346,9 +346,19 @@
     return -errno;
   }
 
+  struct stat st;
+  if (fstat(fileno(f), &st) < 0) {
+    err->assign(strerror(errno));
+    fclose(f);
+    return -errno;
+  }
+
+  // +1 is for the resize in ManifestParser::Load
+  contents->reserve(st.st_size + 1);
+
   char buf[64 << 10];
   size_t len;
-  while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
+  while (!feof(f) && (len = fread(buf, 1, sizeof(buf), f)) > 0) {
     contents->append(buf, len);
   }
   if (ferror(f)) {
@@ -577,7 +587,7 @@
 string ElideMiddle(const string& str, size_t width) {
   const int kMargin = 3;  // Space for "...".
   string result = str;
-  if (result.size() + kMargin > width) {
+  if (result.size() > width) {
     size_t elide_size = (width - kMargin) / 2;
     result = result.substr(0, elide_size)
       + "..."
diff --git a/src/util_test.cc b/src/util_test.cc
index b4b7516..d97b48c 100644
--- a/src/util_test.cc
+++ b/src/util_test.cc
@@ -419,10 +419,12 @@
 TEST(ElideMiddle, NothingToElide) {
   string input = "Nothing to elide in this short string.";
   EXPECT_EQ(input, ElideMiddle(input, 80));
+  EXPECT_EQ(input, ElideMiddle(input, 38));
 }
 
 TEST(ElideMiddle, ElideInTheMiddle) {
   string input = "01234567890123456789";
   string elided = ElideMiddle(input, 10);
   EXPECT_EQ("012...789", elided);
+  EXPECT_EQ("01234567...23456789", ElideMiddle(input, 19));
 }