summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--HACKING8
-rwxr-xr-xbootstrap.py4
-rwxr-xr-xconfigure.py41
-rw-r--r--doc/manual.asciidoc13
-rw-r--r--misc/ninja.vim8
-rw-r--r--src/build.cc14
-rw-r--r--src/build.h2
-rw-r--r--src/build_log.cc64
-rw-r--r--src/build_log.h7
-rw-r--r--src/build_log_test.cc12
-rw-r--r--src/build_test.cc6
-rw-r--r--src/canon_perftest.cc38
-rw-r--r--src/depfile_parser.cc16
-rw-r--r--src/depfile_parser.h5
-rw-r--r--src/depfile_parser.in.cc2
-rw-r--r--src/depfile_parser_test.cc13
-rw-r--r--src/explain.h5
-rw-r--r--src/graph.cc20
-rw-r--r--src/hash_collision_bench.cc43
-rw-r--r--src/lexer.h4
-rw-r--r--src/metrics.h5
-rw-r--r--src/parsers.cc2
-rw-r--r--src/parsers_test.cc17
-rw-r--r--src/test.cc5
-rw-r--r--src/test.h1
-rw-r--r--src/util.h1
26 files changed, 287 insertions, 69 deletions
diff --git a/HACKING b/HACKING
index 786524f..4e01d49 100644
--- a/HACKING
+++ b/HACKING
@@ -2,6 +2,14 @@ Adjusting build flags:
CFLAGS=-O3 ./configure.py
and rebuild.
+Building tests requires gtest, to get it:
+ - On older Ubuntus you can apt-get install libgtest.
+ - On newer Ubuntus it's only distributed as source;
+ 1) apt-get install libgtest-dev
+ 2) ./configure --with-gtest=/usr/src/gtest
+ - Otherwise you need to download it, unpack it, and pass --with-gtest
+ as appropriate.
+
Test-driven development:
Set your build command to
./ninja ninja_test && ./ninja_test --gtest_filter=MyTest.Name
diff --git a/bootstrap.py b/bootstrap.py
index ad6f1eb..9ac46ba 100755
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -21,6 +21,8 @@ import errno
import shlex
import subprocess
+os.chdir(os.path.dirname(os.path.abspath(__file__)))
+
parser = OptionParser()
parser.add_option('--verbose', action='store_true',
help='enable verbose build',)
@@ -51,6 +53,8 @@ sources = []
for src in glob.glob('src/*.cc'):
if src.endswith('test.cc') or src.endswith('.in.cc'):
continue
+ if src.endswith('bench.cc'):
+ continue
filename = os.path.basename(src)
if filename == 'browse.cc': # Depends on generated header.
diff --git a/configure.py b/configure.py
index 312fbcb..5329708 100755
--- a/configure.py
+++ b/configure.py
@@ -74,8 +74,9 @@ n.comment('The arguments passed to configure.py, for rerunning it.')
n.variable('configure_args', ' '.join(sys.argv[1:]))
env_keys = set(['CXX', 'AR', 'CFLAGS', 'LDFLAGS'])
configure_env = dict((k, os.environ[k]) for k in os.environ if k in env_keys)
-n.variable('configure_env',
- ' '.join([k + '=' + configure_env[k] for k in configure_env]))
+if configure_env:
+ config_str = ' '.join([k + '=' + configure_env[k] for k in configure_env])
+ n.variable('configure_env', config_str + '$ ')
n.newline()
CXX = configure_env.get('CXX', 'g++')
@@ -122,7 +123,7 @@ else:
'-fno-rtti',
'-fno-exceptions',
'-fvisibility=hidden', '-pipe',
- "'-DNINJA_PYTHON=\"%s\"'" % (options.with_python,)]
+ "-DNINJA_PYTHON=\"%s\"" % options.with_python]
if options.debug:
cflags += ['-D_GLIBCXX_DEBUG', '-D_GLIBCXX_DEBUG_PEDANTIC']
else:
@@ -146,12 +147,21 @@ else:
elif options.profile == 'pprof':
libs.append('-lprofiler')
+def shell_escape(str):
+ """Escape str such that it's interpreted as a single argument by the shell."""
+ # This isn't complete, but it's just enough to make NINJA_PYTHON work.
+ # TODO: do the appropriate thing for Windows-style cmd here, perhaps by
+ # just returning the input string.
+ if '"' in str:
+ return "'%s'" % str.replace("'", "\\'")
+ return str
+
if 'CFLAGS' in configure_env:
cflags.append(configure_env['CFLAGS'])
-n.variable('cflags', ' '.join(cflags))
+n.variable('cflags', ' '.join(shell_escape(flag) for flag in cflags))
if 'LDFLAGS' in configure_env:
ldflags.append(configure_env['LDFLAGS'])
-n.variable('ldflags', ' '.join(ldflags))
+n.variable('ldflags', ' '.join(shell_escape(flag) for flag in ldflags))
n.newline()
if platform == 'windows':
@@ -313,17 +323,20 @@ n.newline()
all_targets += ninja_test
-n.comment('Perftest executables.')
+n.comment('Ancilliary executables.')
objs = cxx('parser_perftest')
-parser_perftest = n.build(binary('parser_perftest'), 'link', objs,
- implicit=ninja_lib,
- variables=[('libs', libs)])
+all_targets += n.build(binary('parser_perftest'), 'link', objs,
+ implicit=ninja_lib, variables=[('libs', libs)])
objs = cxx('build_log_perftest')
-build_log_perftest = n.build(binary('build_log_perftest'), 'link', objs,
- implicit=ninja_lib,
- variables=[('libs', libs)])
+all_targets += n.build(binary('build_log_perftest'), 'link', objs,
+ implicit=ninja_lib, variables=[('libs', libs)])
+objs = cxx('canon_perftest')
+all_targets += n.build(binary('canon_perftest'), 'link', objs,
+ implicit=ninja_lib, variables=[('libs', libs)])
+objs = cxx('hash_collision_bench')
+all_targets += n.build(binary('hash_collision_bench'), 'link', objs,
+ implicit=ninja_lib, variables=[('libs', libs)])
n.newline()
-all_targets += parser_perftest + build_log_perftest
n.comment('Generate a graph using the "graph" tool.')
n.rule('gendot',
@@ -362,7 +375,7 @@ n.newline()
if host != '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 configure.py $configure_args' %
options.with_python,
generator=True)
n.build('build.ninja', 'configure',
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index d9a6188..e7983be 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -123,7 +123,7 @@ but they are not part of make itself.)
you can print e.g. `CC foo.o` instead of a long command line while
building.
-* Builds are always run in parallel, based by defaulton the number of
+* Builds are always run in parallel, based by default on the number of
CPUs your system has. Underspecified build dependencies will result
in incorrect builds.
@@ -143,7 +143,7 @@ full details of the implementation -- like how to get C header
interdependencies correct and fast when using MSVC's compiler -- is
not yet complete.
-If your project is small, Ninja's speed impact is likely unnoticable.
+If your project is small, Ninja's speed impact is likely unnoticeable.
Some build timing numbers are included below. (However, even for
small projects it sometimes turns out that Ninja's limited syntax
forces simpler build rules that result in faster builds.) Another way
@@ -151,7 +151,7 @@ to say this is that if you're happy with the edit-compile cycle time
of your project already then Ninja won't help.
There are many other build systems that are more user-friendly or
-featureful than Ninja itself. For some recomendations: the Ninja
+featureful than Ninja itself. For some recommendations: the Ninja
author found http://gittup.org/tup/[the tup build system] influential
in Ninja's design, and thinks https://github.com/apenwarr/redo[redo]'s
design is quite clever.
@@ -359,9 +359,12 @@ command to run. (In the context of a rule, the `command` variable is
special and defines the command to run. A full list of special
variables is provided in <<ref_rule,the reference>>.)
-Within the context of a rule, two additional special variables are
+Within the context of a rule, three additional special variables are
available: `$in` expands to the list of input files (`foo.c`) and
-`$out` to the output file (`foo.o`) for the command.
+`$out` to the output file (`foo.o`) for the command. For use with
+`$rspfile_content`, there is also `$in_newline`, which is the same as
+`$in`, except that multiple inputs are separated by `\n`, rather than
+spaces.
Build statements
diff --git a/misc/ninja.vim b/misc/ninja.vim
index f8ba783..51a95c2 100644
--- a/misc/ninja.vim
+++ b/misc/ninja.vim
@@ -1,10 +1,10 @@
" ninja build file syntax.
" Language: ninja build file as described at
" http://martine.github.com/ninja/manual.html
-" Version: 1.1
-" Last Change: 2012/05/13
+" Version: 1.2
+" Last Change: 2012/06/01
" Maintainer: Nicolas Weber <nicolasweber@gmx.de>
-" Version 1.1 of this script is in the upstream vim repository and will be
+" Version 1.2 of this script is in the upstream vim repository and will be
" included in the next vim release. If you change this, please send your change
" upstream.
@@ -18,7 +18,7 @@ endif
syn case match
-syn match ninjaComment /#.*/
+syn match ninjaComment /#.*/ contains=@Spell
" Toplevel statements are the ones listed here and
" toplevel variable assignments (ident '=' value).
diff --git a/src/build.cc b/src/build.cc
index 157442d..65aa6a9 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -16,6 +16,7 @@
#include <assert.h>
#include <stdio.h>
+#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
@@ -23,7 +24,6 @@
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/time.h>
-#include <sys/termios.h>
#endif
#include "build_log.h"
@@ -36,7 +36,6 @@
BuildStatus::BuildStatus(const BuildConfig& config)
: config_(config),
start_time_millis_(GetTimeMillis()),
- last_update_millis_(start_time_millis_),
started_edges_(0), finished_edges_(0), total_edges_(0),
have_blank_line_(true), progress_status_format_(NULL) {
#ifndef _WIN32
@@ -85,7 +84,6 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
RunningEdgeMap::iterator i = running_edges_.find(edge);
*start_time = i->second;
*end_time = (int)(now - start_time_millis_);
- int total_time = end_time - start_time;
running_edges_.erase(i);
if (config_.verbosity == BuildConfig::QUIET)
@@ -94,15 +92,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
if (smart_terminal_)
PrintStatus(edge);
- if (success && output.empty()) {
- if (!smart_terminal_) {
- if (total_time > 5*1000) {
- printf("%.1f%% %d/%d\n", finished_edges_ * 100 / (float)total_edges_,
- finished_edges_, total_edges_);
- last_update_millis_ = now;
- }
- }
- } else {
+ if (!success || !output.empty()) {
if (smart_terminal_)
printf("\n");
diff --git a/src/build.h b/src/build.h
index 036325d..c9ee4ac 100644
--- a/src/build.h
+++ b/src/build.h
@@ -176,8 +176,6 @@ struct BuildStatus {
/// Time the build started.
int64_t start_time_millis_;
- /// Time we last printed an update.
- int64_t last_update_millis_;
int started_edges_, finished_edges_, total_edges_;
diff --git a/src/build_log.cc b/src/build_log.cc
index 5803ada..97c3344 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -37,10 +37,57 @@
namespace {
const char kFileSignature[] = "# ninja log v%d\n";
-const int kCurrentVersion = 4;
+const int kCurrentVersion = 5;
+
+// 64bit MurmurHash2, by Austin Appleby
+#if defined(_MSC_VER)
+#define BIG_CONSTANT(x) (x)
+#else // defined(_MSC_VER)
+#define BIG_CONSTANT(x) (x##LLU)
+#endif // !defined(_MSC_VER)
+inline
+uint64_t MurmurHash64A(const void* key, int len) {
+ static const uint64_t seed = 0xDECAFBADDECAFBADull;
+ const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995);
+ const int r = 47;
+ uint64_t h = seed ^ (len * m);
+ const uint64_t * data = (const uint64_t *)key;
+ const uint64_t * end = data + (len/8);
+ while(data != end) {
+ uint64_t k = *data++;
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+ h ^= k;
+ h *= m;
+ }
+ const unsigned char* data2 = (const unsigned char*)data;
+ switch(len & 7)
+ {
+ case 7: h ^= uint64_t(data2[6]) << 48;
+ case 6: h ^= uint64_t(data2[5]) << 40;
+ case 5: h ^= uint64_t(data2[4]) << 32;
+ case 4: h ^= uint64_t(data2[3]) << 24;
+ case 3: h ^= uint64_t(data2[2]) << 16;
+ case 2: h ^= uint64_t(data2[1]) << 8;
+ case 1: h ^= uint64_t(data2[0]);
+ h *= m;
+ };
+ h ^= h >> r;
+ h *= m;
+ h ^= h >> r;
+ return h;
+}
+#undef BIG_CONSTANT
+
} // namespace
+// static
+uint64_t BuildLog::LogEntry::HashCommand(StringPiece command) {
+ return MurmurHash64A(command.str_, command.len_);
+}
+
BuildLog::BuildLog()
: log_file_(NULL), config_(NULL), needs_recompaction_(false) {}
@@ -95,7 +142,7 @@ void BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
log_entry->output = path;
log_.insert(Log::value_type(log_entry->output, log_entry));
}
- log_entry->command = command;
+ log_entry->command_hash = LogEntry::HashCommand(command);
log_entry->start_time = start_time;
log_entry->end_time = end_time;
log_entry->restat_mtime = restat_mtime;
@@ -239,7 +286,14 @@ bool BuildLog::Load(const string& path, string* err) {
entry->start_time = start_time;
entry->end_time = end_time;
entry->restat_mtime = restat_mtime;
- entry->command = string(start, end - start);
+ if (log_version >= 5) {
+ char c = *end; *end = '\0';
+ entry->command_hash = (uint64_t)strtoull(start, NULL, 10);
+ *end = c;
+ }
+ else
+ entry->command_hash = LogEntry::HashCommand(StringPiece(start,
+ end - start));
}
// Decide whether it's time to rebuild the log:
@@ -267,9 +321,9 @@ BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
}
void BuildLog::WriteEntry(FILE* f, const LogEntry& entry) {
- fprintf(f, "%d\t%d\t%ld\t%s\t%s\n",
+ fprintf(f, "%d\t%d\t%ld\t%s\t%llu\n",
entry.start_time, entry.end_time, (long) entry.restat_mtime,
- entry.output.c_str(), entry.command.c_str());
+ entry.output.c_str(), entry.command_hash);
}
bool BuildLog::Recompact(const string& path, string* err) {
diff --git a/src/build_log.h b/src/build_log.h
index da8e726..d3994ff 100644
--- a/src/build_log.h
+++ b/src/build_log.h
@@ -22,6 +22,7 @@ using namespace std;
#include "hash_map.h"
#include "timestamp.h"
+#include "util.h"
struct BuildConfig;
struct Edge;
@@ -49,14 +50,16 @@ struct BuildLog {
struct LogEntry {
string output;
- string command;
+ uint64_t command_hash;
int start_time;
int end_time;
TimeStamp restat_mtime;
+ static uint64_t HashCommand(StringPiece command);
+
// Used by tests.
bool operator==(const LogEntry& o) {
- return output == o.output && command == o.command &&
+ return output == o.output && command_hash == o.command_hash &&
start_time == o.start_time && end_time == o.end_time &&
restat_mtime == o.restat_mtime;
}
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index 199e016..afd3b81 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -110,7 +110,7 @@ TEST_F(BuildLogTest, DoubleEntry) {
BuildLog::LogEntry* e = log.LookupByOutput("out");
ASSERT_TRUE(e);
- ASSERT_EQ("command def", e->command);
+ ASSERT_NO_FATAL_FAILURE(AssertHash("command def", e->command_hash));
}
TEST_F(BuildLogTest, Truncate) {
@@ -164,7 +164,7 @@ TEST_F(BuildLogTest, UpgradeV3) {
ASSERT_EQ(123, e->start_time);
ASSERT_EQ(456, e->end_time);
ASSERT_EQ(0, e->restat_mtime);
- ASSERT_EQ("command", e->command);
+ ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
}
TEST_F(BuildLogTest, SpacesInOutputV4) {
@@ -183,7 +183,7 @@ TEST_F(BuildLogTest, SpacesInOutputV4) {
ASSERT_EQ(123, e->start_time);
ASSERT_EQ(456, e->end_time);
ASSERT_EQ(456, e->restat_mtime);
- ASSERT_EQ("command", e->command);
+ ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
}
TEST_F(BuildLogTest, DuplicateVersionHeader) {
@@ -207,14 +207,14 @@ TEST_F(BuildLogTest, DuplicateVersionHeader) {
ASSERT_EQ(123, e->start_time);
ASSERT_EQ(456, e->end_time);
ASSERT_EQ(456, e->restat_mtime);
- ASSERT_EQ("command", e->command);
+ ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
e = log.LookupByOutput("out2");
ASSERT_TRUE(e);
ASSERT_EQ(456, e->start_time);
ASSERT_EQ(789, e->end_time);
ASSERT_EQ(789, e->restat_mtime);
- ASSERT_EQ("command2", e->command);
+ ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
}
TEST_F(BuildLogTest, VeryLongInputLine) {
@@ -242,5 +242,5 @@ TEST_F(BuildLogTest, VeryLongInputLine) {
ASSERT_EQ(456, e->start_time);
ASSERT_EQ(789, e->end_time);
ASSERT_EQ(789, e->restat_mtime);
- ASSERT_EQ("command2", e->command);
+ ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
}
diff --git a/src/build_test.cc b/src/build_test.cc
index c45f2b3..23f1909 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -953,8 +953,10 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
// (to simulate a change in the command line between 2 builds)
BuildLog::LogEntry * log_entry = build_log_.LookupByOutput("out");
ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ("cat out.rsp > out;rspfile=Original very long command", log_entry->command);
- log_entry->command = "cat out.rsp > out;rspfile=Altered very long command";
+ ASSERT_NO_FATAL_FAILURE(AssertHash(
+ "cat out.rsp > out;rspfile=Original very long command",
+ log_entry->command_hash));
+ log_entry->command_hash++; // Change the command hash to something else.
// Now expect the target to be rebuilt
commands_ran_.clear();
state_.Reset();
diff --git a/src/canon_perftest.cc b/src/canon_perftest.cc
new file mode 100644
index 0000000..aaec935
--- /dev/null
+++ b/src/canon_perftest.cc
@@ -0,0 +1,38 @@
+#include "util.h"
+
+const char kPath[] =
+ "../../third_party/WebKit/Source/WebCore/"
+ "platform/leveldb/LevelDBWriteBatch.cpp";
+
+int main() {
+ vector<int> times;
+ string err;
+
+ char buf[200];
+ int len = strlen(kPath);
+ strcpy(buf, kPath);
+
+ for (int j = 0; j < 5; ++j) {
+ const int kNumRepetitions = 2000000;
+ int64_t start = GetTimeMillis();
+ for (int i = 0; i < kNumRepetitions; ++i) {
+ CanonicalizePath(buf, &len, &err);
+ }
+ int delta = (int)(GetTimeMillis() - start);
+ times.push_back(delta);
+ }
+
+ int min = times[0];
+ int max = times[0];
+ float total = 0;
+ for (size_t i = 0; i < times.size(); ++i) {
+ total += times[i];
+ if (times[i] < min)
+ min = times[i];
+ else if (times[i] > max)
+ max = times[i];
+ }
+
+ printf("min %dms max %dms avg %.1fms\n",
+ min, max, total / times.size());
+}
diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc
index 261893f..54b934c 100644
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -54,7 +54,7 @@ bool DepfileParser::Parse(string* content, string* err) {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 128, 128, 128, 128, 128,
+ 128, 128, 0, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 0, 0, 0, 0, 0,
0, 128, 128, 128, 128, 128, 128, 128,
@@ -84,18 +84,21 @@ bool DepfileParser::Parse(string* content, string* err) {
};
yych = *in;
- if (yych <= '\\') {
- if (yych <= ':') {
+ if (yych <= '[') {
+ if (yych <= '*') {
if (yych <= 0x00) goto yy6;
- if (yych <= '*') goto yy8;
- goto yy4;
+ if (yych <= '\'') goto yy8;
+ if (yych <= ')') goto yy4;
+ goto yy8;
} else {
+ if (yych <= ':') goto yy4;
if (yych <= '@') goto yy8;
if (yych <= 'Z') goto yy4;
- if (yych <= '[') goto yy8;
+ goto yy8;
}
} else {
if (yych <= '`') {
+ if (yych <= '\\') goto yy2;
if (yych == '_') goto yy4;
goto yy8;
} else {
@@ -104,6 +107,7 @@ bool DepfileParser::Parse(string* content, string* err) {
goto yy8;
}
}
+yy2:
++in;
if ((yych = *in) <= '$') {
if (yych <= '\n') {
diff --git a/src/depfile_parser.h b/src/depfile_parser.h
index c900956..1e6ebb5 100644
--- a/src/depfile_parser.h
+++ b/src/depfile_parser.h
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#ifndef NINJA_DEPFILE_PARSER_H_
+#define NINJA_DEPFILE_PARSER_H_
+
#include <string>
#include <vector>
using namespace std;
@@ -28,3 +31,5 @@ struct DepfileParser {
StringPiece out_;
vector<StringPiece> ins_;
};
+
+#endif // NINJA_DEPFILE_PARSER_H_
diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc
index 5e073df..8c415b9 100644
--- a/src/depfile_parser.in.cc
+++ b/src/depfile_parser.in.cc
@@ -68,7 +68,7 @@ bool DepfileParser::Parse(string* content, string* err) {
*out++ = yych;
continue;
}
- [a-zA-Z0-9+,/_:.~-]+ {
+ [a-zA-Z0-9+,/_:.~()-]+ {
// Got a span of plain text.
int len = in - start;
// Need to shift it over if we're overwriting backslashes.
diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc
index 9094283..fd76ae7 100644
--- a/src/depfile_parser_test.cc
+++ b/src/depfile_parser_test.cc
@@ -103,7 +103,18 @@ TEST_F(DepfileParserTest, Escapes) {
ASSERT_EQ(0u, parser_.ins_.size());
}
-TEST_F(DepfileParserTest, UnifyMultupleOutputs) {
+TEST_F(DepfileParserTest, SpecialChars) {
+ string err;
+ EXPECT_TRUE(Parse(
+"C:/Program\\ Files\\ (x86)/Microsoft\\ crtdefs.h:",
+ &err));
+ ASSERT_EQ("", err);
+ EXPECT_EQ("C:/Program Files (x86)/Microsoft crtdefs.h",
+ parser_.out_.AsString());
+ ASSERT_EQ(0u, parser_.ins_.size());
+}
+
+TEST_F(DepfileParserTest, UnifyMultipleOutputs) {
// check that multiple duplicate targets are properly unified
string err;
EXPECT_TRUE(Parse("foo foo: x y z", &err));
diff --git a/src/explain.h b/src/explain.h
index 021f0d4..d4f6a6c 100644
--- a/src/explain.h
+++ b/src/explain.h
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#ifndef NINJA_EXPLAIN_H_
+#define NINJA_EXPLAIN_H_
+
#include <stdio.h>
#define EXPLAIN(fmt, ...) { \
@@ -20,3 +23,5 @@
}
extern bool g_explaining;
+
+#endif // NINJA_EXPLAIN_H_
diff --git a/src/graph.cc b/src/graph.cc
index 3531e86..5418ecf 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -157,7 +157,7 @@ bool Edge::RecomputeOutputDirty(BuildLog* build_log,
// dirty.
if (!rule_->generator() && build_log &&
(entry || (entry = build_log->LookupByOutput(output->path())))) {
- if (command != entry->command) {
+ if (BuildLog::LogEntry::HashCommand(command) != entry->command_hash) {
EXPLAIN("command line changed for %s", output->path().c_str());
return true;
}
@@ -183,34 +183,38 @@ struct EdgeEnv : public Env {
/// Given a span of Nodes, construct a list of paths suitable for a command
/// line. XXX here is where shell-escaping of e.g spaces should happen.
string MakePathList(vector<Node*>::iterator begin,
- vector<Node*>::iterator end);
+ vector<Node*>::iterator end,
+ char sep);
Edge* edge_;
};
string EdgeEnv::LookupVariable(const string& var) {
- if (var == "in") {
+ if (var == "in" || var == "in_newline") {
int explicit_deps_count = edge_->inputs_.size() - edge_->implicit_deps_ -
edge_->order_only_deps_;
return MakePathList(edge_->inputs_.begin(),
- edge_->inputs_.begin() + explicit_deps_count);
+ edge_->inputs_.begin() + explicit_deps_count,
+ var == "in" ? ' ' : '\n');
} else if (var == "out") {
return MakePathList(edge_->outputs_.begin(),
- edge_->outputs_.end());
+ edge_->outputs_.end(),
+ ' ');
} else if (edge_->env_) {
return edge_->env_->LookupVariable(var);
} else {
- // XXX shoudl we warn here?
+ // XXX should we warn here?
return string();
}
}
string EdgeEnv::MakePathList(vector<Node*>::iterator begin,
- vector<Node*>::iterator end) {
+ vector<Node*>::iterator end,
+ char sep) {
string result;
for (vector<Node*>::iterator i = begin; i != end; ++i) {
if (!result.empty())
- result.push_back(' ');
+ result.push_back(sep);
const string& path = (*i)->path();
if (path.find(" ") != string::npos) {
result.append("\"");
diff --git a/src/hash_collision_bench.cc b/src/hash_collision_bench.cc
new file mode 100644
index 0000000..d9b09d9
--- /dev/null
+++ b/src/hash_collision_bench.cc
@@ -0,0 +1,43 @@
+#include "build_log.h"
+
+int random(int low, int high) {
+ return int(low + (rand() / double(RAND_MAX)) * (high - low) + 0.5);
+}
+
+void RandomCommand(char** s) {
+ int len = random(5, 100);
+ *s = new char[len];
+ for (int i = 0; i < len; ++i)
+ (*s)[i] = random(32, 127);
+}
+
+int main() {
+ const int N = 20 * 1000 * 1000;
+
+ // Leak these, else 10% of the runtime is spent destroying strings.
+ char** commands = new char*[N];
+ pair<uint64_t, int>* hashes = new pair<uint64_t, int>[N];
+
+ srand(time(NULL));
+
+ for (int i = 0; i < N; ++i) {
+ RandomCommand(&commands[i]);
+ hashes[i] = make_pair(BuildLog::LogEntry::HashCommand(commands[i]), i);
+ }
+
+ sort(hashes, hashes + N);
+
+ int num_collisions = 0;
+ for (int i = 1; i < N; ++i) {
+ if (hashes[i - 1].first == hashes[i].first) {
+ if (strcmp(commands[hashes[i - 1].second],
+ commands[hashes[i].second]) != 0) {
+ printf("collision!\n string 1: '%s'\n string 2: '%s'\n",
+ commands[hashes[i - 1].second],
+ commands[hashes[i].second]);
+ num_collisions++;
+ }
+ }
+ }
+ printf("\n\n%d collisions after %d runs\n", num_collisions, N);
+}
diff --git a/src/lexer.h b/src/lexer.h
index 75c1b2f..19008d7 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#ifndef NINJA_LEXER_H_
+#define NINJA_LEXER_H_
+
#include "string_piece.h"
// Windows may #define ERROR.
@@ -95,3 +98,4 @@ private:
const char* last_token_;
};
+#endif // NINJA_LEXER_H_
diff --git a/src/metrics.h b/src/metrics.h
index 74f5f8f..af6e9a2 100644
--- a/src/metrics.h
+++ b/src/metrics.h
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#ifndef NINJA_METRICS_H_
+#define NINJA_METRICS_H_
+
#include <string>
#include <vector>
using namespace std;
@@ -62,3 +65,5 @@ private:
ScopedMetric metrics_h_scoped(metrics_h_metric);
extern Metrics* g_metrics;
+
+#endif // NINJA_METRICS_H_
diff --git a/src/parsers.cc b/src/parsers.cc
index c3844fb..bc76ba1 100644
--- a/src/parsers.cc
+++ b/src/parsers.cc
@@ -218,7 +218,7 @@ bool ManifestParser::ParseEdge(string* err) {
ins.push_back(in);
}
- // Add all order-only deps, counting how many as we go.
+ // Add all implicit deps, counting how many as we go.
int implicit = 0;
if (lexer_.PeekToken(Lexer::PIPE)) {
for (;;) {
diff --git a/src/parsers_test.cc b/src/parsers_test.cc
index c5151b8..fc83946 100644
--- a/src/parsers_test.cc
+++ b/src/parsers_test.cc
@@ -115,6 +115,23 @@ TEST_F(ParserTest, ResponseFiles) {
EXPECT_EQ("[$in]", rule->rspfile_content().Serialize());
}
+TEST_F(ParserTest, InNewline) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat_rsp\n"
+" command = cat $in_newline > $out\n"
+"\n"
+"build out: cat_rsp in in2\n"
+" rspfile=out.rsp\n"));
+
+ ASSERT_EQ(2u, state.rules_.size());
+ const Rule* rule = state.rules_.begin()->second;
+ EXPECT_EQ("cat_rsp", rule->name());
+ EXPECT_EQ("[cat ][$in_newline][ > ][$out]", rule->command().Serialize());
+
+ Edge* edge = state.edges_[0];
+ EXPECT_EQ("cat in\nin2 > out", edge->EvaluateCommand());
+}
+
TEST_F(ParserTest, Variables) {
ASSERT_NO_FATAL_FAILURE(AssertParse(
"l = one-letter-test\n"
diff --git a/src/test.cc b/src/test.cc
index 2fbb4df..afedd10 100644
--- a/src/test.cc
+++ b/src/test.cc
@@ -18,6 +18,7 @@
#include <errno.h>
+#include "build_log.h"
#include "parsers.h"
#include "util.h"
@@ -89,6 +90,10 @@ void AssertParse(State* state, const char* input) {
ASSERT_EQ("", err);
}
+void AssertHash(const char* expected, uint64_t actual) {
+ ASSERT_EQ(BuildLog::LogEntry::HashCommand(expected), actual);
+}
+
void VirtualFileSystem::Create(const string& path, int time,
const string& contents) {
files_[path].mtime = time;
diff --git a/src/test.h b/src/test.h
index 97f7cb1..37ca1f9 100644
--- a/src/test.h
+++ b/src/test.h
@@ -35,6 +35,7 @@ struct StateTestWithBuiltinRules : public testing::Test {
};
void AssertParse(State* state, const char* input);
+void AssertHash(const char* expected, uint64_t actual);
/// An implementation of DiskInterface that uses an in-memory representation
/// of disk state. It also logs file accesses and directory creations
diff --git a/src/util.h b/src/util.h
index 399913e..82f4850 100644
--- a/src/util.h
+++ b/src/util.h
@@ -77,6 +77,7 @@ double GetLoadAverage();
#define fileno _fileno
#define unlink _unlink
#define chdir _chdir
+#define strtoull _strtoui64
#endif
#ifdef _WIN32