`./configure.py` generates the `build.ninja` files used to build
ninja. It accepts various flags to adjust build parameters.
+Run './configure.py --help' for more configuration options.
The primary build target of interest is `ninja`, but when hacking on
-Ninja your changes should be testable so it's more useful to build
-and run `ninja_test` when developing.
+Ninja your changes should be testable so it's more useful to build and
+run `ninja_test` when developing.
+
+### Bootstrapping
+
+Ninja is built using itself. To bootstrap the first binary, run the
+configure script as `./configure.py --bootstrap`. This first compiles
+all non-test source files together, then re-builds Ninja using itself.
+You should end up with a `ninja` binary (or `ninja.exe`) in the source root.
+
+#### Windows
+
+On Windows, you'll need to install Python to run `configure.py`, and
+run everything under a Visual Studio Tools Command Prompt (or after
+running `vcvarsall` in a normal command prompt). See below if you
+want to use mingw or some other compiler instead using Visual Studio.
### Adjusting build flags
./ninja_test
gcov build/*.o
-Look at the generated `.gcov` files directly, or use your favorit gcov viewer.
+Look at the generated `.gcov` files directly, or use your favorite gcov viewer.
### Using afl-fuzz
doc/manual.asciidoc included in the distribution -- for background
and more details.
-To build, run ./configure.py --bootstrap. It first compiles all non-test
-source files together, then re-builds Ninja using itself. You should
-end up with a 'ninja' binary in the source root.
-
-Run './configure.py --help' for more configuration options.
+Binaries for Linux, Mac, and Windows are available at
+ https://github.com/martine/ninja/releases
Run './ninja -h' for Ninja help.
+To build your own binary, on many platforms it should be sufficient to
+just run `./configure.py --bootstrap`; for more details see HACKING.md.
+(Also read that before making changes to Ninja, as it has advice.)
+
Installation is not necessary because the only required file is is the
resulting ninja binary. However, to enable features like Bash
completion and Emacs and Vim editing modes, some files in misc/ must be
import subprocess
import sys
-sys.path.insert(0, 'misc')
+sourcedir = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(sourcedir, 'misc'))
import ninja_syntax
def _expand_paths(self, paths):
"""Expand $vars in an array of paths, e.g. from a 'build' block."""
paths = ninja_syntax.as_list(paths)
- return ' '.join(map(self._expand, paths))
+ return ' '.join(map(self._shell_escape, (map(self._expand, paths))))
def _expand(self, str, local_vars={}):
"""Expand $vars in a string."""
return ninja_syntax.expand(str, self.vars, local_vars)
+ def _shell_escape(self, path):
+ """Quote paths containing spaces."""
+ return '"%s"' % path if ' ' in path else path
+
def _run_command(self, cmdline):
"""Run a subcommand, quietly. Prints the full command on error."""
try:
objext = '.obj'
def src(filename):
- return os.path.join('src', filename)
+ return os.path.join('$sourcedir', 'src', filename)
def built(filename):
return os.path.join('$builddir', filename)
def doc(filename):
- return os.path.join('doc', filename)
+ return os.path.join('$sourcedir', 'doc', filename)
def cc(name, **kwargs):
return n.build(built(name + objext), 'cxx', src(name + '.c'), **kwargs)
def cxx(name, **kwargs):
return exe
return name
+n.variable('sourcedir', sourcedir)
n.variable('builddir', 'build')
n.variable('cxx', CXX)
if platform.is_msvc():
if platform.supports_ninja_browse():
cflags.append('-DNINJA_HAVE_BROWSE')
+# Search for generated headers relative to build dir.
+cflags.append('-I.')
+
def shell_escape(str):
"""Escape str such that it's interpreted as a single argument by
the shell."""
if platform.supports_ninja_browse():
n.comment('browse_py.h is used to inline browse.py.')
n.rule('inline',
- command='src/inline.sh $varname < $in > $out',
+ command='"%s"' % src('inline.sh') + ' $varname < $in > $out',
description='INLINE $out')
n.build(built('browse_py.h'), 'inline', src('browse.py'),
- implicit='src/inline.sh',
+ implicit=src('inline.sh'),
variables=[('varname', 'kBrowsePy')])
n.newline()
if not host.is_mingw():
n.comment('Regenerate build files if build script changes.')
n.rule('configure',
- command='${configure_env}%s configure.py $configure_args' %
+ command='${configure_env}%s $sourcedir/configure.py $configure_args' %
options.with_python,
generator=True)
n.build('build.ninja', 'configure',
- implicit=['configure.py', os.path.normpath('misc/ninja_syntax.py')])
+ implicit=['$sourcedir/configure.py',
+ os.path.normpath('$sourcedir/misc/ninja_syntax.py')])
n.newline()
n.default(ninja)
#include <stdlib.h>
#include <unistd.h>
-#include "../build/browse_py.h"
+#include "build/browse_py.h"
void RunBrowsePython(State* state, const char* ninja_command,
const char* initial_target) {
#ifdef _WIN32
if (deps_type == "msvc") {
CLParser parser;
- result->output = parser.Parse(result->output, deps_prefix);
+ string output;
+ if (!parser.Parse(result->output, deps_prefix, &output, err))
+ return false;
+ result->output = output;
for (set<string>::iterator i = parser.includes_.begin();
i != parser.includes_.end(); ++i) {
// ~0 is assuming that with MSVC-parsed headers, it's ok to always make
return Join(rel_list, '/');
}
-string IncludesNormalize::Normalize(const string& input,
- const char* relative_to) {
- char copy[_MAX_PATH];
+bool IncludesNormalize::Normalize(const string& input, const char* relative_to,
+ string* result, string* err) {
+ char copy[_MAX_PATH + 1];
size_t len = input.size();
+ if (len > _MAX_PATH) {
+ *err = "path too long";
+ return false;
+ }
strncpy(copy, input.c_str(), input.size() + 1);
- string err;
unsigned int slash_bits;
- if (!CanonicalizePath(copy, &len, &slash_bits, &err))
- Warning("couldn't canonicalize '%s': %s\n", input.c_str(), err.c_str());
+ if (!CanonicalizePath(copy, &len, &slash_bits, err))
+ return false;
StringPiece partially_fixed(copy, len);
string curdir;
curdir = AbsPath(".");
relative_to = curdir.c_str();
}
- if (!SameDrive(partially_fixed, relative_to))
- return partially_fixed.AsString();
- return Relativize(partially_fixed, relative_to);
+ if (!SameDrive(partially_fixed, relative_to)) {
+ *result = partially_fixed.AsString();
+ return true;
+ }
+ *result = Relativize(partially_fixed, relative_to);
+ return true;
}
/// Normalize by fixing slashes style, fixing redundant .. and . and makes the
/// path relative to |relative_to|.
- static string Normalize(const string& input, const char* relative_to);
+ static bool Normalize(const string& input, const char* relative_to,
+ string* result, string* err);
};
#include "includes_normalize.h"
+#include <algorithm>
+
#include <direct.h>
#include "test.h"
#include "util.h"
-TEST(IncludesNormalize, Simple) {
- EXPECT_EQ("b", IncludesNormalize::Normalize("a\\..\\b", NULL));
- EXPECT_EQ("b", IncludesNormalize::Normalize("a\\../b", NULL));
- EXPECT_EQ("a/b", IncludesNormalize::Normalize("a\\.\\b", NULL));
- EXPECT_EQ("a/b", IncludesNormalize::Normalize("a\\./b", NULL));
-}
-
namespace {
string GetCurDir() {
return parts[parts.size() - 1];
}
+string NormalizeAndCheckNoError(const std::string& input) {
+ string result, err;
+ EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), NULL, &result, &err));
+ EXPECT_EQ("", err);
+ return result;
+}
+
+string NormalizeRelativeAndCheckNoError(const std::string& input,
+ const std::string& relative_to) {
+ string result, err;
+ EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), relative_to.c_str(),
+ &result, &err));
+ EXPECT_EQ("", err);
+ return result;
+}
+
} // namespace
+TEST(IncludesNormalize, Simple) {
+ EXPECT_EQ("b", NormalizeAndCheckNoError("a\\..\\b"));
+ EXPECT_EQ("b", NormalizeAndCheckNoError("a\\../b"));
+ EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\.\\b"));
+ EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\./b"));
+}
+
TEST(IncludesNormalize, WithRelative) {
string currentdir = GetCurDir();
- EXPECT_EQ("c", IncludesNormalize::Normalize("a/b/c", "a/b"));
- EXPECT_EQ("a", IncludesNormalize::Normalize(IncludesNormalize::AbsPath("a"),
- NULL));
+ EXPECT_EQ("c", NormalizeRelativeAndCheckNoError("a/b/c", "a/b"));
+ EXPECT_EQ("a", NormalizeAndCheckNoError(IncludesNormalize::AbsPath("a")));
EXPECT_EQ(string("../") + currentdir + string("/a"),
- IncludesNormalize::Normalize("a", "../b"));
+ NormalizeRelativeAndCheckNoError("a", "../b"));
EXPECT_EQ(string("../") + currentdir + string("/a/b"),
- IncludesNormalize::Normalize("a/b", "../c"));
- EXPECT_EQ("../../a", IncludesNormalize::Normalize("a", "b/c"));
- EXPECT_EQ(".", IncludesNormalize::Normalize("a", "a"));
+ NormalizeRelativeAndCheckNoError("a/b", "../c"));
+ EXPECT_EQ("../../a", NormalizeRelativeAndCheckNoError("a", "b/c"));
+ EXPECT_EQ(".", NormalizeRelativeAndCheckNoError("a", "a"));
}
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("b", NormalizeAndCheckNoError("Abc\\..\\b"));
+ EXPECT_EQ("BdEf", NormalizeAndCheckNoError("Abc\\..\\BdEf"));
+ EXPECT_EQ("A/b", NormalizeAndCheckNoError("A\\.\\b"));
+ EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\./b"));
+ EXPECT_EQ("A/B", NormalizeAndCheckNoError("A\\.\\B"));
+ EXPECT_EQ("A/B", NormalizeAndCheckNoError("A\\./B"));
}
TEST(IncludesNormalize, Join) {
TEST(IncludesNormalize, DifferentDrive) {
EXPECT_EQ("stuff.h",
- IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "p:\\vs08"));
+ NormalizeRelativeAndCheckNoError("p:\\vs08\\stuff.h", "p:\\vs08"));
EXPECT_EQ("stuff.h",
- IncludesNormalize::Normalize("P:\\Vs08\\stuff.h", "p:\\vs08"));
+ NormalizeRelativeAndCheckNoError("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"));
+ NormalizeRelativeAndCheckNoError("p:\\vs08\\stuff.h", "c:\\vs08"));
+ EXPECT_EQ("P:/vs08/stufF.h", NormalizeRelativeAndCheckNoError(
+ "P:\\vs08\\stufF.h", "D:\\stuff/things"));
+ EXPECT_EQ("P:/vs08/stuff.h", NormalizeRelativeAndCheckNoError(
+ "P:/vs08\\stuff.h", "D:\\stuff/things"));
EXPECT_EQ("P:/wee/stuff.h",
- IncludesNormalize::Normalize("P:/vs08\\../wee\\stuff.h",
- "D:\\stuff/things"));
+ NormalizeRelativeAndCheckNoError("P:/vs08\\../wee\\stuff.h",
+ "D:\\stuff/things"));
+}
+
+TEST(IncludesNormalize, LongInvalidPath) {
+ const char kLongInputString[] =
+ "C:\\Program Files (x86)\\Microsoft Visual Studio "
+ "12.0\\VC\\INCLUDEwarning #31001: The dll for reading and writing the "
+ "pdb (for example, mspdb110.dll) could not be found on your path. This "
+ "is usually a configuration error. Compilation will continue using /Z7 "
+ "instead of /Zi, but expect a similar error when you link your program.";
+ // Too long, won't be canonicalized. Ensure doesn't crash.
+ string result, err;
+ EXPECT_FALSE(
+ IncludesNormalize::Normalize(kLongInputString, NULL, &result, &err));
+ EXPECT_EQ("path too long", err);
+
+ const char kExactlyMaxPath[] =
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "012345678\\"
+ "0123456789";
+ std::string forward_slashes(kExactlyMaxPath);
+ std::replace(forward_slashes.begin(), forward_slashes.end(), '\\', '/');
+ // Make sure a path that's exactly _MAX_PATH long is canonicalized.
+ EXPECT_EQ(forward_slashes,
+ NormalizeAndCheckNoError(kExactlyMaxPath));
}
#include "msvc_helper.h"
#include <algorithm>
+#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
EndsWith(line, ".cpp");
}
-string CLParser::Parse(const string& output, const string& deps_prefix) {
- string filtered_output;
-
+bool CLParser::Parse(const string& output, const string& deps_prefix,
+ string* filtered_output, string* err) {
// Loop over all lines in the output to process them.
+ assert(&output != filtered_output);
size_t start = 0;
while (start < output.size()) {
size_t end = output.find_first_of("\r\n", start);
string include = FilterShowIncludes(line, deps_prefix);
if (!include.empty()) {
- include = IncludesNormalize::Normalize(include, NULL);
- if (!IsSystemInclude(include))
- includes_.insert(include);
+ string normalized;
+ if (!IncludesNormalize::Normalize(include, NULL, &normalized, err))
+ return false;
+ if (!IsSystemInclude(normalized))
+ includes_.insert(normalized);
} else if (FilterInputFilename(line)) {
// Drop it.
// TODO: if we support compiling multiple output files in a single
// cl.exe invocation, we should stash the filename.
} else {
- filtered_output.append(line);
- filtered_output.append("\n");
+ filtered_output->append(line);
+ filtered_output->append("\n");
}
if (end < output.size() && output[end] == '\r')
start = end;
}
- return filtered_output;
+ return true;
}
int CLWrapper::Run(const string& command, string* output) {
/// Exposed for testing.
static bool FilterInputFilename(string line);
- /// Parse the full output of cl, returning the output (if any) that
- /// should printed.
- string Parse(const string& output, const string& deps_prefix);
+ /// Parse the full output of cl, filling filtered_output with the text that
+ /// should be printed (if any). Returns true on success, or false with err
+ /// filled. output must not be the same object as filtered_object.
+ bool Parse(const string& output, const string& deps_prefix,
+ string* filtered_output, string* err);
set<string> includes_;
};
if (output_filename) {
CLParser parser;
- output = parser.Parse(output, deps_prefix);
+ string err;
+ if (!parser.Parse(output, deps_prefix, &output, &err))
+ Fatal("%s\n", err.c_str());
WriteDepFileOrDie(output_filename, parser);
}
TEST(CLParserTest, ParseSimple) {
CLParser parser;
- string output = parser.Parse(
+ string output, err;
+ ASSERT_TRUE(parser.Parse(
"foo\r\n"
"Note: inc file prefix: foo.h\r\n"
"bar\r\n",
- "Note: inc file prefix:");
+ "Note: inc file prefix:", &output, &err));
ASSERT_EQ("foo\nbar\n", output);
ASSERT_EQ(1u, parser.includes_.size());
TEST(CLParserTest, ParseFilenameFilter) {
CLParser parser;
- string output = parser.Parse(
+ string output, err;
+ ASSERT_TRUE(parser.Parse(
"foo.cc\r\n"
"cl: warning\r\n",
- "");
+ "", &output, &err));
ASSERT_EQ("cl: warning\n", output);
}
TEST(CLParserTest, ParseSystemInclude) {
CLParser parser;
- string output = parser.Parse(
+ string output, err;
+ ASSERT_TRUE(parser.Parse(
"Note: including file: c:\\Program Files\\foo.h\r\n"
"Note: including file: d:\\Microsoft Visual Studio\\bar.h\r\n"
"Note: including file: path.h\r\n",
- "");
+ "", &output, &err));
// We should have dropped the first two includes because they look like
// system headers.
ASSERT_EQ("", output);
TEST(CLParserTest, DuplicatedHeader) {
CLParser parser;
- string output = parser.Parse(
+ string output, err;
+ ASSERT_TRUE(parser.Parse(
"Note: including file: foo.h\r\n"
"Note: including file: bar.h\r\n"
"Note: including file: foo.h\r\n",
- "");
+ "", &output, &err));
// We should have dropped one copy of foo.h.
ASSERT_EQ("", output);
ASSERT_EQ(2u, parser.includes_.size());
TEST(CLParserTest, DuplicatedHeaderPathConverted) {
CLParser parser;
- string output = parser.Parse(
+ string output, err;
+ ASSERT_TRUE(parser.Parse(
"Note: including file: sub/foo.h\r\n"
"Note: including file: bar.h\r\n"
"Note: including file: sub\\foo.h\r\n",
- "");
+ "", &output, &err));
// We should have dropped one copy of foo.h.
ASSERT_EQ("", output);
ASSERT_EQ(2u, parser.includes_.size());
#include "util.h"
-const char* kNinjaVersion = "1.5.3.git";
+const char* kNinjaVersion = "1.6.0.git";
void ParseVersion(const string& version, int* major, int* minor) {
size_t end = version.find('.');