summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvan Martin <martine@danga.com>2013-06-04 11:46:47 -0700
committerEvan Martin <martine@danga.com>2013-06-04 11:47:31 -0700
commit045d00847b191da46faf1c1b91368a252412bb17 (patch)
treee10dfa89d31abbe5262cf26ac75e240368121691
parent22f60e96a5c8cff37ac6dc11955bff1244dfb109 (diff)
parent0f53fd302b58589626dce073a0cd94061fac1355 (diff)
downloadninja-1.3.4.tar.gz
v1.3.4v1.3.4
-rw-r--r--HACKING.md2
-rw-r--r--RELEASING2
-rwxr-xr-xbootstrap.py6
-rwxr-xr-xconfigure.py5
-rw-r--r--platform_helper.py4
-rwxr-xr-xsrc/browse.py8
-rw-r--r--src/build_test.cc64
-rw-r--r--src/graph.cc1
-rw-r--r--src/includes_normalize-win32.cc4
-rw-r--r--src/includes_normalize_test.cc20
-rw-r--r--src/msvc_helper-win32.cc7
-rw-r--r--src/msvc_helper.h5
-rw-r--r--src/msvc_helper_test.cc1
-rw-r--r--src/ninja.cc473
-rw-r--r--src/subprocess-posix.cc6
-rw-r--r--src/util.cc5
-rw-r--r--src/util.h2
-rw-r--r--src/version.cc2
18 files changed, 389 insertions, 228 deletions
diff --git a/HACKING.md b/HACKING.md
index 11edf74..8e1696a 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -62,7 +62,7 @@ googletest (gtest) library.
* On newer Ubuntus it's only distributed as source
apt-get install libgtest-dev
- ./configure --with-gtest=/usr/src/gtest
+ ./configure.py --with-gtest=/usr/src/gtest
* Otherwise you need to download it, unpack it, and pass
`--with-gtest` to `configure.py`. Get it from [its downloads
diff --git a/RELEASING b/RELEASING
index faa04a2..1110f0b 100644
--- a/RELEASING
+++ b/RELEASING
@@ -8,3 +8,5 @@ Notes to myself on all the steps to make for a Ninja release.
6. commit, tag, push (don't forget to push --tags)
7. construct release notes from prior notes
credits: git shortlog -s --no-merges REV..
+8. update home page mention of latest version.
+
diff --git a/bootstrap.py b/bootstrap.py
index cff10ba..5682bf1 100755
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -36,6 +36,8 @@ parser.add_option('--x64', action='store_true',
parser.add_option('--platform',
help='target platform (' + '/'.join(platform_helper.platforms()) + ')',
choices=platform_helper.platforms())
+parser.add_option('--force-pselect', action='store_true',
+ help="ppoll() is used by default on Linux and OpenBSD, but older versions might need to use pselect instead",)
(options, conf_args) = parser.parse_args()
@@ -107,6 +109,10 @@ else:
cflags.append('-D_WIN32_WINNT=0x0501')
if options.x64:
cflags.append('-m64')
+if (platform.is_linux() or platform.is_openbsd()) and not options.force_pselect:
+ cflags.append('-DUSE_PPOLL')
+if options.force_pselect:
+ conf_args.append("--force-pselect")
args.extend(cflags)
args.extend(ldflags)
binary = 'ninja.bootstrap'
diff --git a/configure.py b/configure.py
index 7c90e66..22eb1e5 100755
--- a/configure.py
+++ b/configure.py
@@ -47,6 +47,8 @@ parser.add_option('--with-gtest', metavar='PATH',
parser.add_option('--with-python', metavar='EXE',
help='use EXE as the Python interpreter',
default=os.path.basename(sys.executable))
+parser.add_option('--force-pselect', action='store_true',
+ help="ppoll() is used by default on Linux and OpenBSD, but older versions might need to use pselect instead",)
(options, args) = parser.parse_args()
if args:
print('ERROR: extra unparsed command-line arguments:', args)
@@ -163,6 +165,9 @@ else:
cflags.append('-fno-omit-frame-pointer')
libs.extend(['-Wl,--no-as-needed', '-lprofiler'])
+if (platform.is_linux() or platform.is_openbsd()) and not options.force_pselect:
+ cflags.append('-DUSE_PPOLL')
+
def shell_escape(str):
"""Escape str such that it's interpreted as a single argument by
the shell."""
diff --git a/platform_helper.py b/platform_helper.py
index 97827c3..5097f49 100644
--- a/platform_helper.py
+++ b/platform_helper.py
@@ -19,7 +19,7 @@ import sys
def platforms():
return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5',
- 'mingw', 'msvc']
+ 'mingw', 'msvc', 'gnukfreebsd8']
class Platform( object ):
def __init__( self, platform):
@@ -31,6 +31,8 @@ class Platform( object ):
self._platform = 'linux'
elif self._platform.startswith('freebsd'):
self._platform = 'freebsd'
+ elif self._platform.startswith('gnukfreebsd8'):
+ self._platform = 'freebsd'
elif self._platform.startswith('openbsd'):
self._platform = 'openbsd'
elif self._platform.startswith('solaris'):
diff --git a/src/browse.py b/src/browse.py
index 652bac2..9e59bd8 100755
--- a/src/browse.py
+++ b/src/browse.py
@@ -26,6 +26,8 @@ try:
import http.server as httpserver
except ImportError:
import BaseHTTPServer as httpserver
+import os
+import socket
import subprocess
import sys
import webbrowser
@@ -183,8 +185,10 @@ class RequestHandler(httpserver.BaseHTTPRequestHandler):
port = 8000
httpd = httpserver.HTTPServer(('',port), RequestHandler)
try:
- print('Web server running on port %d, ctl-C to abort...' % port)
- webbrowser.open_new('http://localhost:%s' % port)
+ hostname = socket.gethostname()
+ print('Web server running on %s:%d, ctl-C to abort...' % (hostname,port) )
+ print('Web server pid %d' % os.getpid(), file=sys.stderr )
+ webbrowser.open_new('http://%s:%s' % (hostname, port) )
httpd.serve_forever()
except KeyboardInterrupt:
print()
diff --git a/src/build_test.cc b/src/build_test.cc
index 90c328a..ed9ade3 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -1602,3 +1602,67 @@ TEST_F(BuildWithDepsLogTest, DepsIgnoredInDryRun) {
builder.command_runner_.release();
}
+
+/// Check that a restat rule generating a header cancels compilations correctly.
+TEST_F(BuildWithDepsLogTest, RestatDepfileDependency) {
+ string err;
+ // Note: in1 was created by the superclass SetUp().
+ const char* manifest =
+ "rule true\n"
+ " command = true\n" // Would be "write if out-of-date" in reality.
+ " restat = 1\n"
+ "build header.h: true header.in\n"
+ "build out: cat in1\n"
+ " deps = gcc\n"
+ " depfile = in1.d\n";
+ {
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ // Run the build once, everything should be ok.
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+ ASSERT_EQ("", err);
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ builder.command_runner_.reset(&command_runner_);
+ EXPECT_TRUE(builder.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+ fs_.Create("in1.d", "out: header.h");
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ deps_log.Close();
+ builder.command_runner_.release();
+ }
+
+ {
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ // Touch the input of the restat rule.
+ fs_.Tick();
+ fs_.Create("header.in", "");
+
+ // Run the build again.
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ builder.command_runner_.reset(&command_runner_);
+ command_runner_.commands_ran_.clear();
+ EXPECT_TRUE(builder.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ // Rule "true" should have run again, but the build of "out" should have
+ // been cancelled due to restat propagating through the depfile header.
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+
+ builder.command_runner_.release();
+ }
+}
diff --git a/src/graph.cc b/src/graph.cc
index b245e52..7a57753 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -418,6 +418,7 @@ bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, TimeStamp* deps_mtime,
PreallocateSpace(edge, deps->node_count);
for (int i = 0; i < deps->node_count; ++i, ++implicit_dep) {
*implicit_dep = deps->nodes[i];
+ deps->nodes[i]->AddOutEdge(edge);
CreatePhonyInEdge(*implicit_dep);
}
return true;
diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc
index 4bc8756..05ce75d 100644
--- a/src/includes_normalize-win32.cc
+++ b/src/includes_normalize-win32.cc
@@ -110,6 +110,6 @@ string IncludesNormalize::Normalize(const string& input,
}
StringPiece partially_fixed(copy, len);
if (!SameDrive(partially_fixed, relative_to))
- return ToLower(partially_fixed.AsString());
- return ToLower(Relativize(partially_fixed, relative_to));
+ return partially_fixed.AsString();
+ return Relativize(partially_fixed, relative_to);
}
diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc
index 29e6755..1713d5d 100644
--- a/src/includes_normalize_test.cc
+++ b/src/includes_normalize_test.cc
@@ -52,11 +52,11 @@ TEST(IncludesNormalize, WithRelative) {
TEST(IncludesNormalize, Case) {
EXPECT_EQ("b", IncludesNormalize::Normalize("Abc\\..\\b", NULL));
- EXPECT_EQ("bdef", IncludesNormalize::Normalize("Abc\\..\\BdEf", NULL));
- EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\.\\b", NULL));
- EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\./b", NULL));
- EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\.\\B", NULL));
- EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\./B", NULL));
+ EXPECT_EQ("BdEf", IncludesNormalize::Normalize("Abc\\..\\BdEf", NULL));
+ EXPECT_EQ("A\\b", IncludesNormalize::Normalize("A\\.\\b", NULL));
+ EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\./b", NULL));
+ EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\.\\B", NULL));
+ EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\./B", NULL));
}
TEST(IncludesNormalize, Join) {
@@ -91,12 +91,12 @@ TEST(IncludesNormalize, DifferentDrive) {
EXPECT_EQ("stuff.h",
IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "p:\\vs08"));
EXPECT_EQ("stuff.h",
- IncludesNormalize::Normalize("P:\\vs08\\stuff.h", "p:\\vs08"));
- EXPECT_EQ("p:\\vs08\\stuff.h",
- IncludesNormalize::Normalize("P:\\vs08\\stuff.h", "c:\\vs08"));
- EXPECT_EQ("p:\\vs08\\stuff.h",
- IncludesNormalize::Normalize("P:\\vs08\\stuff.h", "D:\\stuff/things"));
+ IncludesNormalize::Normalize("P:\\Vs08\\stuff.h", "p:\\vs08"));
EXPECT_EQ("p:\\vs08\\stuff.h",
+ IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "c:\\vs08"));
+ EXPECT_EQ("P:\\vs08\\stufF.h",
+ IncludesNormalize::Normalize("P:\\vs08\\stufF.h", "D:\\stuff/things"));
+ EXPECT_EQ("P:\\vs08\\stuff.h",
IncludesNormalize::Normalize("P:/vs08\\stuff.h", "D:\\stuff/things"));
// TODO: this fails; fix it.
//EXPECT_EQ("P:\\wee\\stuff.h",
diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc
index be2a5e0..7c45029 100644
--- a/src/msvc_helper-win32.cc
+++ b/src/msvc_helper-win32.cc
@@ -14,6 +14,7 @@
#include "msvc_helper.h"
+#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <windows.h>
@@ -63,14 +64,16 @@ string CLParser::FilterShowIncludes(const string& line) {
}
// static
-bool CLParser::IsSystemInclude(const string& path) {
+bool CLParser::IsSystemInclude(string path) {
+ transform(path.begin(), path.end(), path.begin(), ::tolower);
// TODO: this is a heuristic, perhaps there's a better way?
return (path.find("program files") != string::npos ||
path.find("microsoft visual studio") != string::npos);
}
// static
-bool CLParser::FilterInputFilename(const string& line) {
+bool CLParser::FilterInputFilename(string line) {
+ transform(line.begin(), line.end(), line.begin(), ::tolower);
// TODO: other extensions, like .asm?
return EndsWith(line, ".c") ||
EndsWith(line, ".cc") ||
diff --git a/src/msvc_helper.h b/src/msvc_helper.h
index 32ab606..e207485 100644
--- a/src/msvc_helper.h
+++ b/src/msvc_helper.h
@@ -30,15 +30,14 @@ struct CLParser {
static string FilterShowIncludes(const string& line);
/// Return true if a mentioned include file is a system path.
- /// Expects the path to already by normalized (including lower case).
/// Filtering these out reduces dependency information considerably.
- static bool IsSystemInclude(const string& path);
+ static bool IsSystemInclude(string path);
/// Parse a line of cl.exe output and return true if it looks like
/// it's printing an input filename. This is a heuristic but it appears
/// to be the best we can do.
/// Exposed for testing.
- static bool FilterInputFilename(const string& line);
+ static bool FilterInputFilename(string line);
/// Parse the full output of cl, returning the output (if any) that
/// should printed.
diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc
index 1e1cbde..02f2863 100644
--- a/src/msvc_helper_test.cc
+++ b/src/msvc_helper_test.cc
@@ -35,6 +35,7 @@ TEST(CLParserTest, FilterInputFilename) {
ASSERT_TRUE(CLParser::FilterInputFilename("foobar.cc"));
ASSERT_TRUE(CLParser::FilterInputFilename("foo bar.cc"));
ASSERT_TRUE(CLParser::FilterInputFilename("baz.c"));
+ ASSERT_TRUE(CLParser::FilterInputFilename("FOOBAR.CC"));
ASSERT_FALSE(CLParser::FilterInputFilename(
"src\\cl_helper.cc(166) : fatal error C1075: end "
diff --git a/src/ninja.cc b/src/ninja.cc
index b4797ed..3b381b7 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -42,34 +42,100 @@
#include "util.h"
#include "version.h"
+#ifdef _MSC_VER
// Defined in msvc_helper_main-win32.cc.
int MSVCHelperMain(int argc, char** argv);
+// Defined in minidump-win32.cc.
+void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep);
+#endif
+
namespace {
-/// Global information passed into subtools.
-struct Globals {
- Globals() : state(new State()) {}
- ~Globals() {
- delete state;
- }
+struct Tool;
- /// Deletes and recreates state so it is empty.
- void ResetState() {
- delete state;
- state = new State();
- }
+/// Command-line options.
+struct Options {
+ /// Build file to load.
+ const char* input_file;
+
+ /// Directory to change into before running.
+ const char* working_dir;
+
+ /// Tool to run rather than building.
+ const Tool* tool;
+};
+
+/// The Ninja main() loads up a series of data structures; various tools need
+/// to poke into these, so store them as fields on an object.
+struct NinjaMain {
+ NinjaMain(const char* ninja_command, const BuildConfig& config) :
+ ninja_command_(ninja_command), config_(config) {}
/// Command line used to run Ninja.
- const char* ninja_command;
+ const char* ninja_command_;
+
/// Build configuration set from flags (e.g. parallelism).
- BuildConfig* config;
- /// Loaded state (rules, nodes). This is a pointer so it can be reset.
- State* state;
-};
+ const BuildConfig& config_;
+
+ /// Loaded state (rules, nodes).
+ State state_;
+
+ /// Functions for accesssing the disk.
+ RealDiskInterface disk_interface_;
+
+ /// The build directory, used for storing the build log etc.
+ string build_dir_;
+
+ BuildLog build_log_;
+ DepsLog deps_log_;
+
+ /// The type of functions that are the entry points to tools (subcommands).
+ typedef int (NinjaMain::*ToolFunc)(int, char**);
+
+ /// Get the Node for a given command-line path, handling features like
+ /// spell correction.
+ Node* CollectTarget(const char* cpath, string* err);
+
+ /// CollectTarget for all command-line arguments, filling in \a targets.
+ bool CollectTargetsFromArgs(int argc, char* argv[],
+ vector<Node*>* targets, string* err);
+
+ // The various subcommands, run via "-t XXX".
+ int ToolGraph(int argc, char* argv[]);
+ int ToolQuery(int argc, char* argv[]);
+ int ToolBrowse(int argc, char* argv[]);
+ int ToolMSVC(int argc, char* argv[]);
+ int ToolTargets(int argc, char* argv[]);
+ int ToolCommands(int argc, char* argv[]);
+ int ToolClean(int argc, char* argv[]);
+ int ToolCompilationDatabase(int argc, char* argv[]);
+ int ToolUrtle(int argc, char** argv);
-/// The type of functions that are the entry points to tools (subcommands).
-typedef int (*ToolFunc)(Globals*, int, char**);
+ /// Open the build log.
+ /// @return false on error.
+ bool OpenBuildLog();
+
+ /// Open the deps log: load it, then open for writing.
+ /// @return false on error.
+ bool OpenDepsLog();
+
+ /// Ensure the build directory exists, creating it if necessary.
+ /// @return false on error.
+ bool EnsureBuildDirExists();
+
+ /// Rebuild the manifest, if necessary.
+ /// Fills in \a err on error.
+ /// @return true if the manifest was rebuilt.
+ bool RebuildManifest(const char* input_file, string* err);
+
+ /// Build the targets listed on the command line.
+ /// @return an exit code.
+ int RunBuild(int argc, char** argv);
+
+ /// Dump the output requested by '-d stats'.
+ void DumpMetrics();
+};
/// Subtools, accessible via "-t foo".
struct Tool {
@@ -86,10 +152,13 @@ struct Tool {
/// Run after loading build.ninja.
RUN_AFTER_LOAD,
+
+ /// Run after loading the build/deps logs.
+ RUN_AFTER_LOGS,
} when;
/// Implementation of the tool.
- ToolFunc func;
+ NinjaMain::ToolFunc func;
};
/// Print usage information.
@@ -143,20 +212,21 @@ struct RealFileReader : public ManifestParser::FileReader {
/// Rebuild the build manifest, if necessary.
/// Returns true if the manifest was rebuilt.
-bool RebuildManifest(Builder* builder, const char* input_file, string* err) {
+bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
string path = input_file;
if (!CanonicalizePath(&path, err))
return false;
- Node* node = builder->state_->LookupNode(path);
+ Node* node = state_.LookupNode(path);
if (!node)
return false;
- if (!builder->AddTarget(node, err))
+ Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_);
+ if (!builder.AddTarget(node, err))
return false;
- if (builder->AlreadyUpToDate())
+ if (builder.AlreadyUpToDate())
return false; // Not an error, but we didn't rebuild.
- if (!builder->Build(err))
+ if (!builder.Build(err))
return false;
// The manifest was only rebuilt if it is now dirty (it may have been cleaned
@@ -164,7 +234,7 @@ bool RebuildManifest(Builder* builder, const char* input_file, string* err) {
return node->dirty();
}
-Node* CollectTarget(State* state, const char* cpath, string* err) {
+Node* NinjaMain::CollectTarget(const char* cpath, string* err) {
string path = cpath;
if (!CanonicalizePath(&path, err))
return NULL;
@@ -176,7 +246,7 @@ Node* CollectTarget(State* state, const char* cpath, string* err) {
first_dependent = true;
}
- Node* node = state->LookupNode(path);
+ Node* node = state_.LookupNode(path);
if (node) {
if (first_dependent) {
if (node->out_edges().empty()) {
@@ -199,7 +269,7 @@ Node* CollectTarget(State* state, const char* cpath, string* err) {
} else if (path == "help") {
*err += ", did you mean 'ninja -h'?";
} else {
- Node* suggestion = state->SpellcheckNode(path);
+ Node* suggestion = state_.SpellcheckNode(path);
if (suggestion) {
*err += ", did you mean '" + suggestion->path() + "'?";
}
@@ -208,15 +278,15 @@ Node* CollectTarget(State* state, const char* cpath, string* err) {
}
}
-bool CollectTargetsFromArgs(State* state, int argc, char* argv[],
- vector<Node*>* targets, string* err) {
+bool NinjaMain::CollectTargetsFromArgs(int argc, char* argv[],
+ vector<Node*>* targets, string* err) {
if (argc == 0) {
- *targets = state->DefaultNodes(err);
+ *targets = state_.DefaultNodes(err);
return err->empty();
}
for (int i = 0; i < argc; ++i) {
- Node* node = CollectTarget(state, argv[i], err);
+ Node* node = CollectTarget(argv[i], err);
if (node == NULL)
return false;
targets->push_back(node);
@@ -224,10 +294,10 @@ bool CollectTargetsFromArgs(State* state, int argc, char* argv[],
return true;
}
-int ToolGraph(Globals* globals, int argc, char* argv[]) {
+int NinjaMain::ToolGraph(int argc, char* argv[]) {
vector<Node*> nodes;
string err;
- if (!CollectTargetsFromArgs(globals->state, argc, argv, &nodes, &err)) {
+ if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
@@ -241,14 +311,15 @@ int ToolGraph(Globals* globals, int argc, char* argv[]) {
return 0;
}
-int ToolQuery(Globals* globals, int argc, char* argv[]) {
+int NinjaMain::ToolQuery(int argc, char* argv[]) {
if (argc == 0) {
Error("expected a target to query");
return 1;
}
+
for (int i = 0; i < argc; ++i) {
string err;
- Node* node = CollectTarget(globals->state, argv[i], &err);
+ Node* node = CollectTarget(argv[i], &err);
if (!node) {
Error("%s", err.c_str());
return 1;
@@ -279,19 +350,19 @@ int ToolQuery(Globals* globals, int argc, char* argv[]) {
}
#if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP)
-int ToolBrowse(Globals* globals, int argc, char* argv[]) {
+int NinjaMain::ToolBrowse(int argc, char* argv[]) {
if (argc < 1) {
Error("expected a target to browse");
return 1;
}
- RunBrowsePython(globals->state, globals->ninja_command, argv[0]);
+ RunBrowsePython(&state_, ninja_command_, argv[0]);
// If we get here, the browse failed.
return 1;
}
#endif // _WIN32
-#if defined(_WIN32)
-int ToolMSVC(Globals* globals, int argc, char* argv[]) {
+#if defined(_MSC_VER)
+int NinjaMain::ToolMSVC(int argc, char* argv[]) {
// Reset getopt: push one argument onto the front of argv, reset optind.
argc++;
argv--;
@@ -366,7 +437,7 @@ int ToolTargetsList(State* state) {
return 0;
}
-int ToolTargets(Globals* globals, int argc, char* argv[]) {
+int NinjaMain::ToolTargets(int argc, char* argv[]) {
int depth = 1;
if (argc >= 1) {
string mode = argv[0];
@@ -375,17 +446,17 @@ int ToolTargets(Globals* globals, int argc, char* argv[]) {
if (argc > 1)
rule = argv[1];
if (rule.empty())
- return ToolTargetsSourceList(globals->state);
+ return ToolTargetsSourceList(&state_);
else
- return ToolTargetsList(globals->state, rule);
+ return ToolTargetsList(&state_, rule);
} else if (mode == "depth") {
if (argc > 1)
depth = atoi(argv[1]);
} else if (mode == "all") {
- return ToolTargetsList(globals->state);
+ return ToolTargetsList(&state_);
} else {
const char* suggestion =
- SpellcheckString(mode, "rule", "depth", "all", NULL);
+ SpellcheckString(mode.c_str(), "rule", "depth", "all", NULL);
if (suggestion) {
Error("unknown target tool mode '%s', did you mean '%s'?",
mode.c_str(), suggestion);
@@ -397,7 +468,7 @@ int ToolTargets(Globals* globals, int argc, char* argv[]) {
}
string err;
- vector<Node*> root_nodes = globals->state->RootNodes(&err);
+ vector<Node*> root_nodes = state_.RootNodes(&err);
if (err.empty()) {
return ToolTargetsList(root_nodes, depth, 0);
} else {
@@ -420,10 +491,10 @@ void PrintCommands(Edge* edge, set<Edge*>* seen) {
puts(edge->EvaluateCommand().c_str());
}
-int ToolCommands(Globals* globals, int argc, char* argv[]) {
+int NinjaMain::ToolCommands(int argc, char* argv[]) {
vector<Node*> nodes;
string err;
- if (!CollectTargetsFromArgs(globals->state, argc, argv, &nodes, &err)) {
+ if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
@@ -435,7 +506,7 @@ int ToolCommands(Globals* globals, int argc, char* argv[]) {
return 0;
}
-int ToolClean(Globals* globals, int argc, char* argv[]) {
+int NinjaMain::ToolClean(int argc, char* argv[]) {
// The clean tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "clean".
argc++;
@@ -473,7 +544,7 @@ int ToolClean(Globals* globals, int argc, char* argv[]) {
return 1;
}
- Cleaner cleaner(globals->state, *globals->config);
+ Cleaner cleaner(&state_, config_);
if (argc >= 1) {
if (clean_rules)
return cleaner.CleanRules(argc, argv);
@@ -493,25 +564,29 @@ void EncodeJSONString(const char *str) {
}
}
-int ToolCompilationDatabase(Globals* globals, int argc, char* argv[]) {
+int NinjaMain::ToolCompilationDatabase(int argc, char* argv[]) {
bool first = true;
- char cwd[PATH_MAX];
+ vector<char> cwd;
- if (!getcwd(cwd, PATH_MAX)) {
+ do {
+ cwd.resize(cwd.size() + 1024);
+ errno = 0;
+ } while (!getcwd(&cwd[0], cwd.size()) && errno == ERANGE);
+ if (errno != 0 && errno != ERANGE) {
Error("cannot determine working directory: %s", strerror(errno));
return 1;
}
putchar('[');
- for (vector<Edge*>::iterator e = globals->state->edges_.begin();
- e != globals->state->edges_.end(); ++e) {
+ for (vector<Edge*>::iterator e = state_.edges_.begin();
+ e != state_.edges_.end(); ++e) {
for (int i = 0; i != argc; ++i) {
if ((*e)->rule_->name() == argv[i]) {
if (!first)
putchar(',');
printf("\n {\n \"directory\": \"");
- EncodeJSONString(cwd);
+ EncodeJSONString(&cwd[0]);
printf("\",\n \"command\": \"");
EncodeJSONString((*e)->EvaluateCommand().c_str());
printf("\",\n \"file\": \"");
@@ -527,7 +602,7 @@ int ToolCompilationDatabase(Globals* globals, int argc, char* argv[]) {
return 0;
}
-int ToolUrtle(Globals* globals, int argc, char** argv) {
+int NinjaMain::ToolUrtle(int argc, char** argv) {
// RLE encoded.
const char* urtle =
" 13 ,3;2!2;\n8 ,;<11!;\n5 `'<10!(2`'2!\n11 ,6;, `\\. `\\9 .,c13$ec,.\n6 "
@@ -554,31 +629,31 @@ int ToolUrtle(Globals* globals, int argc, char** argv) {
}
/// Find the function to execute for \a tool_name and return it via \a func.
-/// If there is no tool to run (e.g.: unknown tool), returns an exit code.
-int ChooseTool(const string& tool_name, const Tool** tool_out) {
+/// Returns a Tool, or NULL if Ninja should exit.
+const Tool* ChooseTool(const string& tool_name) {
static const Tool kTools[] = {
#if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP)
{ "browse", "browse dependency graph in a web browser",
- Tool::RUN_AFTER_LOAD, ToolBrowse },
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse },
#endif
-#if defined(_WIN32)
+#if defined(_MSC_VER)
{ "msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)",
- Tool::RUN_AFTER_FLAGS, ToolMSVC },
+ Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolMSVC },
#endif
{ "clean", "clean built files",
- Tool::RUN_AFTER_LOAD, ToolClean },
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolClean },
{ "commands", "list all commands required to rebuild given targets",
- Tool::RUN_AFTER_LOAD, ToolCommands },
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCommands },
{ "graph", "output graphviz dot file for targets",
- Tool::RUN_AFTER_LOAD, ToolGraph },
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolGraph },
{ "query", "show inputs/outputs for a path",
- Tool::RUN_AFTER_LOAD, ToolQuery },
+ Tool::RUN_AFTER_LOGS, &NinjaMain::ToolQuery },
{ "targets", "list targets by their rule or depth in the DAG",
- Tool::RUN_AFTER_LOAD, ToolTargets },
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolTargets },
{ "compdb", "dump JSON compilation database to stdout",
- Tool::RUN_AFTER_LOAD, ToolCompilationDatabase },
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCompilationDatabase },
{ "urtle", NULL,
- Tool::RUN_AFTER_FLAGS, ToolUrtle },
+ Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolUrtle },
{ NULL, NULL, Tool::RUN_AFTER_FLAGS, NULL }
};
@@ -588,14 +663,12 @@ int ChooseTool(const string& tool_name, const Tool** tool_out) {
if (tool->desc)
printf("%10s %s\n", tool->name, tool->desc);
}
- return 0;
+ return NULL;
}
for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
- if (tool->name == tool_name) {
- *tool_out = tool;
- return 0;
- }
+ if (tool->name == tool_name)
+ return tool;
}
vector<const char*> words;
@@ -603,17 +676,17 @@ int ChooseTool(const string& tool_name, const Tool** tool_out) {
words.push_back(tool->name);
const char* suggestion = SpellcheckStringV(tool_name, words);
if (suggestion) {
- Error("unknown tool '%s', did you mean '%s'?",
+ Fatal("unknown tool '%s', did you mean '%s'?",
tool_name.c_str(), suggestion);
} else {
- Error("unknown tool '%s'", tool_name.c_str());
+ Fatal("unknown tool '%s'", tool_name.c_str());
}
- return 1;
+ return NULL; // Not reached.
}
/// Enable a debugging mode. Returns false if Ninja should exit instead
/// of continuing.
-bool DebugEnable(const string& name, Globals* globals) {
+bool DebugEnable(const string& name) {
if (name == "list") {
printf("debugging modes:\n"
" stats print operation counts/timing info\n"
@@ -628,7 +701,7 @@ bool DebugEnable(const string& name, Globals* globals) {
return true;
} else {
const char* suggestion =
- SpellcheckString(name, "stats", "explain", NULL);
+ SpellcheckString(name.c_str(), "stats", "explain", NULL);
if (suggestion) {
Error("unknown debug setting '%s', did you mean '%s'?",
name.c_str(), suggestion);
@@ -639,16 +712,13 @@ bool DebugEnable(const string& name, Globals* globals) {
}
}
-/// Open the build log.
-/// @return false on error.
-bool OpenBuildLog(BuildLog* build_log, const string& build_dir,
- Globals* globals, DiskInterface* disk_interface) {
+bool NinjaMain::OpenBuildLog() {
string log_path = ".ninja_log";
- if (!build_dir.empty())
- log_path = build_dir + "/" + log_path;
+ if (!build_dir_.empty())
+ log_path = build_dir_ + "/" + log_path;
string err;
- if (!build_log->Load(log_path, &err)) {
+ if (!build_log_.Load(log_path, &err)) {
Error("loading build log %s: %s", log_path.c_str(), err.c_str());
return false;
}
@@ -658,8 +728,8 @@ bool OpenBuildLog(BuildLog* build_log, const string& build_dir,
err.clear();
}
- if (!globals->config->dry_run) {
- if (!build_log->OpenForWrite(log_path, &err)) {
+ if (!config_.dry_run) {
+ if (!build_log_.OpenForWrite(log_path, &err)) {
Error("opening build log: %s", err.c_str());
return false;
}
@@ -670,14 +740,13 @@ bool OpenBuildLog(BuildLog* build_log, const string& build_dir,
/// Open the deps log: load it, then open for writing.
/// @return false on error.
-bool OpenDepsLog(DepsLog* deps_log, const string& build_dir,
- Globals* globals, DiskInterface* disk_interface) {
+bool NinjaMain::OpenDepsLog() {
string path = ".ninja_deps";
- if (!build_dir.empty())
- path = build_dir + "/" + path;
+ if (!build_dir_.empty())
+ path = build_dir_ + "/" + path;
string err;
- if (!deps_log->Load(path, globals->state, &err)) {
+ if (!deps_log_.Load(path, &state_, &err)) {
Error("loading deps log %s: %s", path.c_str(), err.c_str());
return false;
}
@@ -687,8 +756,8 @@ bool OpenDepsLog(DepsLog* deps_log, const string& build_dir,
err.clear();
}
- if (!globals->config->dry_run) {
- if (!deps_log->OpenForWrite(path, &err)) {
+ if (!config_.dry_run) {
+ if (!deps_log_.OpenForWrite(path, &err)) {
Error("opening deps log: %s", err.c_str());
return false;
}
@@ -697,28 +766,39 @@ bool OpenDepsLog(DepsLog* deps_log, const string& build_dir,
return true;
}
-
-/// Dump the output requested by '-d stats'.
-void DumpMetrics(Globals* globals) {
+void NinjaMain::DumpMetrics() {
g_metrics->Report();
printf("\n");
- int count = (int)globals->state->paths_.size();
- int buckets = (int)globals->state->paths_.bucket_count();
+ int count = (int)state_.paths_.size();
+ int buckets = (int)state_.paths_.bucket_count();
printf("path->node hash load %.2f (%d entries / %d buckets)\n",
count / (double) buckets, count, buckets);
}
-int RunBuild(Builder* builder, int argc, char** argv) {
+bool NinjaMain::EnsureBuildDirExists() {
+ build_dir_ = state_.bindings_.LookupVariable("builddir");
+ if (!build_dir_.empty() && !config_.dry_run) {
+ if (!disk_interface_.MakeDirs(build_dir_ + "/.") && errno != EEXIST) {
+ Error("creating build directory %s: %s",
+ build_dir_.c_str(), strerror(errno));
+ return false;
+ }
+ }
+ return true;
+}
+
+int NinjaMain::RunBuild(int argc, char** argv) {
string err;
vector<Node*> targets;
- if (!CollectTargetsFromArgs(builder->state_, argc, argv, &targets, &err)) {
+ if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) {
Error("%s", err.c_str());
return 1;
}
+ Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_);
for (size_t i = 0; i < targets.size(); ++i) {
- if (!builder->AddTarget(targets[i], &err)) {
+ if (!builder.AddTarget(targets[i], &err)) {
if (!err.empty()) {
Error("%s", err.c_str());
return 1;
@@ -729,15 +809,15 @@ int RunBuild(Builder* builder, int argc, char** argv) {
}
}
- if (builder->AlreadyUpToDate()) {
+ if (builder.AlreadyUpToDate()) {
printf("ninja: no work to do.\n");
return 0;
}
- if (!builder->Build(&err)) {
+ if (!builder.Build(&err)) {
printf("ninja: build stopped: %s.\n", err.c_str());
if (err.find("interrupted by user") != string::npos) {
- return 2;
+ return 2;
}
return 1;
}
@@ -747,13 +827,6 @@ int RunBuild(Builder* builder, int argc, char** argv) {
#ifdef _MSC_VER
-} // anonymous namespace
-
-// Defined in minidump-win32.cc.
-void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep);
-
-namespace {
-
/// This handler processes fatal crashes that you can't catch
/// Test example: C++ exception in a stack-unwind-block
/// Real-world example: ninja launched a compiler to process a tricky
@@ -775,18 +848,11 @@ int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
#endif // _MSC_VER
-int NinjaMain(int argc, char** argv) {
- BuildConfig config;
- Globals globals;
- globals.ninja_command = argv[0];
- globals.config = &config;
- const char* input_file = "build.ninja";
- const char* working_dir = NULL;
- string tool_name;
-
- setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
-
- config.parallelism = GuessParallelism();
+/// Parse argv for command-line options.
+/// Returns an exit code, or -1 if Ninja should continue.
+int ReadFlags(int* argc, char*** argv,
+ Options* options, BuildConfig* config) {
+ config->parallelism = GuessParallelism();
enum { OPT_VERSION = 1 };
const option kLongOptions[] = {
@@ -796,20 +862,25 @@ int NinjaMain(int argc, char** argv) {
};
int opt;
- while (tool_name.empty() &&
- (opt = getopt_long(argc, argv, "d:f:j:k:l:nt:vC:h", kLongOptions,
+ while (!options->tool &&
+ (opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vC:h", kLongOptions,
NULL)) != -1) {
switch (opt) {
case 'd':
- if (!DebugEnable(optarg, &globals))
+ if (!DebugEnable(optarg))
return 1;
break;
case 'f':
- input_file = optarg;
+ options->input_file = optarg;
break;
- case 'j':
- config.parallelism = atoi(optarg);
+ case 'j': {
+ char* end;
+ int value = strtol(optarg, &end, 10);
+ if (*end != 0 || value <= 0)
+ Fatal("invalid -j parameter");
+ config->parallelism = value;
break;
+ }
case 'k': {
char* end;
int value = strtol(optarg, &end, 10);
@@ -819,7 +890,7 @@ int NinjaMain(int argc, char** argv) {
// We want to go until N jobs fail, which means we should allow
// N failures and then stop. For N <= 0, INT_MAX is close enough
// to infinite for most sane builds.
- config.failures_allowed = value > 0 ? value : INT_MAX;
+ config->failures_allowed = value > 0 ? value : INT_MAX;
break;
}
case 'l': {
@@ -827,113 +898,113 @@ int NinjaMain(int argc, char** argv) {
double value = strtod(optarg, &end);
if (end == optarg)
Fatal("-l parameter not numeric: did you mean -l 0.0?");
- config.max_load_average = value;
+ config->max_load_average = value;
break;
}
case 'n':
- config.dry_run = true;
+ config->dry_run = true;
break;
case 't':
- tool_name = optarg;
+ options->tool = ChooseTool(optarg);
+ if (!options->tool)
+ return 0;
break;
case 'v':
- config.verbosity = BuildConfig::VERBOSE;
+ config->verbosity = BuildConfig::VERBOSE;
break;
case 'C':
- working_dir = optarg;
+ options->working_dir = optarg;
break;
case OPT_VERSION:
printf("%s\n", kNinjaVersion);
return 0;
case 'h':
default:
- Usage(config);
+ Usage(*config);
return 1;
}
}
- argv += optind;
- argc -= optind;
+ *argv += optind;
+ *argc -= optind;
- // If specified, select a tool as early as possible, so commands like
- // -t list can run before we attempt to load build.ninja etc.
- const Tool* tool = NULL;
- if (!tool_name.empty()) {
- int exit_code = ChooseTool(tool_name, &tool);
- if (!tool)
- return exit_code;
- }
+ return -1;
+}
+
+int real_main(int argc, char** argv) {
+ BuildConfig config;
+ Options options = {};
+ options.input_file = "build.ninja";
+
+ setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
- if (tool && tool->when == Tool::RUN_AFTER_FLAGS)
- return tool->func(&globals, argc, argv);
+ int exit_code = ReadFlags(&argc, &argv, &options, &config);
+ if (exit_code >= 0)
+ return exit_code;
- if (working_dir) {
+ if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) {
+ // None of the RUN_AFTER_FLAGS actually use a NinjaMain, but it's needed
+ // by other tools.
+ NinjaMain ninja(argv[0], config);
+ return (ninja.*options.tool->func)(argc, argv);
+ }
+
+ if (options.working_dir) {
// The formatting of this string, complete with funny quotes, is
// so Emacs can properly identify that the cwd has changed for
// subsequent commands.
// Don't print this if a tool is being used, so that tool output
// can be piped into a file without this string showing up.
- if (!tool)
- printf("ninja: Entering directory `%s'\n", working_dir);
- if (chdir(working_dir) < 0) {
- Fatal("chdir to '%s' - %s", working_dir, strerror(errno));
+ if (!options.tool)
+ printf("ninja: Entering directory `%s'\n", options.working_dir);
+ if (chdir(options.working_dir) < 0) {
+ Fatal("chdir to '%s' - %s", options.working_dir, strerror(errno));
}
}
- bool rebuilt_manifest = false;
-
-reload:
- RealFileReader file_reader;
- ManifestParser parser(globals.state, &file_reader);
- string err;
- if (!parser.Load(input_file, &err)) {
- Error("%s", err.c_str());
- return 1;
- }
-
- if (tool && tool->when == Tool::RUN_AFTER_LOAD)
- return tool->func(&globals, argc, argv);
+ // The build can take up to 2 passes: one to rebuild the manifest, then
+ // another to build the desired target.
+ for (int cycle = 0; cycle < 2; ++cycle) {
+ NinjaMain ninja(argv[0], config);
- RealDiskInterface disk_interface;
-
- // Create the build dir if it doesn't exist.
- const string build_dir = globals.state->bindings_.LookupVariable("builddir");
- if (!build_dir.empty() && !config.dry_run) {
- if (!disk_interface.MakeDirs(build_dir + "/.") &&
- errno != EEXIST) {
- Error("creating build directory %s: %s",
- build_dir.c_str(), strerror(errno));
+ RealFileReader file_reader;
+ ManifestParser parser(&ninja.state_, &file_reader);
+ string err;
+ if (!parser.Load(options.input_file, &err)) {
+ Error("%s", err.c_str());
return 1;
}
- }
- BuildLog build_log;
- if (!OpenBuildLog(&build_log, build_dir, &globals, &disk_interface))
- return 1;
+ if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD)
+ return (ninja.*options.tool->func)(argc, argv);
- DepsLog deps_log;
- if (!OpenDepsLog(&deps_log, build_dir, &globals, &disk_interface))
- return 1;
+ if (!ninja.EnsureBuildDirExists())
+ return 1;
- if (!rebuilt_manifest) { // Don't get caught in an infinite loop by a rebuild
- // target that is never up to date.
- Builder manifest_builder(globals.state, config, &build_log, &deps_log,
- &disk_interface);
- if (RebuildManifest(&manifest_builder, input_file, &err)) {
- rebuilt_manifest = true;
- globals.ResetState();
- goto reload;
- } else if (!err.empty()) {
- Error("rebuilding '%s': %s", input_file, err.c_str());
+ if (!ninja.OpenBuildLog() || !ninja.OpenDepsLog())
return 1;
+
+ if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS)
+ return (ninja.*options.tool->func)(argc, argv);
+
+ // The first time through, attempt to rebuild the manifest before
+ // building anything else.
+ if (cycle == 0) {
+ if (ninja.RebuildManifest(options.input_file, &err)) {
+ // Start the build over with the new manifest.
+ continue;
+ } else if (!err.empty()) {
+ Error("rebuilding '%s': %s", options.input_file, err.c_str());
+ return 1;
+ }
}
+
+ int result = ninja.RunBuild(argc, argv);
+ if (g_metrics)
+ ninja.DumpMetrics();
+ return result;
}
- Builder builder(globals.state, config, &build_log, &deps_log,
- &disk_interface);
- int result = RunBuild(&builder, argc, argv);
- if (g_metrics)
- DumpMetrics(&globals);
- return result;
+ return 1; // Shouldn't be reached.
}
} // anonymous namespace
@@ -946,7 +1017,7 @@ int main(int argc, char** argv) {
__try {
// Running inside __try ... __except suppresses any Windows error
// dialogs for errors such as bad_alloc.
- return NinjaMain(argc, argv);
+ return real_main(argc, argv);
}
__except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
// Common error situations return exitCode=1. 2 was chosen to
@@ -954,6 +1025,6 @@ int main(int argc, char** argv) {
return 2;
}
#else
- return NinjaMain(argc, argv);
+ return real_main(argc, argv);
#endif
}
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index 339edfe..b396f84 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -40,12 +40,12 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
if (pipe(output_pipe) < 0)
Fatal("pipe: %s", strerror(errno));
fd_ = output_pipe[0];
-#if !defined(linux) && !defined(__OpenBSD__)
+#if !defined(USE_PPOLL)
// On Linux and OpenBSD, we use ppoll in DoWork(); elsewhere we use pselect
// and so must avoid overly-large FDs.
if (fd_ >= static_cast<int>(FD_SETSIZE))
Fatal("pipe: %s", strerror(EMFILE));
-#endif // !linux && !__OpenBSD__
+#endif // !USE_PPOLL
SetCloseOnExec(fd_);
pid_ = fork();
@@ -178,7 +178,7 @@ Subprocess *SubprocessSet::Add(const string& command) {
return subprocess;
}
-#if defined(linux) || defined(__OpenBSD__)
+#ifdef USE_PPOLL
bool SubprocessSet::DoWork() {
vector<pollfd> fds;
nfds_t nfds = 0;
diff --git a/src/util.cc b/src/util.cc
index fa72dd2..b9c2c0d 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -234,13 +234,16 @@ const char* SpellcheckStringV(const string& text,
return result;
}
-const char* SpellcheckString(const string& text, ...) {
+const char* SpellcheckString(const char* text, ...) {
+ // Note: This takes a const char* instead of a string& because using
+ // va_start() with a reference parameter is undefined behavior.
va_list ap;
va_start(ap, text);
vector<const char*> words;
const char* word;
while ((word = va_arg(ap, const char*)))
words.push_back(word);
+ va_end(ap);
return SpellcheckStringV(text, words);
}
diff --git a/src/util.h b/src/util.h
index 9740565..6788410 100644
--- a/src/util.h
+++ b/src/util.h
@@ -59,7 +59,7 @@ 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, ...);
+const char* SpellcheckString(const char* text, ...);
/// Removes all Ansi escape codes (http://www.termsys.demon.co.uk/vtansi.htm).
string StripAnsiEscapeCodes(const string& in);
diff --git a/src/version.cc b/src/version.cc
index 54818a0..28280aa 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -18,7 +18,7 @@
#include "util.h"
-const char* kNinjaVersion = "1.3.3";
+const char* kNinjaVersion = "1.3.4";
void ParseVersion(const string& version, int* major, int* minor) {
size_t end = version.find('.');