summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml2
-rw-r--r--HACKING.md2
-rw-r--r--RELEASING17
-rwxr-xr-xconfigure.py40
-rw-r--r--misc/write_fake_manifests.py89
-rw-r--r--misc/zsh-completion7
-rwxr-xr-xsrc/browse.py24
-rw-r--r--src/build.cc42
-rw-r--r--src/build.h16
-rw-r--r--src/build_log.cc10
-rw-r--r--src/build_log.h6
-rw-r--r--src/build_log_perftest.cc2
-rw-r--r--src/build_log_test.cc8
-rw-r--r--src/build_test.cc63
-rw-r--r--src/canon_perftest.cc2
-rw-r--r--src/clparser.cc18
-rw-r--r--src/clparser_perftest.cc157
-rw-r--r--src/disk_interface.cc21
-rw-r--r--src/graph.cc39
-rw-r--r--src/graph.h9
-rw-r--r--src/graph_test.cc39
-rw-r--r--src/includes_normalize-win32.cc128
-rw-r--r--src/includes_normalize.h18
-rw-r--r--src/includes_normalize_test.cc43
-rw-r--r--src/manifest_parser.cc16
-rw-r--r--src/manifest_parser_test.cc5
-rw-r--r--src/minidump-win32.cc2
-rw-r--r--src/ninja.cc61
-rw-r--r--src/state.cc15
-rw-r--r--src/state.h11
-rw-r--r--src/string_piece.h18
-rw-r--r--src/string_piece_util.cc78
-rw-r--r--src/string_piece_util.h34
-rw-r--r--src/string_piece_util_test.cc129
-rw-r--r--src/subprocess-posix.cc7
-rw-r--r--src/subprocess.h8
-rw-r--r--src/subprocess_test.cc4
-rw-r--r--src/util.cc27
-rw-r--r--src/util.h6
-rw-r--r--src/util_test.cc37
-rw-r--r--src/version.cc2
42 files changed, 991 insertions, 272 deletions
diff --git a/.gitignore b/.gitignore
index 5a85203..a86205b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ TAGS
/ninja.bootstrap
/build_log_perftest
/canon_perftest
+/clparser_perftest
/depfile_parser_perftest
/hash_collision_bench
/ninja_test
diff --git a/.travis.yml b/.travis.yml
index 216b8b0..093139b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,4 +3,4 @@ language: cpp
compiler:
- gcc
- clang
-script: ./configure.py --bootstrap && ./ninja ninja_test && ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots && ./misc/ninja_syntax_test.py
+script: ./configure.py --bootstrap && ./ninja all && ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots && ./misc/ninja_syntax_test.py
diff --git a/HACKING.md b/HACKING.md
index c9c6601..e7c91ef 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -105,7 +105,7 @@ Generally it's the [Google C++ coding style][], but in brief:
* Lines are 80 columns maximum.
* All source files should have the Google Inc. license header.
-[Google C++ coding style]: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
+[Google C++ coding style]: https://google.github.io/styleguide/cppguide.html
## Documentation
diff --git a/RELEASING b/RELEASING
index 880a55d..5f51b73 100644
--- a/RELEASING
+++ b/RELEASING
@@ -2,17 +2,18 @@ Notes to myself on all the steps to make for a Ninja release.
Push new release branch:
1. Consider sending a heads-up to the ninja-build mailing list first
-2. update src/version.cc with new version (with ".git"), then
- git commit -a -m 'mark this 1.5.0.git'
-3. git checkout release; git merge master
-4. fix version number in src/version.cc (it will likely conflict in the above)
-5. fix version in doc/manual.asciidoc
-6. commit, tag, push (don't forget to push --tags)
- git commit -a -m v1.5.0; git push origin release
+2. Make sure branches 'master' and 'release' are synced up locally
+3. update src/version.cc with new version (with ".git"), then
+ git commit -am 'mark this 1.5.0.git'
+4. git checkout release; git merge master
+5. fix version number in src/version.cc (it will likely conflict in the above)
+6. fix version in doc/manual.asciidoc (exists only on release branch)
+7. commit, tag, push (don't forget to push --tags)
+ git commit -am v1.5.0; git push origin release
git tag v1.5.0; git push --tags
# Push the 1.5.0.git change on master too:
git checkout master; git push origin master
-7. construct release notes from prior notes
+8. construct release notes from prior notes
credits: git shortlog -s --no-merges REV..
Release on github:
diff --git a/configure.py b/configure.py
index 84218b9..a443748 100755
--- a/configure.py
+++ b/configure.py
@@ -60,11 +60,14 @@ class Platform(object):
self._platform = 'netbsd'
elif self._platform.startswith('aix'):
self._platform = 'aix'
+ elif self._platform.startswith('dragonfly'):
+ self._platform = 'dragonfly'
@staticmethod
def known_platforms():
return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5',
- 'mingw', 'msvc', 'gnukfreebsd', 'bitrig', 'netbsd', 'aix']
+ 'mingw', 'msvc', 'gnukfreebsd', 'bitrig', 'netbsd', 'aix',
+ 'dragonfly']
def platform(self):
return self._platform
@@ -95,10 +98,11 @@ class Platform(object):
return self._platform == 'aix'
def uses_usr_local(self):
- return self._platform in ('freebsd', 'openbsd', 'bitrig')
+ return self._platform in ('freebsd', 'openbsd', 'bitrig', 'dragonfly')
def supports_ppoll(self):
- return self._platform in ('linux', 'openbsd', 'bitrig')
+ return self._platform in ('freebsd', 'linux', 'openbsd', 'bitrig',
+ 'dragonfly')
def supports_ninja_browse(self):
return (not self.is_windows()
@@ -302,7 +306,7 @@ if platform.is_msvc():
'/Zi', # Create pdb with debug info.
'/W4', # Highest warning level.
'/WX', # Warnings as errors.
- '/wd4530', '/wd4100', '/wd4706',
+ '/wd4530', '/wd4100', '/wd4706', '/wd4244',
'/wd4512', '/wd4800', '/wd4702', '/wd4819',
# Disable warnings about constant conditional expressions.
'/wd4127',
@@ -489,6 +493,7 @@ for name in ['build',
'manifest_parser',
'metrics',
'state',
+ 'string_piece_util',
'util',
'version']:
objs += cxx(name)
@@ -551,6 +556,7 @@ for name in ['build_log_test',
'manifest_parser_test',
'ninja_test',
'state_test',
+ 'string_piece_util_test',
'subprocess_test',
'test',
'util_test']:
@@ -566,21 +572,17 @@ all_targets += ninja_test
n.comment('Ancillary executables.')
-objs = cxx('build_log_perftest')
-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('depfile_parser_perftest')
-all_targets += n.build(binary('depfile_parser_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)])
-objs = cxx('manifest_parser_perftest')
-all_targets += n.build(binary('manifest_parser_perftest'), 'link', objs,
- implicit=ninja_lib, variables=[('libs', libs)])
+
+for name in ['build_log_perftest',
+ 'canon_perftest',
+ 'depfile_parser_perftest',
+ 'hash_collision_bench',
+ 'manifest_parser_perftest',
+ 'clparser_perftest']:
+ objs = cxx(name)
+ all_targets += n.build(binary(name), 'link', objs,
+ implicit=ninja_lib, variables=[('libs', libs)])
+
n.newline()
n.comment('Generate a graph using the "graph" tool.')
diff --git a/misc/write_fake_manifests.py b/misc/write_fake_manifests.py
index ca49535..b3594de 100644
--- a/misc/write_fake_manifests.py
+++ b/misc/write_fake_manifests.py
@@ -50,9 +50,10 @@ def moar(avg_options, p_suffix):
class GenRandom(object):
- def __init__(self):
+ def __init__(self, src_dir):
self.seen_names = set([None])
self.seen_defines = set([None])
+ self.src_dir = src_dir
def _unique_string(self, seen, avg_options=1.3, p_suffix=0.1):
s = None
@@ -76,7 +77,7 @@ class GenRandom(object):
def src_obj_pairs(self, path, name):
num_sources = paretoint(55, alpha=2) + 1
- return [(os.path.join('..', '..', path, s + '.cc'),
+ return [(os.path.join(self.src_dir, path, s + '.cc'),
os.path.join('obj', path, '%s.%s.o' % (name, s)))
for s in self._n_unique_strings(num_sources)]
@@ -103,12 +104,8 @@ class Target(object):
self.kind = kind
self.has_compile_depends = random.random() < 0.4
- @property
- def includes(self):
- return ['-I' + dep.dir_path for dep in self.deps]
-
-def write_target_ninja(ninja, target):
+def write_target_ninja(ninja, target, src_dir):
compile_depends = None
if target.has_compile_depends:
compile_depends = os.path.join(
@@ -117,8 +114,7 @@ def write_target_ninja(ninja, target):
ninja.newline()
ninja.variable('defines', target.defines)
- if target.deps:
- ninja.variable('includes', target.includes)
+ ninja.variable('includes', '-I' + src_dir)
ninja.variable('cflags', ['-Wall', '-fno-rtti', '-fno-exceptions'])
ninja.newline()
@@ -129,17 +125,63 @@ def write_target_ninja(ninja, target):
deps = [dep.output for dep in target.deps]
libs = [dep.output for dep in target.deps if dep.kind == LIB]
if target.kind == EXE:
- ninja.variable('ldflags', '-Wl,pie')
ninja.variable('libs', libs)
+ if sys.platform == "darwin":
+ ninja.variable('ldflags', '-Wl,-pie')
link = { LIB: 'alink', EXE: 'link'}[target.kind]
ninja.build(target.output, link, [obj for _, obj in target.src_obj_pairs],
implicit=deps)
+def write_sources(target, root_dir):
+ need_main = target.kind == EXE
+
+ includes = []
+
+ # Include siblings.
+ for cc_filename, _ in target.src_obj_pairs:
+ h_filename = os.path.basename(os.path.splitext(cc_filename)[0] + '.h')
+ includes.append(h_filename)
+
+ # Include deps.
+ for dep in target.deps:
+ for cc_filename, _ in dep.src_obj_pairs:
+ h_filename = os.path.basename(
+ os.path.splitext(cc_filename)[0] + '.h')
+ includes.append("%s/%s" % (dep.dir_path, h_filename))
+
+ for cc_filename, _ in target.src_obj_pairs:
+ cc_path = os.path.join(root_dir, cc_filename)
+ h_path = os.path.splitext(cc_path)[0] + '.h'
+ namespace = os.path.basename(target.dir_path)
+ class_ = os.path.splitext(os.path.basename(cc_filename))[0]
+ try:
+ os.makedirs(os.path.dirname(cc_path))
+ except OSError:
+ pass
+
+ with open(h_path, 'w') as f:
+ f.write('namespace %s { struct %s { %s(); }; }' % (namespace,
+ class_, class_))
+ with open(cc_path, 'w') as f:
+ for include in includes:
+ f.write('#include "%s"\n' % include)
+ f.write('\n')
+ f.write('namespace %s { %s::%s() {} }' % (namespace,
+ class_, class_))
+
+ if need_main:
+ f.write('int main(int argc, char **argv) {}\n')
+ need_main = False
+
def write_master_ninja(master_ninja, targets):
"""Writes master build.ninja file, referencing all given subninjas."""
master_ninja.variable('cxx', 'c++')
master_ninja.variable('ld', '$cxx')
+ if sys.platform == 'darwin':
+ master_ninja.variable('alink', 'libtool -static')
+ else:
+ master_ninja.variable('alink', 'ar rcs')
master_ninja.newline()
master_ninja.pool('link_pool', depth=4)
@@ -148,8 +190,8 @@ def write_master_ninja(master_ninja, targets):
master_ninja.rule('cxx', description='CXX $out',
command='$cxx -MMD -MF $out.d $defines $includes $cflags -c $in -o $out',
depfile='$out.d', deps='gcc')
- master_ninja.rule('alink', description='LIBTOOL-STATIC $out',
- command='rm -f $out && libtool -static -o $out $in')
+ master_ninja.rule('alink', description='ARCHIVE $out',
+ command='rm -f $out && $alink -o $out $in')
master_ninja.rule('link', description='LINK $out', pool='link_pool',
command='$ld $ldflags -o $out $in $libs')
master_ninja.rule('stamp', description='STAMP $out', command='touch $out')
@@ -181,9 +223,8 @@ def FileWriter(path):
f.close()
-def random_targets():
- num_targets = 1500
- gen = GenRandom()
+def random_targets(num_targets, src_dir):
+ gen = GenRandom(src_dir)
# N-1 static libraries, and 1 executable depending on all of them.
targets = [Target(gen, LIB) for i in xrange(num_targets - 1)]
@@ -199,16 +240,28 @@ def random_targets():
def main():
parser = argparse.ArgumentParser()
+ parser.add_argument('-s', '--sources', nargs="?", const="src",
+ help='write sources to directory (relative to output directory)')
+ parser.add_argument('-t', '--targets', type=int, default=1500,
+ help='number of targets (default: 1500)')
+ parser.add_argument('-S', '--seed', type=int, help='random seed',
+ default=12345)
parser.add_argument('outdir', help='output directory')
args = parser.parse_args()
root_dir = args.outdir
- random.seed(12345)
+ random.seed(args.seed)
- targets = random_targets()
+ do_write_sources = args.sources is not None
+ src_dir = args.sources if do_write_sources else "src"
+
+ targets = random_targets(args.targets, src_dir)
for target in targets:
with FileWriter(os.path.join(root_dir, target.ninja_file_path)) as n:
- write_target_ninja(n, target)
+ write_target_ninja(n, target, src_dir)
+
+ if do_write_sources:
+ write_sources(target, root_dir)
with FileWriter(os.path.join(root_dir, 'build.ninja')) as master_ninja:
master_ninja.width = 120
diff --git a/misc/zsh-completion b/misc/zsh-completion
index 446e269..bf23fac 100644
--- a/misc/zsh-completion
+++ b/misc/zsh-completion
@@ -22,7 +22,12 @@ __get_targets() {
then
eval dir="${opt_args[-C]}"
fi
- targets_command="ninja -C \"${dir}\" -t targets all"
+ file="build.ninja"
+ if [ -n "${opt_args[-f]}" ];
+ then
+ eval file="${opt_args[-f]}"
+ fi
+ targets_command="ninja -f \"${file}\" -C \"${dir}\" -t targets all"
eval ${targets_command} 2>/dev/null | cut -d: -f1
}
diff --git a/src/browse.py b/src/browse.py
index 32792f3..64a16f2 100755
--- a/src/browse.py
+++ b/src/browse.py
@@ -27,6 +27,7 @@ try:
except ImportError:
import BaseHTTPServer as httpserver
import argparse
+import cgi
import os
import socket
import subprocess
@@ -58,6 +59,9 @@ def match_strip(line, prefix):
return (False, line)
return (True, line[len(prefix):])
+def html_escape(text):
+ return cgi.escape(text, quote=True)
+
def parse(text):
lines = iter(text.split('\n'))
@@ -124,19 +128,19 @@ tt {
''' + body
def generate_html(node):
- document = ['<h1><tt>%s</tt></h1>' % node.target]
+ document = ['<h1><tt>%s</tt></h1>' % html_escape(node.target)]
if node.inputs:
document.append('<h2>target is built using rule <tt>%s</tt> of</h2>' %
- node.rule)
+ html_escape(node.rule))
if len(node.inputs) > 0:
document.append('<div class=filelist>')
for input, type in sorted(node.inputs):
extra = ''
if type:
- extra = ' (%s)' % type
+ extra = ' (%s)' % html_escape(type)
document.append('<tt><a href="?%s">%s</a>%s</tt><br>' %
- (input, input, extra))
+ (html_escape(input), html_escape(input), extra))
document.append('</div>')
if node.outputs:
@@ -144,7 +148,7 @@ def generate_html(node):
document.append('<div class=filelist>')
for output in sorted(node.outputs):
document.append('<tt><a href="?%s">%s</a></tt><br>' %
- (output, output))
+ (html_escape(output), html_escape(output)))
document.append('</div>')
return '\n'.join(document)
@@ -177,7 +181,7 @@ class RequestHandler(httpserver.BaseHTTPRequestHandler):
page_body = generate_html(parse(ninja_output.strip()))
else:
# Relay ninja's error message.
- page_body = '<h1><tt>%s</tt></h1>' % ninja_error
+ page_body = '<h1><tt>%s</tt></h1>' % html_escape(ninja_error)
self.send_response(200)
self.end_headers()
@@ -189,6 +193,8 @@ class RequestHandler(httpserver.BaseHTTPRequestHandler):
parser = argparse.ArgumentParser(prog='ninja -t browse')
parser.add_argument('--port', '-p', default=8000, type=int,
help='Port number to use (default %(default)d)')
+parser.add_argument('--hostname', '-a', default='localhost', type=str,
+ help='Hostname to bind to (default %(default)s)')
parser.add_argument('--no-browser', action='store_true',
help='Do not open a webbrowser on startup.')
@@ -201,9 +207,11 @@ parser.add_argument('initial_target', default='all', nargs='?',
args = parser.parse_args()
port = args.port
-httpd = httpserver.HTTPServer(('',port), RequestHandler)
+hostname = args.hostname
+httpd = httpserver.HTTPServer((hostname,port), RequestHandler)
try:
- hostname = socket.gethostname()
+ if hostname == "":
+ 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 )
if not args.no_browser:
diff --git a/src/build.cc b/src/build.cc
index e4fe21a..8f4169b 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -115,11 +115,6 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
int* end_time) {
int64_t now = GetTimeMillis();
- if (finished_edges_ == 0) {
- overall_rate_.Restart();
- current_rate_.Restart();
- }
-
++finished_edges_;
RunningEdgeMap::iterator i = running_edges_.find(edge);
@@ -179,6 +174,11 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
}
}
+void BuildStatus::BuildStarted() {
+ overall_rate_.Restart();
+ current_rate_.Restart();
+}
+
void BuildStatus::BuildFinished() {
printer_.SetConsoleLocked(false);
printer_.PrintOnNewLine("");
@@ -235,14 +235,14 @@ string BuildStatus::FormatProgressStatus(
// Overall finished edges per second.
case 'o':
overall_rate_.UpdateRate(finished_edges_);
- snprinfRate(overall_rate_.rate(), buf, "%.1f");
+ SnprintfRate(overall_rate_.rate(), buf, "%.1f");
out += buf;
break;
// Current rate, average over the last '-j' jobs.
case 'c':
current_rate_.UpdateRate(finished_edges_);
- snprinfRate(current_rate_.rate(), buf, "%.1f");
+ SnprintfRate(current_rate_.rate(), buf, "%.1f");
out += buf;
break;
@@ -290,6 +290,13 @@ void BuildStatus::PrintStatus(Edge* edge, EdgeStatus status) {
Plan::Plan() : command_edges_(0), wanted_edges_(0) {}
+void Plan::Reset() {
+ command_edges_ = 0;
+ wanted_edges_ = 0;
+ ready_.clear();
+ want_.clear();
+}
+
bool Plan::AddTarget(Node* node, string* err) {
vector<Node*> stack;
return AddSubTarget(node, &stack, err);
@@ -665,6 +672,9 @@ bool Builder::Build(string* err) {
command_runner_.reset(new RealCommandRunner(config_));
}
+ // We are about to start the build process.
+ status_->BuildStarted();
+
// This main loop runs the entire build process.
// It is structured like this:
// First, we attempt to start as many commands as allowed by the
@@ -805,9 +815,10 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
return true;
}
- // Restat the edge outputs, if necessary.
- TimeStamp restat_mtime = 0;
- if (edge->GetBindingBool("restat") && !config_.dry_run) {
+ // Restat the edge outputs
+ TimeStamp output_mtime = 0;
+ bool restat = edge->GetBindingBool("restat");
+ if (!config_.dry_run) {
bool node_cleaned = false;
for (vector<Node*>::iterator o = edge->outputs_.begin();
@@ -815,7 +826,9 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
TimeStamp new_mtime = disk_interface_->Stat((*o)->path(), err);
if (new_mtime == -1)
return false;
- if ((*o)->mtime() == new_mtime) {
+ if (new_mtime > output_mtime)
+ output_mtime = new_mtime;
+ if ((*o)->mtime() == new_mtime && restat) {
// The rule command did not change the output. Propagate the clean
// state through the build graph.
// Note that this also applies to nonexistent outputs (mtime == 0).
@@ -826,6 +839,7 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
}
if (node_cleaned) {
+ TimeStamp restat_mtime = 0;
// If any output was cleaned, find the most recent mtime of any
// (existing) non-order-only input or the depfile.
for (vector<Node*>::iterator i = edge->inputs_.begin();
@@ -849,6 +863,8 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
// The total number of edges in the plan may have changed as a result
// of a restat.
status_->PlanHasTotalEdges(plan_.command_edge_count());
+
+ output_mtime = restat_mtime;
}
}
@@ -861,7 +877,7 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
if (scan_.build_log()) {
if (!scan_.build_log()->RecordCommand(edge, start_time, end_time,
- restat_mtime)) {
+ output_mtime)) {
*err = string("Error writing to build log: ") + strerror(errno);
return false;
}
@@ -930,7 +946,7 @@ bool Builder::ExtractDeps(CommandRunner::Result* result,
deps_nodes->reserve(deps.ins_.size());
for (vector<StringPiece>::iterator i = deps.ins_.begin();
i != deps.ins_.end(); ++i) {
- unsigned int slash_bits;
+ uint64_t slash_bits;
if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits,
err))
return false;
diff --git a/src/build.h b/src/build.h
index 819d832..f97d67e 100644
--- a/src/build.h
+++ b/src/build.h
@@ -71,6 +71,9 @@ struct Plan {
/// Number of edges with commands to run.
int command_edge_count() const { return command_edges_; }
+ /// Reset state. Clears want and ready sets.
+ void Reset();
+
private:
bool AddSubTarget(Node* node, vector<Node*>* stack, string* err);
bool CheckDependencyCycle(Node* node, const vector<Node*>& stack,
@@ -200,6 +203,7 @@ struct BuildStatus {
void BuildEdgeStarted(Edge* edge);
void BuildEdgeFinished(Edge* edge, bool success, const string& output,
int* start_time, int* end_time);
+ void BuildStarted();
void BuildFinished();
enum EdgeStatus {
@@ -211,9 +215,9 @@ struct BuildStatus {
/// See the user manual for more information about the available
/// placeholders.
/// @param progress_status_format The format of the progress status.
- /// @param finished True if the edge being printed just finished
+ /// @param status The status of the edge.
string FormatProgressStatus(const char* progress_status_format,
- EdgeStatus kEdgeFinished) const;
+ EdgeStatus status) const;
private:
void PrintStatus(Edge* edge, EdgeStatus status);
@@ -236,9 +240,11 @@ struct BuildStatus {
const char* progress_status_format_;
template<size_t S>
- void snprinfRate(double rate, char(&buf)[S], const char* format) const {
- if (rate == -1) snprintf(buf, S, "?");
- else snprintf(buf, S, format, rate);
+ void SnprintfRate(double rate, char(&buf)[S], const char* format) const {
+ if (rate == -1)
+ snprintf(buf, S, "?");
+ else
+ snprintf(buf, S, format, rate);
}
struct RateInfo {
diff --git a/src/build_log.cc b/src/build_log.cc
index 8a52514..333915a 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -105,7 +105,7 @@ BuildLog::LogEntry::LogEntry(const string& output)
BuildLog::LogEntry::LogEntry(const string& output, uint64_t command_hash,
int start_time, int end_time, TimeStamp restat_mtime)
: output(output), command_hash(command_hash),
- start_time(start_time), end_time(end_time), restat_mtime(restat_mtime)
+ start_time(start_time), end_time(end_time), mtime(restat_mtime)
{}
BuildLog::BuildLog()
@@ -145,7 +145,7 @@ bool BuildLog::OpenForWrite(const string& path, const BuildLogUser& user,
}
bool BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
- TimeStamp restat_mtime) {
+ TimeStamp mtime) {
string command = edge->EvaluateCommand(true);
uint64_t command_hash = LogEntry::HashCommand(command);
for (vector<Node*>::iterator out = edge->outputs_.begin();
@@ -162,7 +162,7 @@ bool BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
log_entry->command_hash = command_hash;
log_entry->start_time = start_time;
log_entry->end_time = end_time;
- log_entry->restat_mtime = restat_mtime;
+ log_entry->mtime = mtime;
if (log_file_) {
if (!WriteEntry(log_file_, *log_entry))
@@ -314,7 +314,7 @@ bool BuildLog::Load(const string& path, string* err) {
entry->start_time = start_time;
entry->end_time = end_time;
- entry->restat_mtime = restat_mtime;
+ entry->mtime = restat_mtime;
if (log_version >= 5) {
char c = *end; *end = '\0';
entry->command_hash = (uint64_t)strtoull(start, NULL, 16);
@@ -354,7 +354,7 @@ BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
bool BuildLog::WriteEntry(FILE* f, const LogEntry& entry) {
return fprintf(f, "%d\t%d\t%d\t%s\t%" PRIx64 "\n",
- entry.start_time, entry.end_time, entry.restat_mtime,
+ entry.start_time, entry.end_time, entry.mtime,
entry.output.c_str(), entry.command_hash) > 0;
}
diff --git a/src/build_log.h b/src/build_log.h
index 785961e..5268fab 100644
--- a/src/build_log.h
+++ b/src/build_log.h
@@ -45,7 +45,7 @@ struct BuildLog {
bool OpenForWrite(const string& path, const BuildLogUser& user, string* err);
bool RecordCommand(Edge* edge, int start_time, int end_time,
- TimeStamp restat_mtime = 0);
+ TimeStamp mtime = 0);
void Close();
/// Load the on-disk log.
@@ -56,7 +56,7 @@ struct BuildLog {
uint64_t command_hash;
int start_time;
int end_time;
- TimeStamp restat_mtime;
+ TimeStamp mtime;
static uint64_t HashCommand(StringPiece command);
@@ -64,7 +64,7 @@ struct BuildLog {
bool operator==(const LogEntry& o) {
return output == o.output && command_hash == o.command_hash &&
start_time == o.start_time && end_time == o.end_time &&
- restat_mtime == o.restat_mtime;
+ mtime == o.mtime;
}
explicit LogEntry(const string& output);
diff --git a/src/build_log_perftest.cc b/src/build_log_perftest.cc
index 185c512..b4efb1d 100644
--- a/src/build_log_perftest.cc
+++ b/src/build_log_perftest.cc
@@ -92,7 +92,7 @@ bool WriteTestData(string* err) {
log.RecordCommand(state.edges_[i],
/*start_time=*/100 * i,
/*end_time=*/100 * i + 1,
- /*restat_mtime=*/0);
+ /*mtime=*/0);
}
return true;
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index f4c9044..ad30380 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -181,7 +181,7 @@ TEST_F(BuildLogTest, SpacesInOutputV4) {
ASSERT_TRUE(e);
ASSERT_EQ(123, e->start_time);
ASSERT_EQ(456, e->end_time);
- ASSERT_EQ(456, e->restat_mtime);
+ ASSERT_EQ(456, e->mtime);
ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
}
@@ -205,14 +205,14 @@ TEST_F(BuildLogTest, DuplicateVersionHeader) {
ASSERT_TRUE(e);
ASSERT_EQ(123, e->start_time);
ASSERT_EQ(456, e->end_time);
- ASSERT_EQ(456, e->restat_mtime);
+ ASSERT_EQ(456, e->mtime);
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(789, e->mtime);
ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
}
@@ -240,7 +240,7 @@ TEST_F(BuildLogTest, VeryLongInputLine) {
ASSERT_TRUE(e);
ASSERT_EQ(456, e->start_time);
ASSERT_EQ(789, e->end_time);
- ASSERT_EQ(789, e->restat_mtime);
+ ASSERT_EQ(789, e->mtime);
ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
}
diff --git a/src/build_test.cc b/src/build_test.cc
index 06871d2..0eb9aaa 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -608,7 +608,8 @@ bool FakeCommandRunner::StartCommand(Edge* edge) {
edge->rule().name() == "cat_rsp_out" ||
edge->rule().name() == "cc" ||
edge->rule().name() == "touch" ||
- edge->rule().name() == "touch-interrupt") {
+ edge->rule().name() == "touch-interrupt" ||
+ edge->rule().name() == "touch-fail-tick2") {
for (vector<Node*>::iterator out = edge->outputs_.begin();
out != edge->outputs_.end(); ++out) {
fs_->Create((*out)->path(), "");
@@ -649,7 +650,8 @@ bool FakeCommandRunner::WaitForCommand(Result* result) {
return true;
}
- if (edge->rule().name() == "fail")
+ if (edge->rule().name() == "fail" ||
+ (edge->rule().name() == "touch-fail-tick2" && fs_->now_ == 2))
result->status = ExitFailure;
else
result->status = ExitSuccess;
@@ -1258,6 +1260,51 @@ TEST_F(BuildWithLogTest, NotInLogButOnDisk) {
EXPECT_TRUE(builder_.AlreadyUpToDate());
}
+TEST_F(BuildWithLogTest, RebuildAfterFailure) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch-fail-tick2\n"
+" command = touch-fail-tick2\n"
+"build out1: touch-fail-tick2 in\n"));
+
+ string err;
+
+ fs_.Create("in", "");
+
+ // Run once successfully to get out1 in the log
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ builder_.Cleanup();
+ builder_.plan_.Reset();
+
+ fs_.Tick();
+ fs_.Create("in", "");
+
+ // Run again with a failure that updates the output file timestamp
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ EXPECT_FALSE(builder_.Build(&err));
+ EXPECT_EQ("subcommand failed", err);
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ builder_.Cleanup();
+ builder_.plan_.Reset();
+
+ fs_.Tick();
+
+ // Run again, should rerun even though the output file is up to date on disk
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ EXPECT_FALSE(builder_.AlreadyUpToDate());
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("", err);
+}
+
TEST_F(BuildWithLogTest, RestatTest) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule true\n"
@@ -1438,7 +1485,7 @@ TEST_F(BuildWithLogTest, RestatMissingInput) {
// the right mtime
BuildLog::LogEntry* log_entry = build_log_.LookupByOutput("out1");
ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ(restat_mtime, log_entry->restat_mtime);
+ ASSERT_EQ(restat_mtime, log_entry->mtime);
// Now remove a file, referenced from depfile, so that target becomes
// dirty, but the output does not change
@@ -1455,7 +1502,7 @@ TEST_F(BuildWithLogTest, RestatMissingInput) {
// Check that the logfile entry remains correctly set
log_entry = build_log_.LookupByOutput("out1");
ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ(restat_mtime, log_entry->restat_mtime);
+ ASSERT_EQ(restat_mtime, log_entry->mtime);
}
struct BuildDryRun : public BuildWithLogTest {
@@ -1725,6 +1772,14 @@ TEST_F(BuildTest, DepsGccWithEmptyDepfileErrorsOut) {
ASSERT_EQ(1u, command_runner_.commands_ran_.size());
}
+TEST_F(BuildTest, StatusFormatElapsed) {
+ status_.BuildStarted();
+ // Before any task is done, the elapsed time must be zero.
+ EXPECT_EQ("[%/e0.000]",
+ status_.FormatProgressStatus("[%%/e%e]",
+ BuildStatus::kEdgeStarted));
+}
+
TEST_F(BuildTest, StatusFormatReplacePlaceholder) {
EXPECT_EQ("[%/s0/t0/r0/u0/f0]",
status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]",
diff --git a/src/canon_perftest.cc b/src/canon_perftest.cc
index 389ac24..03f4a2f 100644
--- a/src/canon_perftest.cc
+++ b/src/canon_perftest.cc
@@ -33,7 +33,7 @@ int main() {
for (int j = 0; j < 5; ++j) {
const int kNumRepetitions = 2000000;
int64_t start = GetTimeMillis();
- unsigned int slash_bits;
+ uint64_t slash_bits;
for (int i = 0; i < kNumRepetitions; ++i) {
CanonicalizePath(buf, &len, &slash_bits, &err);
}
diff --git a/src/clparser.cc b/src/clparser.cc
index f73a8c1..7994c06 100644
--- a/src/clparser.cc
+++ b/src/clparser.cc
@@ -18,8 +18,12 @@
#include <assert.h>
#include <string.h>
+#include "metrics.h"
+#include "string_piece_util.h"
+
#ifdef _WIN32
#include "includes_normalize.h"
+#include "string_piece.h"
#else
#include "util.h"
#endif
@@ -53,7 +57,7 @@ string CLParser::FilterShowIncludes(const string& line,
// static
bool CLParser::IsSystemInclude(string path) {
- transform(path.begin(), path.end(), path.begin(), ::tolower);
+ transform(path.begin(), path.end(), path.begin(), ToLowerASCII);
// 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);
@@ -61,7 +65,7 @@ bool CLParser::IsSystemInclude(string path) {
// static
bool CLParser::FilterInputFilename(string line) {
- transform(line.begin(), line.end(), line.begin(), ::tolower);
+ transform(line.begin(), line.end(), line.begin(), ToLowerASCII);
// TODO: other extensions, like .asm?
return EndsWith(line, ".c") ||
EndsWith(line, ".cc") ||
@@ -72,9 +76,15 @@ bool CLParser::FilterInputFilename(string line) {
// static
bool CLParser::Parse(const string& output, const string& deps_prefix,
string* filtered_output, string* err) {
+ METRIC_RECORD("CLParser::Parse");
+
// Loop over all lines in the output to process them.
assert(&output != filtered_output);
size_t start = 0;
+#ifdef _WIN32
+ IncludesNormalize normalizer(".");
+#endif
+
while (start < output.size()) {
size_t end = output.find_first_of("\r\n", start);
if (end == string::npos)
@@ -85,12 +95,12 @@ bool CLParser::Parse(const string& output, const string& deps_prefix,
if (!include.empty()) {
string normalized;
#ifdef _WIN32
- if (!IncludesNormalize::Normalize(include, NULL, &normalized, err))
+ if (!normalizer.Normalize(include, &normalized, err))
return false;
#else
// TODO: should this make the path relative to cwd?
normalized = include;
- unsigned int slash_bits;
+ uint64_t slash_bits;
if (!CanonicalizePath(&normalized, &slash_bits, err))
return false;
#endif
diff --git a/src/clparser_perftest.cc b/src/clparser_perftest.cc
new file mode 100644
index 0000000..7ac5230
--- /dev/null
+++ b/src/clparser_perftest.cc
@@ -0,0 +1,157 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "clparser.h"
+#include "metrics.h"
+
+int main(int argc, char* argv[]) {
+ // Output of /showIncludes from #include <iostream>
+ string perf_testdata =
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\iostream\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\istream\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ostream\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ios\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocnum\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\climits\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\yvals.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xkeycheck.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\crtdefs.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\sal.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ConcurrencySal.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vadefs.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\use_ansi.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\limits.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cmath\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\math.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xtgmath.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xtr1common\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstdlib\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\stdlib.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_malloc.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_search.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\stddef.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wstdlib.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstdio\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\stdio.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wstdio.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_stdio_config.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\streambuf\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xiosbase\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocale\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstring\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\string.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_memory.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_memcpy_s.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\errno.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_string.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wstring.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\stdexcept\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\exception\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\type_traits\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xstddef\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstddef\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\initializer_list\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\malloc.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_exception.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\eh.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_terminate.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xstring\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xmemory0\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstdint\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\stdint.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\limits\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ymath.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cfloat\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\float.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cwchar\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\wchar.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wconio.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wctype.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wdirect.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wio.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_share.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wprocess.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wtime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\sys/stat.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\sys/types.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\new\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_new.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xutility\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\utility\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\iosfwd\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\crtdbg.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_new_debug.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xatomic0.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\intrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\setjmp.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\immintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\wmmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\nmmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\smmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\tmmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\pmmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\emmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xmmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\mmintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ammintrin.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\mm3dnow.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\typeinfo\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_typeinfo.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocinfo\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocinfo.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\ctype.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\locale.h\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xfacet\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\system_error\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cerrno\r\n"
+ "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\share.h\r\n";
+
+ for (int limit = 1 << 10; limit < (1<<20); limit *= 2) {
+ int64_t start = GetTimeMillis();
+ for (int rep = 0; rep < limit; ++rep) {
+ string output;
+ string err;
+
+ CLParser parser;
+ if (!parser.Parse(perf_testdata, "", &output, &err)) {
+ printf("%s\n", err.c_str());
+ return 1;
+ }
+ }
+ int64_t end = GetTimeMillis();
+
+ if (end - start > 2000) {
+ int delta_ms = (int)(end - start);
+ printf("Parse %d times in %dms avg %.1fus\n",
+ limit, delta_ms, float(delta_ms * 1000) / limit);
+ break;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index 451a9b4..b418e04 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -28,6 +28,7 @@
#include <direct.h> // _mkdir
#endif
+#include "metrics.h"
#include "util.h"
namespace {
@@ -80,20 +81,15 @@ TimeStamp StatSingleFile(const string& path, string* err) {
return TimeStampFromFileTime(attrs.ftLastWriteTime);
}
-#ifdef _MSC_VER
-#pragma warning(push)
-#pragma warning(disable: 4996) // GetVersionExA is deprecated post SDK 8.1.
-#endif
bool IsWindows7OrLater() {
- OSVERSIONINFO version_info = { sizeof(version_info) };
- if (!GetVersionEx(&version_info))
- Fatal("GetVersionEx: %s", GetLastErrorString().c_str());
- return version_info.dwMajorVersion > 6 ||
- (version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 1);
+ OSVERSIONINFOEX version_info =
+ { sizeof(OSVERSIONINFOEX), 6, 1, 0, 0, {0}, 0, 0, 0, 0, 0};
+ DWORDLONG comparison = 0;
+ VER_SET_CONDITION(comparison, VER_MAJORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(comparison, VER_MINORVERSION, VER_GREATER_EQUAL);
+ return VerifyVersionInfo(
+ &version_info, VER_MAJORVERSION | VER_MINORVERSION, comparison);
}
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
bool StatAllFilesInDir(const string& dir, map<string, TimeStamp>* stamps,
string* err) {
@@ -153,6 +149,7 @@ bool DiskInterface::MakeDirs(const string& path) {
// RealDiskInterface -----------------------------------------------------------
TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
+ METRIC_RECORD("node stat");
#ifdef _WIN32
// MSDN: "Naming Files, Paths, and Namespaces"
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
diff --git a/src/graph.cc b/src/graph.cc
index f1d9ca2..27013d5 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -28,7 +28,6 @@
#include "util.h"
bool Node::Stat(DiskInterface* disk_interface, string* err) {
- METRIC_RECORD("node stat");
return (mtime_ = disk_interface->Stat(path_, err)) != -1;
}
@@ -169,7 +168,7 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge,
bool used_restat = false;
if (edge->GetBindingBool("restat") && build_log() &&
(entry = build_log()->LookupByOutput(output->path()))) {
- output_mtime = entry->restat_mtime;
+ output_mtime = entry->mtime;
used_restat = true;
}
@@ -183,17 +182,29 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge,
}
}
- // May also be dirty due to the command changing since the last build.
- // But if this is a generator rule, the command changing does not make us
- // dirty.
- if (!edge->GetBindingBool("generator") && build_log()) {
+ if (build_log()) {
+ bool generator = edge->GetBindingBool("generator");
if (entry || (entry = build_log()->LookupByOutput(output->path()))) {
- if (BuildLog::LogEntry::HashCommand(command) != entry->command_hash) {
+ if (!generator &&
+ BuildLog::LogEntry::HashCommand(command) != entry->command_hash) {
+ // May also be dirty due to the command changing since the last build.
+ // But if this is a generator rule, the command changing does not make us
+ // dirty.
EXPLAIN("command line changed for %s", output->path().c_str());
return true;
}
+ if (entry->mtime < most_recent_input->mtime()) {
+ // May also be dirty due to the mtime in the log being older than the
+ // mtime of the most recent input. This can occur even when the mtime
+ // on disk is newer if a previous run wrote to the output file but
+ // exited with an error or was interrupted.
+ EXPLAIN("recorded mtime of %s older than most recent input %s (%d vs %d)",
+ output->path().c_str(), most_recent_input->path().c_str(),
+ entry->mtime, most_recent_input->mtime());
+ return true;
+ }
}
- if (!entry) {
+ if (!entry && !generator) {
EXPLAIN("command line not found in log for %s", output->path().c_str());
return true;
}
@@ -348,10 +359,10 @@ bool Edge::use_console() const {
}
// static
-string Node::PathDecanonicalized(const string& path, unsigned int slash_bits) {
+string Node::PathDecanonicalized(const string& path, uint64_t slash_bits) {
string result = path;
#ifdef _WIN32
- unsigned int mask = 1;
+ uint64_t mask = 1;
for (char* c = &result[0]; (c = strchr(c, '/')) != NULL;) {
if (slash_bits & mask)
*c = '\\';
@@ -420,10 +431,12 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
return false;
}
- unsigned int unused;
+ uint64_t unused;
if (!CanonicalizePath(const_cast<char*>(depfile.out_.str_),
- &depfile.out_.len_, &unused, err))
+ &depfile.out_.len_, &unused, err)) {
+ *err = path + ": " + *err;
return false;
+ }
// Check that this depfile matches the edge's output, if not return false to
// mark the edge as dirty.
@@ -442,7 +455,7 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
// Add all its in-edges.
for (vector<StringPiece>::iterator i = depfile.ins_.begin();
i != depfile.ins_.end(); ++i, ++implicit_dep) {
- unsigned int slash_bits;
+ uint64_t slash_bits;
if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits,
err))
return false;
diff --git a/src/graph.h b/src/graph.h
index add8d3d..ec24293 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -21,6 +21,7 @@ using namespace std;
#include "eval_env.h"
#include "timestamp.h"
+#include "util.h"
struct BuildLog;
struct DiskInterface;
@@ -33,7 +34,7 @@ struct State;
/// Information about a node in the dependency graph: the file, whether
/// it's dirty, mtime, etc.
struct Node {
- Node(const string& path, unsigned int slash_bits)
+ Node(const string& path, uint64_t slash_bits)
: path_(path),
slash_bits_(slash_bits),
mtime_(-1),
@@ -76,8 +77,8 @@ struct Node {
return PathDecanonicalized(path_, slash_bits_);
}
static string PathDecanonicalized(const string& path,
- unsigned int slash_bits);
- unsigned int slash_bits() const { return slash_bits_; }
+ uint64_t slash_bits);
+ uint64_t slash_bits() const { return slash_bits_; }
TimeStamp mtime() const { return mtime_; }
@@ -101,7 +102,7 @@ private:
/// Set bits starting from lowest for backslashes that were normalized to
/// forward slashes by CanonicalizePath. See |PathDecanonicalized|.
- unsigned int slash_bits_;
+ uint64_t slash_bits_;
/// Possible values of mtime_:
/// -1: file hasn't been examined
diff --git a/src/graph_test.cc b/src/graph_test.cc
index 723e8ea..be08b19 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -149,6 +149,45 @@ TEST_F(GraphTest, ImplicitOutputOutOfDate) {
EXPECT_TRUE(GetNode("out.imp")->dirty());
}
+TEST_F(GraphTest, ImplicitOutputOnlyParse) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build | out.imp: cat in\n"));
+
+ Edge* edge = GetNode("out.imp")->in_edge();
+ EXPECT_EQ(1, edge->outputs_.size());
+ EXPECT_EQ("out.imp", edge->outputs_[0]->path());
+ EXPECT_EQ(1, edge->implicit_outs_);
+ EXPECT_EQ(edge, GetNode("out.imp")->in_edge());
+}
+
+TEST_F(GraphTest, ImplicitOutputOnlyMissing) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build | out.imp: cat in\n"));
+ fs_.Create("in", "");
+
+ Edge* edge = GetNode("out.imp")->in_edge();
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out.imp")->dirty());
+}
+
+TEST_F(GraphTest, ImplicitOutputOnlyOutOfDate) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build | out.imp: cat in\n"));
+ fs_.Create("out.imp", "");
+ fs_.Tick();
+ fs_.Create("in", "");
+
+ Edge* edge = GetNode("out.imp")->in_edge();
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out.imp")->dirty());
+}
+
TEST_F(GraphTest, PathWithCurrentDirectory) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule catdep\n"
diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc
index ca35012..459329b 100644
--- a/src/includes_normalize-win32.cc
+++ b/src/includes_normalize-win32.cc
@@ -15,6 +15,7 @@
#include "includes_normalize.h"
#include "string_piece.h"
+#include "string_piece_util.h"
#include "util.h"
#include <algorithm>
@@ -25,8 +26,39 @@
namespace {
-/// Return true if paths a and b are on the same Windows drive.
+bool IsPathSeparator(char c) {
+ return c == '/' || c == '\\';
+}
+
+// Return true if paths a and b are on the same windows drive.
+// Return false if this funcation cannot check
+// whether or not on the same windows drive.
+bool SameDriveFast(StringPiece a, StringPiece b) {
+ if (a.size() < 3 || b.size() < 3) {
+ return false;
+ }
+
+ if (!islatinalpha(a[0]) || !islatinalpha(b[0])) {
+ return false;
+ }
+
+ if (ToLowerASCII(a[0]) != ToLowerASCII(b[0])) {
+ return false;
+ }
+
+ if (a[1] != ':' || b[1] != ':') {
+ return false;
+ }
+
+ return IsPathSeparator(a[2]) && IsPathSeparator(b[2]);
+}
+
+// Return true if paths a and b are on the same Windows drive.
bool SameDrive(StringPiece a, StringPiece b) {
+ if (SameDriveFast(a, b)) {
+ return true;
+ }
+
char a_absolute[_MAX_PATH];
char b_absolute[_MAX_PATH];
GetFullPathName(a.AsString().c_str(), sizeof(a_absolute), a_absolute, NULL);
@@ -38,34 +70,57 @@ bool SameDrive(StringPiece a, StringPiece b) {
return _stricmp(a_drive, b_drive) == 0;
}
-} // anonymous namespace
+// Check path |s| is FullPath style returned by GetFullPathName.
+// This ignores difference of path separator.
+// This is used not to call very slow GetFullPathName API.
+bool IsFullPathName(StringPiece s) {
+ if (s.size() < 3 ||
+ !islatinalpha(s[0]) ||
+ s[1] != ':' ||
+ !IsPathSeparator(s[2])) {
+ return false;
+ }
+
+ // Check "." or ".." is contained in path.
+ for (size_t i = 2; i < s.size(); ++i) {
+ if (!IsPathSeparator(s[i])) {
+ continue;
+ }
+
+ // Check ".".
+ if (i + 1 < s.size() && s[i+1] == '.' &&
+ (i + 2 >= s.size() || IsPathSeparator(s[i+2]))) {
+ return false;
+ }
-string IncludesNormalize::Join(const vector<string>& list, char sep) {
- string ret;
- for (size_t i = 0; i < list.size(); ++i) {
- ret += list[i];
- if (i != list.size() - 1)
- ret += sep;
+ // Check "..".
+ if (i + 2 < s.size() && s[i+1] == '.' && s[i+2] == '.' &&
+ (i + 3 >= s.size() || IsPathSeparator(s[i+3]))) {
+ return false;
+ }
}
- return ret;
-}
-vector<string> IncludesNormalize::Split(const string& input, char sep) {
- vector<string> elems;
- stringstream ss(input);
- string item;
- while (getline(ss, item, sep))
- elems.push_back(item);
- return elems;
+ return true;
}
-string IncludesNormalize::ToLower(const string& s) {
- string ret;
- transform(s.begin(), s.end(), back_inserter(ret), ::tolower);
- return ret;
+} // anonymous namespace
+
+IncludesNormalize::IncludesNormalize(const string& relative_to) {
+ relative_to_ = AbsPath(relative_to);
+ split_relative_to_ = SplitStringPiece(relative_to_, '/');
}
string IncludesNormalize::AbsPath(StringPiece s) {
+ if (IsFullPathName(s)) {
+ string result = s.AsString();
+ for (size_t i = 0; i < result.size(); ++i) {
+ if (result[i] == '\\') {
+ result[i] = '/';
+ }
+ }
+ return result;
+ }
+
char result[_MAX_PATH];
GetFullPathName(s.AsString().c_str(), sizeof(result), result, NULL);
for (char* c = result; *c; ++c)
@@ -74,28 +129,31 @@ string IncludesNormalize::AbsPath(StringPiece s) {
return result;
}
-string IncludesNormalize::Relativize(StringPiece path, const string& start) {
- vector<string> start_list = Split(AbsPath(start), '/');
- vector<string> path_list = Split(AbsPath(path), '/');
+string IncludesNormalize::Relativize(
+ StringPiece path, const vector<StringPiece>& start_list) {
+ string abs_path = AbsPath(path);
+ vector<StringPiece> path_list = SplitStringPiece(abs_path, '/');
int i;
for (i = 0; i < static_cast<int>(min(start_list.size(), path_list.size()));
++i) {
- if (ToLower(start_list[i]) != ToLower(path_list[i]))
+ if (!EqualsCaseInsensitiveASCII(start_list[i], path_list[i])) {
break;
+ }
}
- vector<string> rel_list;
+ vector<StringPiece> rel_list;
+ rel_list.reserve(start_list.size() - i + path_list.size() - i);
for (int j = 0; j < static_cast<int>(start_list.size() - i); ++j)
rel_list.push_back("..");
for (int j = i; j < static_cast<int>(path_list.size()); ++j)
rel_list.push_back(path_list[j]);
if (rel_list.size() == 0)
return ".";
- return Join(rel_list, '/');
+ return JoinStringPiece(rel_list, '/');
}
-bool IncludesNormalize::Normalize(const string& input, const char* relative_to,
- string* result, string* err) {
+bool IncludesNormalize::Normalize(const string& input,
+ string* result, string* err) const {
char copy[_MAX_PATH + 1];
size_t len = input.size();
if (len > _MAX_PATH) {
@@ -103,20 +161,16 @@ bool IncludesNormalize::Normalize(const string& input, const char* relative_to,
return false;
}
strncpy(copy, input.c_str(), input.size() + 1);
- unsigned int slash_bits;
+ uint64_t slash_bits;
if (!CanonicalizePath(copy, &len, &slash_bits, err))
return false;
StringPiece partially_fixed(copy, len);
+ string abs_input = AbsPath(partially_fixed);
- string curdir;
- if (!relative_to) {
- curdir = AbsPath(".");
- relative_to = curdir.c_str();
- }
- if (!SameDrive(partially_fixed, relative_to)) {
+ if (!SameDrive(abs_input, relative_to_)) {
*result = partially_fixed.AsString();
return true;
}
- *result = Relativize(partially_fixed, relative_to);
+ *result = Relativize(abs_input, split_relative_to_);
return true;
}
diff --git a/src/includes_normalize.h b/src/includes_normalize.h
index 98e912f..3811e53 100644
--- a/src/includes_normalize.h
+++ b/src/includes_normalize.h
@@ -21,15 +21,19 @@ struct StringPiece;
/// Utility functions for normalizing include paths on Windows.
/// TODO: this likely duplicates functionality of CanonicalizePath; refactor.
struct IncludesNormalize {
+ /// Normalize path relative to |relative_to|.
+ IncludesNormalize(const string& relative_to);
+
// Internal utilities made available for testing, maybe useful otherwise.
- static string Join(const vector<string>& list, char sep);
- static vector<string> Split(const string& input, char sep);
- static string ToLower(const string& s);
static string AbsPath(StringPiece s);
- static string Relativize(StringPiece path, const string& start);
+ static string Relativize(StringPiece path,
+ const vector<StringPiece>& start_list);
/// Normalize by fixing slashes style, fixing redundant .. and . and makes the
- /// path relative to |relative_to|.
- static bool Normalize(const string& input, const char* relative_to,
- string* result, string* err);
+ /// path |input| relative to |this->relative_to_| and store to |result|.
+ bool Normalize(const string& input, string* result, string* err) const;
+
+ private:
+ string relative_to_;
+ vector<StringPiece> split_relative_to_;
};
diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc
index f18795c..0bb14ec 100644
--- a/src/includes_normalize_test.cc
+++ b/src/includes_normalize_test.cc
@@ -18,6 +18,7 @@
#include <direct.h>
+#include "string_piece_util.h"
#include "test.h"
#include "util.h"
@@ -26,13 +27,14 @@ namespace {
string GetCurDir() {
char buf[_MAX_PATH];
_getcwd(buf, sizeof(buf));
- vector<string> parts = IncludesNormalize::Split(string(buf), '\\');
- return parts[parts.size() - 1];
+ vector<StringPiece> parts = SplitStringPiece(buf, '\\');
+ return parts[parts.size() - 1].AsString();
}
string NormalizeAndCheckNoError(const string& input) {
string result, err;
- EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), NULL, &result, &err));
+ IncludesNormalize normalizer(".");
+ EXPECT_TRUE(normalizer.Normalize(input, &result, &err));
EXPECT_EQ("", err);
return result;
}
@@ -40,8 +42,8 @@ string NormalizeAndCheckNoError(const string& input) {
string NormalizeRelativeAndCheckNoError(const string& input,
const string& relative_to) {
string result, err;
- EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), relative_to.c_str(),
- &result, &err));
+ IncludesNormalize normalizer(relative_to);
+ EXPECT_TRUE(normalizer.Normalize(input, &result, &err));
EXPECT_EQ("", err);
return result;
}
@@ -76,34 +78,6 @@ TEST(IncludesNormalize, Case) {
EXPECT_EQ("A/B", NormalizeAndCheckNoError("A\\./B"));
}
-TEST(IncludesNormalize, Join) {
- vector<string> x;
- EXPECT_EQ("", IncludesNormalize::Join(x, ':'));
- x.push_back("alpha");
- EXPECT_EQ("alpha", IncludesNormalize::Join(x, ':'));
- x.push_back("beta");
- x.push_back("gamma");
- EXPECT_EQ("alpha:beta:gamma", IncludesNormalize::Join(x, ':'));
-}
-
-TEST(IncludesNormalize, Split) {
- EXPECT_EQ("", IncludesNormalize::Join(IncludesNormalize::Split("", '/'),
- ':'));
- EXPECT_EQ("a", IncludesNormalize::Join(IncludesNormalize::Split("a", '/'),
- ':'));
- EXPECT_EQ("a:b:c",
- IncludesNormalize::Join(
- IncludesNormalize::Split("a/b/c", '/'), ':'));
-}
-
-TEST(IncludesNormalize, ToLower) {
- EXPECT_EQ("", IncludesNormalize::ToLower(""));
- EXPECT_EQ("stuff", IncludesNormalize::ToLower("Stuff"));
- EXPECT_EQ("stuff and things", IncludesNormalize::ToLower("Stuff AND thINGS"));
- EXPECT_EQ("stuff 3and thin43gs",
- IncludesNormalize::ToLower("Stuff 3AND thIN43GS"));
-}
-
TEST(IncludesNormalize, DifferentDrive) {
EXPECT_EQ("stuff.h",
NormalizeRelativeAndCheckNoError("p:\\vs08\\stuff.h", "p:\\vs08"));
@@ -129,8 +103,9 @@ TEST(IncludesNormalize, LongInvalidPath) {
"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;
+ IncludesNormalize normalizer(".");
EXPECT_FALSE(
- IncludesNormalize::Normalize(kLongInputString, NULL, &result, &err));
+ normalizer.Normalize(kLongInputString, &result, &err));
EXPECT_EQ("path too long", err);
const char kExactlyMaxPath[] =
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index a4f489e..2164921 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -212,7 +212,7 @@ bool ManifestParser::ParseDefault(string* err) {
do {
string path = eval.Evaluate(env_);
string path_err;
- unsigned int slash_bits; // Unused because this only does lookup.
+ uint64_t slash_bits; // Unused because this only does lookup.
if (!CanonicalizePath(&path, &slash_bits, &path_err))
return lexer_.Error(path_err, err);
if (!state_->AddDefault(path, &path_err))
@@ -236,16 +236,13 @@ bool ManifestParser::ParseEdge(string* err) {
EvalString out;
if (!lexer_.ReadPath(&out, err))
return false;
- if (out.empty())
- return lexer_.Error("expected path", err);
-
- do {
+ while (!out.empty()) {
outs.push_back(out);
out.Clear();
if (!lexer_.ReadPath(&out, err))
return false;
- } while (!out.empty());
+ }
}
// Add all implicit outs, counting how many as we go.
@@ -262,6 +259,9 @@ bool ManifestParser::ParseEdge(string* err) {
}
}
+ if (outs.empty())
+ return lexer_.Error("expected path", err);
+
if (!ExpectToken(Lexer::COLON, err))
return false;
@@ -342,7 +342,7 @@ bool ManifestParser::ParseEdge(string* err) {
for (size_t i = 0, e = outs.size(); i != e; ++i) {
string path = outs[i].Evaluate(env);
string path_err;
- unsigned int slash_bits;
+ uint64_t slash_bits;
if (!CanonicalizePath(&path, &slash_bits, &path_err))
return lexer_.Error(path_err, err);
if (!state_->AddOut(edge, path, slash_bits)) {
@@ -375,7 +375,7 @@ bool ManifestParser::ParseEdge(string* err) {
for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) {
string path = i->Evaluate(env);
string path_err;
- unsigned int slash_bits;
+ uint64_t slash_bits;
if (!CanonicalizePath(&path, &slash_bits, &path_err))
return lexer_.Error(path_err, err);
state_->AddIn(edge, path, slash_bits);
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
index 1312d26..3c82dc5 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -976,13 +976,10 @@ TEST_F(ParserTest, ImplicitOutputDupes) {
TEST_F(ParserTest, NoExplicitOutput) {
ManifestParser parser(&state, NULL, kDupeEdgeActionWarn);
string err;
- EXPECT_FALSE(parser.ParseTest(
+ EXPECT_TRUE(parser.ParseTest(
"rule cat\n"
" command = cat $in > $out\n"
"build | imp : cat bar\n", &err));
- ASSERT_EQ("input:3: expected path\n"
- "build | imp : cat bar\n"
- " ^ near here", err);
}
TEST_F(ParserTest, DefaultDefault) {
diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc
index c611919..1efb085 100644
--- a/src/minidump-win32.cc
+++ b/src/minidump-win32.cc
@@ -34,7 +34,7 @@ void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep) {
char temp_path[MAX_PATH];
GetTempPath(sizeof(temp_path), temp_path);
char temp_file[MAX_PATH];
- sprintf(temp_file, "%s\\ninja_crash_dump_%d.dmp",
+ sprintf(temp_file, "%s\\ninja_crash_dump_%lu.dmp",
temp_path, GetCurrentProcessId());
// Delete any previous minidump of the same name.
diff --git a/src/ninja.cc b/src/ninja.cc
index 25eafe8..54de7b9 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -233,7 +233,7 @@ int GuessParallelism() {
/// Returns true if the manifest was rebuilt.
bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
string path = input_file;
- unsigned int slash_bits; // Unused because this path is only used for lookup.
+ uint64_t slash_bits; // Unused because this path is only used for lookup.
if (!CanonicalizePath(&path, &slash_bits, err))
return false;
Node* node = state_.LookupNode(path);
@@ -247,15 +247,24 @@ bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
if (builder.AlreadyUpToDate())
return false; // Not an error, but we didn't rebuild.
- // Even if the manifest was cleaned by a restat rule, claim that it was
- // rebuilt. Not doing so can lead to crashes, see
- // https://github.com/ninja-build/ninja/issues/874
- return builder.Build(err);
+ if (!builder.Build(err))
+ return false;
+
+ // The manifest was only rebuilt if it is now dirty (it may have been cleaned
+ // by a restat).
+ if (!node->dirty()) {
+ // Reset the state to prevent problems like
+ // https://github.com/ninja-build/ninja/issues/874
+ state_.Reset();
+ return false;
+ }
+
+ return true;
}
Node* NinjaMain::CollectTarget(const char* cpath, string* err) {
string path = cpath;
- unsigned int slash_bits;
+ uint64_t slash_bits;
if (!CanonicalizePath(&path, &slash_bits, err))
return NULL;
@@ -533,21 +542,51 @@ int NinjaMain::ToolTargets(const Options* options, int argc, char* argv[]) {
}
}
-void PrintCommands(Edge* edge, set<Edge*>* seen) {
+enum PrintCommandMode { PCM_Single, PCM_All };
+void PrintCommands(Edge* edge, set<Edge*>* seen, PrintCommandMode mode) {
if (!edge)
return;
if (!seen->insert(edge).second)
return;
- for (vector<Node*>::iterator in = edge->inputs_.begin();
- in != edge->inputs_.end(); ++in)
- PrintCommands((*in)->in_edge(), seen);
+ if (mode == PCM_All) {
+ for (vector<Node*>::iterator in = edge->inputs_.begin();
+ in != edge->inputs_.end(); ++in)
+ PrintCommands((*in)->in_edge(), seen, mode);
+ }
if (!edge->is_phony())
puts(edge->EvaluateCommand().c_str());
}
int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) {
+ // The clean tool uses getopt, and expects argv[0] to contain the name of
+ // the tool, i.e. "commands".
+ ++argc;
+ --argv;
+
+ PrintCommandMode mode = PCM_All;
+
+ optind = 1;
+ int opt;
+ while ((opt = getopt(argc, argv, const_cast<char*>("hs"))) != -1) {
+ switch (opt) {
+ case 's':
+ mode = PCM_Single;
+ break;
+ case 'h':
+ default:
+ printf("usage: ninja -t commands [options] [targets]\n"
+"\n"
+"options:\n"
+" -s only print the final command to build [target], not the whole chain\n"
+ );
+ return 1;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
vector<Node*> nodes;
string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
@@ -557,7 +596,7 @@ int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) {
set<Edge*> seen;
for (vector<Node*>::iterator in = nodes.begin(); in != nodes.end(); ++in)
- PrintCommands((*in)->in_edge(), &seen);
+ PrintCommands((*in)->in_edge(), &seen, mode);
return 0;
}
diff --git a/src/state.cc b/src/state.cc
index a70f211..6079229 100644
--- a/src/state.cc
+++ b/src/state.cc
@@ -100,7 +100,7 @@ Edge* State::AddEdge(const Rule* rule) {
return edge;
}
-Node* State::GetNode(StringPiece path, unsigned int slash_bits) {
+Node* State::GetNode(StringPiece path, uint64_t slash_bits) {
Node* node = LookupNode(path);
if (node)
return node;
@@ -134,13 +134,13 @@ Node* State::SpellcheckNode(const string& path) {
return result;
}
-void State::AddIn(Edge* edge, StringPiece path, unsigned int slash_bits) {
+void State::AddIn(Edge* edge, StringPiece path, uint64_t slash_bits) {
Node* node = GetNode(path, slash_bits);
edge->inputs_.push_back(node);
node->AddOutEdge(edge);
}
-bool State::AddOut(Edge* edge, StringPiece path, unsigned int slash_bits) {
+bool State::AddOut(Edge* edge, StringPiece path, uint64_t slash_bits) {
Node* node = GetNode(path, slash_bits);
if (node->in_edge())
return false;
@@ -159,11 +159,12 @@ bool State::AddDefault(StringPiece path, string* err) {
return true;
}
-vector<Node*> State::RootNodes(string* err) {
+vector<Node*> State::RootNodes(string* err) const {
vector<Node*> root_nodes;
// Search for nodes with no output.
- for (vector<Edge*>::iterator e = edges_.begin(); e != edges_.end(); ++e) {
- for (vector<Node*>::iterator out = (*e)->outputs_.begin();
+ for (vector<Edge*>::const_iterator e = edges_.begin();
+ e != edges_.end(); ++e) {
+ for (vector<Node*>::const_iterator out = (*e)->outputs_.begin();
out != (*e)->outputs_.end(); ++out) {
if ((*out)->out_edges().empty())
root_nodes.push_back(*out);
@@ -176,7 +177,7 @@ vector<Node*> State::RootNodes(string* err) {
return root_nodes;
}
-vector<Node*> State::DefaultNodes(string* err) {
+vector<Node*> State::DefaultNodes(string* err) const {
return defaults_.empty() ? RootNodes(err) : defaults_;
}
diff --git a/src/state.h b/src/state.h
index d7987ba..54e9dc5 100644
--- a/src/state.h
+++ b/src/state.h
@@ -23,6 +23,7 @@ using namespace std;
#include "eval_env.h"
#include "hash_map.h"
+#include "util.h"
struct Edge;
struct Node;
@@ -93,12 +94,12 @@ struct State {
Edge* AddEdge(const Rule* rule);
- Node* GetNode(StringPiece path, unsigned int slash_bits);
+ Node* GetNode(StringPiece path, uint64_t slash_bits);
Node* LookupNode(StringPiece path) const;
Node* SpellcheckNode(const string& path);
- void AddIn(Edge* edge, StringPiece path, unsigned int slash_bits);
- bool AddOut(Edge* edge, StringPiece path, unsigned int slash_bits);
+ void AddIn(Edge* edge, StringPiece path, uint64_t slash_bits);
+ bool AddOut(Edge* edge, StringPiece path, uint64_t slash_bits);
bool AddDefault(StringPiece path, string* error);
/// Reset state. Keeps all nodes and edges, but restores them to the
@@ -110,8 +111,8 @@ struct State {
/// @return the root node(s) of the graph. (Root nodes have no output edges).
/// @param error where to write the error message if somethings went wrong.
- vector<Node*> RootNodes(string* error);
- vector<Node*> DefaultNodes(string* error);
+ vector<Node*> RootNodes(string* error) const;
+ vector<Node*> DefaultNodes(string* error) const;
/// Mapping of path -> Node.
typedef ExternalStringHashMap<Node*>::Type Paths;
diff --git a/src/string_piece.h b/src/string_piece.h
index b1bf105..031bda4 100644
--- a/src/string_piece.h
+++ b/src/string_piece.h
@@ -25,6 +25,8 @@ using namespace std;
/// externally. It is useful for reducing the number of std::strings
/// we need to allocate.
struct StringPiece {
+ typedef const char* const_iterator;
+
StringPiece() : str_(NULL), len_(0) {}
/// The constructors intentionally allow for implicit conversions.
@@ -46,6 +48,22 @@ struct StringPiece {
return len_ ? string(str_, len_) : string();
}
+ const_iterator begin() const {
+ return str_;
+ }
+
+ const_iterator end() const {
+ return str_ + len_;
+ }
+
+ char operator[](size_t pos) const {
+ return str_[pos];
+ }
+
+ size_t size() const {
+ return len_;
+ }
+
const char* str_;
size_t len_;
};
diff --git a/src/string_piece_util.cc b/src/string_piece_util.cc
new file mode 100644
index 0000000..8e1ecfd
--- /dev/null
+++ b/src/string_piece_util.cc
@@ -0,0 +1,78 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "string_piece_util.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+using namespace std;
+
+vector<StringPiece> SplitStringPiece(StringPiece input, char sep) {
+ vector<StringPiece> elems;
+ elems.reserve(count(input.begin(), input.end(), sep) + 1);
+
+ StringPiece::const_iterator pos = input.begin();
+
+ for (;;) {
+ const char* next_pos = find(pos, input.end(), sep);
+ if (next_pos == input.end()) {
+ elems.push_back(StringPiece(pos, input.end() - pos));
+ break;
+ }
+ elems.push_back(StringPiece(pos, next_pos - pos));
+ pos = next_pos + 1;
+ }
+
+ return elems;
+}
+
+string JoinStringPiece(const vector<StringPiece>& list, char sep) {
+ if (list.size() == 0){
+ return "";
+ }
+
+ string ret;
+
+ {
+ size_t cap = list.size() - 1;
+ for (size_t i = 0; i < list.size(); ++i) {
+ cap += list[i].len_;
+ }
+ ret.reserve(cap);
+ }
+
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (i != 0) {
+ ret += sep;
+ }
+ ret.append(list[i].str_, list[i].len_);
+ }
+
+ return ret;
+}
+
+bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b) {
+ if (a.len_ != b.len_) {
+ return false;
+ }
+
+ for (size_t i = 0; i < a.len_; ++i) {
+ if (ToLowerASCII(a.str_[i]) != ToLowerASCII(b.str_[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/string_piece_util.h b/src/string_piece_util.h
new file mode 100644
index 0000000..2e40b9f
--- /dev/null
+++ b/src/string_piece_util.h
@@ -0,0 +1,34 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NINJA_STRINGPIECE_UTIL_H_
+#define NINJA_STRINGPIECE_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "string_piece.h"
+using namespace std;
+
+vector<StringPiece> SplitStringPiece(StringPiece input, char sep);
+
+string JoinStringPiece(const vector<StringPiece>& list, char sep);
+
+inline char ToLowerASCII(char c) {
+ return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
+}
+
+bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b);
+
+#endif // NINJA_STRINGPIECE_UTIL_H_
diff --git a/src/string_piece_util_test.cc b/src/string_piece_util_test.cc
new file mode 100644
index 0000000..648c647
--- /dev/null
+++ b/src/string_piece_util_test.cc
@@ -0,0 +1,129 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "string_piece_util.h"
+
+#include "test.h"
+
+TEST(StringPieceUtilTest, SplitStringPiece) {
+ {
+ string input("a:b:c");
+ vector<StringPiece> list = SplitStringPiece(input, ':');
+
+ EXPECT_EQ(list.size(), 3);
+
+ EXPECT_EQ(list[0], "a");
+ EXPECT_EQ(list[1], "b");
+ EXPECT_EQ(list[2], "c");
+ }
+
+ {
+ string empty("");
+ vector<StringPiece> list = SplitStringPiece(empty, ':');
+
+ EXPECT_EQ(list.size(), 1);
+
+ EXPECT_EQ(list[0], "");
+ }
+
+ {
+ string one("a");
+ vector<StringPiece> list = SplitStringPiece(one, ':');
+
+ EXPECT_EQ(list.size(), 1);
+
+ EXPECT_EQ(list[0], "a");
+ }
+
+ {
+ string sep_only(":");
+ vector<StringPiece> list = SplitStringPiece(sep_only, ':');
+
+ EXPECT_EQ(list.size(), 2);
+
+ EXPECT_EQ(list[0], "");
+ EXPECT_EQ(list[1], "");
+ }
+
+ {
+ string sep(":a:b:c:");
+ vector<StringPiece> list = SplitStringPiece(sep, ':');
+
+ EXPECT_EQ(list.size(), 5);
+
+ EXPECT_EQ(list[0], "");
+ EXPECT_EQ(list[1], "a");
+ EXPECT_EQ(list[2], "b");
+ EXPECT_EQ(list[3], "c");
+ EXPECT_EQ(list[4], "");
+ }
+}
+
+TEST(StringPieceUtilTest, JoinStringPiece) {
+ {
+ string input("a:b:c");
+ vector<StringPiece> list = SplitStringPiece(input, ':');
+
+ EXPECT_EQ("a:b:c", JoinStringPiece(list, ':'));
+ EXPECT_EQ("a/b/c", JoinStringPiece(list, '/'));
+ }
+
+ {
+ string empty("");
+ vector<StringPiece> list = SplitStringPiece(empty, ':');
+
+ EXPECT_EQ("", JoinStringPiece(list, ':'));
+ }
+
+ {
+ vector<StringPiece> empty_list;
+
+ EXPECT_EQ("", JoinStringPiece(empty_list, ':'));
+ }
+
+ {
+ string one("a");
+ vector<StringPiece> single_list = SplitStringPiece(one, ':');
+
+ EXPECT_EQ("a", JoinStringPiece(single_list, ':'));
+ }
+
+ {
+ string sep(":a:b:c:");
+ vector<StringPiece> list = SplitStringPiece(sep, ':');
+
+ EXPECT_EQ(":a:b:c:", JoinStringPiece(list, ':'));
+ }
+}
+
+TEST(StringPieceUtilTest, ToLowerASCII) {
+ EXPECT_EQ('a', ToLowerASCII('A'));
+ EXPECT_EQ('z', ToLowerASCII('Z'));
+ EXPECT_EQ('a', ToLowerASCII('a'));
+ EXPECT_EQ('z', ToLowerASCII('z'));
+ EXPECT_EQ('/', ToLowerASCII('/'));
+ EXPECT_EQ('1', ToLowerASCII('1'));
+}
+
+TEST(StringPieceUtilTest, EqualsCaseInsensitiveASCII) {
+ EXPECT_TRUE(EqualsCaseInsensitiveASCII("abc", "abc"));
+ EXPECT_TRUE(EqualsCaseInsensitiveASCII("abc", "ABC"));
+ EXPECT_TRUE(EqualsCaseInsensitiveASCII("abc", "aBc"));
+ EXPECT_TRUE(EqualsCaseInsensitiveASCII("AbC", "aBc"));
+ EXPECT_TRUE(EqualsCaseInsensitiveASCII("", ""));
+
+ EXPECT_FALSE(EqualsCaseInsensitiveASCII("a", "ac"));
+ EXPECT_FALSE(EqualsCaseInsensitiveASCII("/", "\\"));
+ EXPECT_FALSE(EqualsCaseInsensitiveASCII("1", "10"));
+}
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index 5ffe85b..1de22c3 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -73,8 +73,6 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
// default action in the new process image, so no explicit
// POSIX_SPAWN_SETSIGDEF parameter is needed.
- // TODO: Consider using POSIX_SPAWN_USEVFORK on Linux with glibc?
-
if (!use_console_) {
// Put the child in its own process group, so ctrl-c won't reach it.
flags |= POSIX_SPAWN_SETPGROUP;
@@ -90,9 +88,14 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
Fatal("posix_spawn_file_actions_adddup2: %s", strerror(errno));
if (posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2) != 0)
Fatal("posix_spawn_file_actions_adddup2: %s", strerror(errno));
+ if (posix_spawn_file_actions_addclose(&action, output_pipe[1]) != 0)
+ Fatal("posix_spawn_file_actions_addclose: %s", strerror(errno));
// In the console case, output_pipe is still inherited by the child and
// closed when the subprocess finishes, which then notifies ninja.
}
+#ifdef POSIX_SPAWN_USEVFORK
+ flags |= POSIX_SPAWN_USEVFORK;
+#endif
if (posix_spawnattr_setflags(&attr, flags) != 0)
Fatal("posix_spawnattr_setflags: %s", strerror(errno));
diff --git a/src/subprocess.h b/src/subprocess.h
index 51f40b2..b2d486c 100644
--- a/src/subprocess.h
+++ b/src/subprocess.h
@@ -26,6 +26,14 @@ using namespace std;
#include <signal.h>
#endif
+// ppoll() exists on FreeBSD, but only on newer versions.
+#ifdef __FreeBSD__
+# include <sys/param.h>
+# if defined USE_PPOLL && __FreeBSD_version < 1002000
+# undef USE_PPOLL
+# endif
+#endif
+
#include "exit_status.h"
/// Subprocess wraps a single async subprocess. It is entirely
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index ee16190..0a8c206 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -214,9 +214,7 @@ TEST_F(SubprocessTest, SetWithMulti) {
}
}
-// OS X's process limit is less than 1025 by default
-// (|sysctl kern.maxprocperuid| is 709 on 10.7 and 10.8 and less prior to that).
-#if !defined(__APPLE__) && !defined(_WIN32)
+#if defined(USE_PPOLL)
TEST_F(SubprocessTest, SetWithLots) {
// Arbitrary big number; needs to be over 1024 to confirm we're no longer
// hostage to pselect.
diff --git a/src/util.cc b/src/util.cc
index e31fd1f..84de879 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -90,7 +90,7 @@ void Error(const char* msg, ...) {
fprintf(stderr, "\n");
}
-bool CanonicalizePath(string* path, unsigned int* slash_bits, string* err) {
+bool CanonicalizePath(string* path, uint64_t* slash_bits, string* err) {
METRIC_RECORD("canonicalize str");
size_t len = path->size();
char* str = 0;
@@ -103,19 +103,19 @@ bool CanonicalizePath(string* path, unsigned int* slash_bits, string* err) {
}
#ifdef _WIN32
-static unsigned int ShiftOverBit(int offset, unsigned int bits) {
+static uint64_t ShiftOverBit(int offset, uint64_t bits) {
// e.g. for |offset| == 2:
// | ... 9 8 7 6 5 4 3 2 1 0 |
// \_________________/ \_/
// above below
// So we drop the bit at offset and move above "down" into its place.
- unsigned int above = bits & ~((1 << (offset + 1)) - 1);
- unsigned int below = bits & ((1 << offset) - 1);
+ uint64_t above = bits & ~((1 << (offset + 1)) - 1);
+ uint64_t below = bits & ((1 << offset) - 1);
return (above >> 1) | below;
}
#endif
-bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits,
+bool CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits,
string* err) {
// WARNING: this function is performance-critical; please benchmark
// any changes you make to it.
@@ -125,7 +125,7 @@ bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits,
return false;
}
- const int kMaxPathComponents = 30;
+ const int kMaxPathComponents = 60;
char* components[kMaxPathComponents];
int component_count = 0;
@@ -135,8 +135,8 @@ bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits,
const char* end = start + *len;
#ifdef _WIN32
- unsigned int bits = 0;
- unsigned int bits_mask = 1;
+ uint64_t bits = 0;
+ uint64_t bits_mask = 1;
int bits_offset = 0;
// Convert \ to /, setting a bit in |bits| for each \ encountered.
for (char* c = path; c < end; ++c) {
@@ -150,7 +150,7 @@ bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits,
bits_offset++;
}
}
- if (bits_offset > 32) {
+ if (bits_offset > 64) {
*err = "too many path components";
return false;
}
@@ -471,7 +471,7 @@ void Win32Fatal(const char* function) {
}
#endif
-static bool islatinalpha(int c) {
+bool islatinalpha(int c) {
// isalpha() is locale-dependent.
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
@@ -585,6 +585,13 @@ double GetLoadAverage() {
// Calculation taken from comment in libperfstats.h
return double(cpu_stats.loadavg[0]) / double(1 << SBITS);
}
+#elif defined(__UCLIBC__)
+double GetLoadAverage() {
+ struct sysinfo si;
+ if (sysinfo(&si) != 0)
+ return -0.0f;
+ return 1.0 / (1 << SI_LOAD_SHIFT) * si.loads[0];
+}
#else
double GetLoadAverage() {
double loadavg[3] = { 0.0f, 0.0f, 0.0f };
diff --git a/src/util.h b/src/util.h
index cbdc1a6..4ee41a5 100644
--- a/src/util.h
+++ b/src/util.h
@@ -43,8 +43,8 @@ void Error(const char* msg, ...);
/// Canonicalize a path like "foo/../bar.h" into just "bar.h".
/// |slash_bits| has bits set starting from lowest for a backslash that was
/// normalized to a forward slash. (only used on Windows)
-bool CanonicalizePath(string* path, unsigned int* slash_bits, string* err);
-bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits,
+bool CanonicalizePath(string* path, uint64_t* slash_bits, string* err);
+bool CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits,
string* err);
/// Appends |input| to |*result|, escaping according to the whims of either
@@ -70,6 +70,8 @@ const char* SpellcheckStringV(const string& text,
/// Like SpellcheckStringV, but takes a NULL-terminated list.
const char* SpellcheckString(const char* text, ...);
+bool islatinalpha(int c);
+
/// Removes all Ansi escape codes (http://www.termsys.demon.co.uk/vtansi.htm).
string StripAnsiEscapeCodes(const string& in);
diff --git a/src/util_test.cc b/src/util_test.cc
index 33a4107..45d0727 100644
--- a/src/util_test.cc
+++ b/src/util_test.cc
@@ -19,7 +19,7 @@
namespace {
bool CanonicalizePath(string* path, string* err) {
- unsigned int unused;
+ uint64_t unused;
return ::CanonicalizePath(path, &unused, err);
}
@@ -177,7 +177,7 @@ TEST(CanonicalizePath, PathSamplesWindows) {
TEST(CanonicalizePath, SlashTracking) {
string path;
string err;
- unsigned int slash_bits;
+ uint64_t slash_bits;
path = "foo.h"; err = "";
EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err));
@@ -263,7 +263,7 @@ TEST(CanonicalizePath, SlashTracking) {
TEST(CanonicalizePath, CanonicalizeNotExceedingLen) {
// Make sure searching \/ doesn't go past supplied len.
char buf[] = "foo/bar\\baz.h\\"; // Last \ past end.
- unsigned int slash_bits;
+ uint64_t slash_bits;
string err;
size_t size = 13;
EXPECT_TRUE(::CanonicalizePath(buf, &size, &slash_bits, &err));
@@ -274,31 +274,38 @@ TEST(CanonicalizePath, CanonicalizeNotExceedingLen) {
TEST(CanonicalizePath, TooManyComponents) {
string path;
string err;
- unsigned int slash_bits;
+ uint64_t slash_bits;
- // 32 is OK.
- path = "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x.h";
+ // 64 is OK.
+ path = "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./"
+ "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x.h";
EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err));
+ EXPECT_EQ(slash_bits, 0x0);
// Backslashes version.
path =
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\."
- "\\a\\.\\a\\.\\a\\.\\a\\.\\x.h";
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x.h";
+
EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err));
- EXPECT_EQ(slash_bits, 0xffff);
+ EXPECT_EQ(slash_bits, 0xffffffff);
- // 33 is not.
+ // 65 is not.
err = "";
- path =
- "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/x.h";
+ path = "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./"
+ "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x/y.h";
EXPECT_FALSE(CanonicalizePath(&path, &slash_bits, &err));
EXPECT_EQ(err, "too many path components");
// Backslashes version.
err = "";
path =
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\."
- "\\a\\.\\a\\.\\a\\.\\a\\.\\a\\x.h";
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
+ "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x\\y.h";
EXPECT_FALSE(CanonicalizePath(&path, &slash_bits, &err));
EXPECT_EQ(err, "too many path components");
}
@@ -326,7 +333,7 @@ TEST(CanonicalizePath, NotNullTerminated) {
string path;
string err;
size_t len;
- unsigned int unused;
+ uint64_t unused;
path = "foo/. bar/.";
len = strlen("foo/."); // Canonicalize only the part before the space.
diff --git a/src/version.cc b/src/version.cc
index f64a94d..e2a83bb 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -18,7 +18,7 @@
#include "util.h"
-const char* kNinjaVersion = "1.7.1.git";
+const char* kNinjaVersion = "1.7.2.git";
void ParseVersion(const string& version, int* major, int* minor) {
size_t end = version.find('.');