summaryrefslogtreecommitdiff
path: root/ninja
diff options
context:
space:
mode:
authorAndras Becsi <andras.becsi@digia.com>2014-05-22 18:24:25 +0200
committerAndras Becsi <andras.becsi@digia.com>2014-06-04 16:32:40 +0200
commit4ce69f7403811819800e7c5ae1318b2647e778d1 (patch)
tree2ec3a98b5abef002670a0916354eb7e0abfe2aa2 /ninja
parenta6dd70e0328d155d5df8d6df48afbab690b08fb6 (diff)
downloadqtwebengine-chromium-4ce69f7403811819800e7c5ae1318b2647e778d1.tar.gz
Update Chromium snapshot to stable version 33.0.1750.170
This is meant as a baseline commit hence it does not include the patches we need to apply for QtWebEngine. All patches should be rebased on top of this commit so we can get rid of the external patches directory. In future these baseline commits always have to include the exact Chromium version returned by version_resolver.py's currentVersion() in their first line, so that we can retrieve the patches on top to apply on the upstream repository. This also includes a ninja update. Change-Id: I60abeadb785a3b7d149c58b65ddb5a823fed3083 Reviewed-by: Jocelyn Turcotte <jocelyn.turcotte@digia.com>
Diffstat (limited to 'ninja')
-rw-r--r--ninja/.clang-format25
-rwxr-xr-xninja/configure.py9
-rw-r--r--ninja/doc/manual.asciidoc15
-rw-r--r--ninja/misc/bash-completion54
-rw-r--r--ninja/misc/ninja_syntax.py44
-rw-r--r--ninja/misc/write_fake_manifests.py219
-rw-r--r--ninja/misc/zsh-completion47
-rw-r--r--ninja/src/build.cc14
-rw-r--r--ninja/src/build_test.cc62
-rw-r--r--ninja/src/clean_test.cc8
-rw-r--r--ninja/src/depfile_parser.cc20
-rw-r--r--ninja/src/depfile_parser.in.cc6
-rw-r--r--ninja/src/depfile_parser_perftest.cc (renamed from ninja/src/parser_perftest.cc)0
-rw-r--r--ninja/src/depfile_parser_test.cc19
-rw-r--r--ninja/src/disk_interface.cc16
-rw-r--r--ninja/src/disk_interface_test.cc13
-rw-r--r--ninja/src/graph.cc4
-rw-r--r--ninja/src/graph.h1
-rw-r--r--ninja/src/line_printer.cc51
-rw-r--r--ninja/src/line_printer.h20
-rw-r--r--ninja/src/manifest_parser.cc9
-rw-r--r--ninja/src/manifest_parser_perftest.cc114
-rw-r--r--ninja/src/msvc_helper-win32.cc2
-rw-r--r--ninja/src/msvc_helper_test.cc7
-rw-r--r--ninja/src/ninja.cc2
-rw-r--r--ninja/src/state.cc2
-rw-r--r--ninja/src/state.h1
-rw-r--r--ninja/src/subprocess-posix.cc53
-rw-r--r--ninja/src/subprocess-win32.cc30
-rw-r--r--ninja/src/subprocess.h5
-rw-r--r--ninja/src/subprocess_test.cc15
31 files changed, 758 insertions, 129 deletions
diff --git a/ninja/.clang-format b/ninja/.clang-format
new file mode 100644
index 00000000000..1841c036f9c
--- /dev/null
+++ b/ninja/.clang-format
@@ -0,0 +1,25 @@
+# Copyright 2014 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.
+
+# This isn't meant to be authoritative, but it's good enough to be useful.
+# Still use your best judgement for formatting decisions: clang-format
+# sometimes makes strange choices.
+
+BasedOnStyle: Google
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+Cpp11BracedListStyle: false
+IndentCaseLabels: false
diff --git a/ninja/configure.py b/ninja/configure.py
index da2f6ef478d..c5a6abdd93c 100755
--- a/ninja/configure.py
+++ b/ninja/configure.py
@@ -377,18 +377,21 @@ all_targets += ninja_test
n.comment('Ancillary executables.')
-objs = cxx('parser_perftest')
-all_targets += n.build(binary('parser_perftest'), 'link', objs,
- implicit=ninja_lib, variables=[('libs', libs)])
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)])
n.newline()
n.comment('Generate a graph using the "graph" tool.')
diff --git a/ninja/doc/manual.asciidoc b/ninja/doc/manual.asciidoc
index 67fcbfd2788..18760dde3f4 100644
--- a/ninja/doc/manual.asciidoc
+++ b/ninja/doc/manual.asciidoc
@@ -648,6 +648,21 @@ build heavy_object2.obj: cc heavy_obj2.cc
----------------
+The `console` pool
+^^^^^^^^^^^^^^^^^^
+
+_Available since Ninja 1.5._
+
+There exists a pre-defined pool named `console` with a depth of 1. It has
+the special property that any task in the pool has direct access to the
+standard input, output and error streams provided to Ninja, which are
+normally connected to the user's console (hence the name) but could be
+redirected. This can be useful for interactive tasks or long-running tasks
+which produce status updates on the console (such as test suites).
+
+While a task in the `console` pool is running, Ninja's regular output (such
+as progress status and output from concurrent tasks) is buffered until
+it completes.
Ninja file reference
--------------------
diff --git a/ninja/misc/bash-completion b/ninja/misc/bash-completion
index 2d6975b9450..6edf4dfea02 100644
--- a/ninja/misc/bash-completion
+++ b/ninja/misc/bash-completion
@@ -16,25 +16,43 @@
# . path/to/ninja/misc/bash-completion
_ninja_target() {
- local cur targets dir line targets_command OPTIND
- cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur prev targets dir line targets_command OPTIND
- if [[ "$cur" == "--"* ]]; then
- # there is currently only one argument that takes --
- COMPREPLY=($(compgen -P '--' -W 'version' -- "${cur:2}"))
- else
- dir="."
- line=$(echo ${COMP_LINE} | cut -d" " -f 2-)
- # filter out all non relevant arguments but keep C for dirs
- while getopts C:f:j:l:k:nvd:t: opt "${line[@]}"; do
- case $opt in
- C) dir="$OPTARG" ;;
- esac
- done;
- targets_command="ninja -C ${dir} -t targets all"
- targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}')
- COMPREPLY=($(compgen -W "$targets" -- "$cur"))
- fi
+ # When available, use bash_completion to:
+ # 1) Complete words when the cursor is in the middle of the word
+ # 2) Complete paths with files or directories, as appropriate
+ if _get_comp_words_by_ref cur prev &>/dev/null ; then
+ case $prev in
+ -f)
+ _filedir
+ return 0
+ ;;
+ -C)
+ _filedir -d
+ return 0
+ ;;
+ esac
+ else
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ fi
+
+ if [[ "$cur" == "--"* ]]; then
+ # there is currently only one argument that takes --
+ COMPREPLY=($(compgen -P '--' -W 'version' -- "${cur:2}"))
+ else
+ dir="."
+ line=$(echo ${COMP_LINE} | cut -d" " -f 2-)
+ # filter out all non relevant arguments but keep C for dirs
+ while getopts :C:f:j:l:k:nvd:t: opt $line; do
+ case $opt in
+ # eval for tilde expansion
+ C) eval dir="$OPTARG" ;;
+ esac
+ done;
+ targets_command="eval ninja -C \"${dir}\" -t targets all"
+ targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}')
+ COMPREPLY=($(compgen -W "$targets" -- "$cur"))
+ fi
return
}
complete -F _ninja_target ninja
diff --git a/ninja/misc/ninja_syntax.py b/ninja/misc/ninja_syntax.py
index d69e3e49e2d..14b932ff4c8 100644
--- a/ninja/misc/ninja_syntax.py
+++ b/ninja/misc/ninja_syntax.py
@@ -8,10 +8,9 @@ use Python.
"""
import textwrap
-import re
def escape_path(word):
- return word.replace('$ ','$$ ').replace(' ','$ ').replace(':', '$:')
+ return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
class Writer(object):
def __init__(self, output, width=78):
@@ -61,21 +60,20 @@ class Writer(object):
def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
variables=None):
outputs = self._as_list(outputs)
- all_inputs = self._as_list(inputs)[:]
- out_outputs = list(map(escape_path, outputs))
- all_inputs = list(map(escape_path, all_inputs))
+ out_outputs = [escape_path(x) for x in outputs]
+ all_inputs = [escape_path(x) for x in self._as_list(inputs)]
if implicit:
- implicit = map(escape_path, self._as_list(implicit))
+ implicit = [escape_path(x) for x in self._as_list(implicit)]
all_inputs.append('|')
all_inputs.extend(implicit)
if order_only:
- order_only = map(escape_path, self._as_list(order_only))
+ order_only = [escape_path(x) for x in self._as_list(order_only)]
all_inputs.append('||')
all_inputs.extend(order_only)
self._line('build %s: %s' % (' '.join(out_outputs),
- ' '.join([rule] + all_inputs)))
+ ' '.join([rule] + all_inputs)))
if variables:
if isinstance(variables, dict):
@@ -98,13 +96,13 @@ class Writer(object):
self._line('default %s' % ' '.join(self._as_list(paths)))
def _count_dollars_before_index(self, s, i):
- """Returns the number of '$' characters right in front of s[i]."""
- dollar_count = 0
- dollar_index = i - 1
- while dollar_index > 0 and s[dollar_index] == '$':
- dollar_count += 1
- dollar_index -= 1
- return dollar_count
+ """Returns the number of '$' characters right in front of s[i]."""
+ dollar_count = 0
+ dollar_index = i - 1
+ while dollar_index > 0 and s[dollar_index] == '$':
+ dollar_count += 1
+ dollar_index -= 1
+ return dollar_count
def _line(self, text, indent=0):
"""Write 'text' word-wrapped at self.width characters."""
@@ -117,19 +115,19 @@ class Writer(object):
available_space = self.width - len(leading_space) - len(' $')
space = available_space
while True:
- space = text.rfind(' ', 0, space)
- if space < 0 or \
- self._count_dollars_before_index(text, space) % 2 == 0:
- break
+ space = text.rfind(' ', 0, space)
+ if (space < 0 or
+ self._count_dollars_before_index(text, space) % 2 == 0):
+ break
if space < 0:
# No such space; just use the first unescaped space we can find.
space = available_space - 1
while True:
- space = text.find(' ', space + 1)
- if space < 0 or \
- self._count_dollars_before_index(text, space) % 2 == 0:
- break
+ space = text.find(' ', space + 1)
+ if (space < 0 or
+ self._count_dollars_before_index(text, space) % 2 == 0):
+ break
if space < 0:
# Give up on breaking.
break
diff --git a/ninja/misc/write_fake_manifests.py b/ninja/misc/write_fake_manifests.py
new file mode 100644
index 00000000000..837007e603b
--- /dev/null
+++ b/ninja/misc/write_fake_manifests.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python
+
+"""Writes large manifest files, for manifest parser performance testing.
+
+The generated manifest files are (eerily) similar in appearance and size to the
+ones used in the Chromium project.
+
+Usage:
+ python misc/write_fake_manifests.py outdir # Will run for about 5s.
+
+The program contains a hardcoded random seed, so it will generate the same
+output every time it runs. By changing the seed, it's easy to generate many
+different sets of manifest files.
+"""
+
+import argparse
+import contextlib
+import os
+import random
+import sys
+
+import ninja_syntax
+
+
+def paretoint(avg, alpha):
+ """Returns a random integer that's avg on average, following a power law.
+ alpha determines the shape of the power curve. alpha has to be larger
+ than 1. The closer alpha is to 1, the higher the variation of the returned
+ numbers."""
+ return int(random.paretovariate(alpha) * avg / (alpha / (alpha - 1)))
+
+
+# Based on http://neugierig.org/software/chromium/class-name-generator.html
+def moar(avg_options, p_suffix):
+ kStart = ['render', 'web', 'browser', 'tab', 'content', 'extension', 'url',
+ 'file', 'sync', 'content', 'http', 'profile']
+ kOption = ['view', 'host', 'holder', 'container', 'impl', 'ref',
+ 'delegate', 'widget', 'proxy', 'stub', 'context',
+ 'manager', 'master', 'watcher', 'service', 'file', 'data',
+ 'resource', 'device', 'info', 'provider', 'internals', 'tracker',
+ 'api', 'layer']
+ kOS = ['win', 'mac', 'aura', 'linux', 'android', 'unittest', 'browsertest']
+ num_options = min(paretoint(avg_options, alpha=4), 5)
+ # The original allows kOption to repeat as long as no consecutive options
+ # repeat. This version doesn't allow any option repetition.
+ name = [random.choice(kStart)] + random.sample(kOption, num_options)
+ if random.random() < p_suffix:
+ name.append(random.choice(kOS))
+ return '_'.join(name)
+
+
+class GenRandom(object):
+ def __init__(self):
+ self.seen_names = set([None])
+ self.seen_defines = set([None])
+
+ def _unique_string(self, seen, avg_options=1.3, p_suffix=0.1):
+ s = None
+ while s in seen:
+ s = moar(avg_options, p_suffix)
+ seen.add(s)
+ return s
+
+ def _n_unique_strings(self, n):
+ seen = set([None])
+ return [self._unique_string(seen, avg_options=3, p_suffix=0.4)
+ for _ in xrange(n)]
+
+ def target_name(self):
+ return self._unique_string(p_suffix=0, seen=self.seen_names)
+
+ def path(self):
+ return os.path.sep.join([
+ self._unique_string(self.seen_names, avg_options=1, p_suffix=0)
+ for _ in xrange(1 + paretoint(0.6, alpha=4))])
+
+ def src_obj_pairs(self, path, name):
+ num_sources = paretoint(55, alpha=2) + 1
+ return [(os.path.join('..', '..', path, s + '.cc'),
+ os.path.join('obj', path, '%s.%s.o' % (name, s)))
+ for s in self._n_unique_strings(num_sources)]
+
+ def defines(self):
+ return [
+ '-DENABLE_' + self._unique_string(self.seen_defines).upper()
+ for _ in xrange(paretoint(20, alpha=3))]
+
+
+LIB, EXE = 0, 1
+class Target(object):
+ def __init__(self, gen, kind):
+ self.name = gen.target_name()
+ self.dir_path = gen.path()
+ self.ninja_file_path = os.path.join(
+ 'obj', self.dir_path, self.name + '.ninja')
+ self.src_obj_pairs = gen.src_obj_pairs(self.dir_path, self.name)
+ if kind == LIB:
+ self.output = os.path.join('lib' + self.name + '.a')
+ elif kind == EXE:
+ self.output = os.path.join(self.name)
+ self.defines = gen.defines()
+ self.deps = []
+ 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):
+ compile_depends = None
+ if target.has_compile_depends:
+ compile_depends = os.path.join(
+ 'obj', target.dir_path, target.name + '.stamp')
+ ninja.build(compile_depends, 'stamp', target.src_obj_pairs[0][0])
+ ninja.newline()
+
+ ninja.variable('defines', target.defines)
+ if target.deps:
+ ninja.variable('includes', target.includes)
+ ninja.variable('cflags', ['-Wall', '-fno-rtti', '-fno-exceptions'])
+ ninja.newline()
+
+ for src, obj in target.src_obj_pairs:
+ ninja.build(obj, 'cxx', src, implicit=compile_depends)
+ ninja.newline()
+
+ 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)
+ link = { LIB: 'alink', EXE: 'link'}[target.kind]
+ ninja.build(target.output, link, [obj for _, obj in target.src_obj_pairs],
+ implicit=deps)
+
+
+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')
+ master_ninja.newline()
+
+ master_ninja.pool('link_pool', depth=4)
+ master_ninja.newline()
+
+ 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('link', description='LINK $out', pool='link_pool',
+ command='$ld $ldflags -o $out $in $libs')
+ master_ninja.rule('stamp', description='STAMP $out', command='touch $out')
+ master_ninja.newline()
+
+ for target in targets:
+ master_ninja.subninja(target.ninja_file_path)
+ master_ninja.newline()
+
+ master_ninja.comment('Short names for targets.')
+ for target in targets:
+ if target.name != target.output:
+ master_ninja.build(target.name, 'phony', target.output)
+ master_ninja.newline()
+
+ master_ninja.build('all', 'phony', [target.output for target in targets])
+ master_ninja.default('all')
+
+
+@contextlib.contextmanager
+def FileWriter(path):
+ """Context manager for a ninja_syntax object writing to a file."""
+ try:
+ os.makedirs(os.path.dirname(path))
+ except OSError:
+ pass
+ f = open(path, 'w')
+ yield ninja_syntax.Writer(f)
+ f.close()
+
+
+def random_targets():
+ num_targets = 800
+ gen = GenRandom()
+
+ # N-1 static libraries, and 1 executable depending on all of them.
+ targets = [Target(gen, LIB) for i in xrange(num_targets - 1)]
+ for i in range(len(targets)):
+ targets[i].deps = [t for t in targets[0:i] if random.random() < 0.05]
+
+ last_target = Target(gen, EXE)
+ last_target.deps = targets[:]
+ last_target.src_obj_pairs = last_target.src_obj_pairs[0:10] # Trim.
+ targets.append(last_target)
+ return targets
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('outdir', help='output directory')
+ args = parser.parse_args()
+ root_dir = args.outdir
+
+ random.seed(12345)
+
+ targets = random_targets()
+ for target in targets:
+ with FileWriter(os.path.join(root_dir, target.ninja_file_path)) as n:
+ write_target_ninja(n, target)
+
+ with FileWriter(os.path.join(root_dir, 'build.ninja')) as master_ninja:
+ master_ninja.width = 120
+ write_master_ninja(master_ninja, targets)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/ninja/misc/zsh-completion b/ninja/misc/zsh-completion
index cd0edfbd97a..2fe16fb0499 100644
--- a/ninja/misc/zsh-completion
+++ b/ninja/misc/zsh-completion
@@ -1,3 +1,4 @@
+#compdef ninja
# Copyright 2011 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +16,47 @@
# Add the following to your .zshrc to tab-complete ninja targets
# . path/to/ninja/misc/zsh-completion
-_ninja() {
- reply=(`(ninja -t targets all 2&>/dev/null) | awk -F: '{print $1}'`)
+__get_targets() {
+ ninja -t targets 2>/dev/null | while read -r a b; do echo $a | cut -d ':' -f1; done;
}
-compctl -K _ninja ninja
+
+__get_tools() {
+ ninja -t list 2>/dev/null | while read -r a b; do echo $a; done | tail -n +2
+}
+
+__get_modes() {
+ ninja -d list 2>/dev/null | while read -r a b; do echo $a; done | tail -n +2 | head -n -1
+}
+
+__modes() {
+ local -a modes
+ modes=(${(fo)"$(__get_modes)"})
+ _describe 'modes' modes
+}
+
+__tools() {
+ local -a tools
+ tools=(${(fo)"$(__get_tools)"})
+ _describe 'tools' tools
+}
+
+__targets() {
+ local -a targets
+ targets=(${(fo)"$(__get_targets)"})
+ _describe 'targets' targets
+}
+
+_arguments \
+ {-h,--help}'[Show help]' \
+ '--version[Print ninja version]' \
+ '-C+[Change to directory before doing anything else]:directories:_directories' \
+ '-f+[Specify input build file (default=build.ninja)]:files:_files' \
+ '-j+[Run N jobs in parallel (default=number of CPUs available)]:number of jobs' \
+ '-l+[Do not start new jobs if the load average is greater than N]:number of jobs' \
+ '-k+[Keep going until N jobs fail (default=1)]:number of jobs' \
+ '-n[Dry run (do not run commands but act like they succeeded)]' \
+ '-v[Show all command lines while building]' \
+ '-d+[Enable debugging (use -d list to list modes)]:modes:__modes' \
+ '-t+[Run a subtool (use -t list to list subtools)]:tools:__tools' \
+ '*::targets:__targets'
+
diff --git a/ninja/src/build.cc b/ninja/src/build.cc
index f91ff2fb2b3..91f1754c150 100644
--- a/ninja/src/build.cc
+++ b/ninja/src/build.cc
@@ -97,6 +97,9 @@ void BuildStatus::BuildEdgeStarted(Edge* edge) {
++started_edges_;
PrintStatus(edge);
+
+ if (edge->use_console())
+ printer_.SetConsoleLocked(true);
}
void BuildStatus::BuildEdgeFinished(Edge* edge,
@@ -112,10 +115,13 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
*end_time = (int)(now - start_time_millis_);
running_edges_.erase(i);
+ if (edge->use_console())
+ printer_.SetConsoleLocked(false);
+
if (config_.verbosity == BuildConfig::QUIET)
return;
- if (printer_.is_smart_terminal())
+ if (!edge->use_console() && printer_.is_smart_terminal())
PrintStatus(edge);
// Print the command that is spewing before printing its output.
@@ -145,6 +151,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
}
void BuildStatus::BuildFinished() {
+ printer_.SetConsoleLocked(false);
printer_.PrintOnNewLine("");
}
@@ -488,7 +495,7 @@ bool RealCommandRunner::CanRunMore() {
bool RealCommandRunner::StartCommand(Edge* edge) {
string command = edge->EvaluateCommand();
- Subprocess* subproc = subprocs_.Add(command);
+ Subprocess* subproc = subprocs_.Add(command, edge->use_console());
if (!subproc)
return false;
subproc_to_edge_.insert(make_pair(subproc, edge));
@@ -610,6 +617,7 @@ bool Builder::Build(string* err) {
if (failures_allowed && command_runner_->CanRunMore()) {
if (Edge* edge = plan_.FindWork()) {
if (!StartEdge(edge, err)) {
+ Cleanup();
status_->BuildFinished();
return false;
}
@@ -630,6 +638,7 @@ bool Builder::Build(string* err) {
CommandRunner::Result result;
if (!command_runner_->WaitForCommand(&result) ||
result.status == ExitInterrupted) {
+ Cleanup();
status_->BuildFinished();
*err = "interrupted by user";
return false;
@@ -637,6 +646,7 @@ bool Builder::Build(string* err) {
--pending_commands;
if (!FinishCommand(&result, err)) {
+ Cleanup();
status_->BuildFinished();
return false;
}
diff --git a/ninja/src/build_test.cc b/ninja/src/build_test.cc
index 86a911bd281..c414c88fa86 100644
--- a/ninja/src/build_test.cc
+++ b/ninja/src/build_test.cc
@@ -44,6 +44,8 @@ struct PlanTest : public StateTestWithBuiltinRules {
ASSERT_FALSE(plan_.FindWork());
sort(ret->begin(), ret->end(), CompareEdgesByOutput::cmp);
}
+
+ void TestPoolWithDepthOne(const char *test_case);
};
TEST_F(PlanTest, Basic) {
@@ -197,15 +199,8 @@ TEST_F(PlanTest, DependencyCycle) {
ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err);
}
-TEST_F(PlanTest, PoolWithDepthOne) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"pool foobar\n"
-" depth = 1\n"
-"rule poolcat\n"
-" command = cat $in > $out\n"
-" pool = foobar\n"
-"build out1: poolcat in\n"
-"build out2: poolcat in\n"));
+void PlanTest::TestPoolWithDepthOne(const char* test_case) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, test_case));
GetNode("out1")->MarkDirty();
GetNode("out2")->MarkDirty();
string err;
@@ -239,6 +234,26 @@ TEST_F(PlanTest, PoolWithDepthOne) {
ASSERT_EQ(0, edge);
}
+TEST_F(PlanTest, PoolWithDepthOne) {
+ TestPoolWithDepthOne(
+"pool foobar\n"
+" depth = 1\n"
+"rule poolcat\n"
+" command = cat $in > $out\n"
+" pool = foobar\n"
+"build out1: poolcat in\n"
+"build out2: poolcat in\n");
+}
+
+TEST_F(PlanTest, ConsolePool) {
+ TestPoolWithDepthOne(
+"rule poolcat\n"
+" command = cat $in > $out\n"
+" pool = console\n"
+"build out1: poolcat in\n"
+"build out2: poolcat in\n");
+}
+
TEST_F(PlanTest, PoolsWithDepthTwo) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"pool foobar\n"
@@ -515,7 +530,8 @@ bool FakeCommandRunner::StartCommand(Edge* edge) {
}
} else if (edge->rule().name() == "true" ||
edge->rule().name() == "fail" ||
- edge->rule().name() == "interrupt") {
+ edge->rule().name() == "interrupt" ||
+ edge->rule().name() == "console") {
// Don't do anything.
} else {
printf("unknown command\n");
@@ -539,6 +555,15 @@ bool FakeCommandRunner::WaitForCommand(Result* result) {
return true;
}
+ if (edge->rule().name() == "console") {
+ if (edge->use_console())
+ result->status = ExitSuccess;
+ else
+ result->status = ExitFailure;
+ last_command_ = NULL;
+ return true;
+ }
+
if (edge->rule().name() == "fail")
result->status = ExitFailure;
else
@@ -1911,3 +1936,20 @@ TEST_F(BuildWithDepsLogTest, RestatMissingDepfileDepslog) {
RebuildTarget("out", manifest, "build_log", "ninja_deps2");
ASSERT_EQ(0u, command_runner_.commands_ran_.size());
}
+
+TEST_F(BuildTest, Console) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule console\n"
+" command = console\n"
+" pool = console\n"
+"build cons: console in.txt\n"));
+
+ fs_.Create("in.txt", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cons", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+}
diff --git a/ninja/src/clean_test.cc b/ninja/src/clean_test.cc
index 04cff734aa7..4a00fd8fe37 100644
--- a/ninja/src/clean_test.cc
+++ b/ninja/src/clean_test.cc
@@ -286,8 +286,7 @@ TEST_F(CleanTest, CleanRspFile) {
" rspfile = $rspfile\n"
" rspfile_content=$in\n"
"build out1: cc in1\n"
-" rspfile = cc1.rsp\n"
-" rspfile_content=$in\n"));
+" rspfile = cc1.rsp\n"));
fs_.Create("out1", "");
fs_.Create("cc1.rsp", "");
@@ -307,10 +306,9 @@ TEST_F(CleanTest, CleanRsp) {
"build out1: cat in1\n"
"build in2: cat_rsp src2\n"
" rspfile=in2.rsp\n"
-" rspfile_content=$in\n"
"build out2: cat_rsp in2\n"
" rspfile=out2.rsp\n"
-" rspfile_content=$in\n"));
+));
fs_.Create("in1", "");
fs_.Create("out1", "");
fs_.Create("in2.rsp", "");
@@ -336,8 +334,6 @@ TEST_F(CleanTest, CleanRsp) {
EXPECT_EQ(0, fs_.Stat("out2"));
EXPECT_EQ(0, fs_.Stat("in2.rsp"));
EXPECT_EQ(0, fs_.Stat("out2.rsp"));
-
- fs_.files_removed_.clear();
}
TEST_F(CleanTest, CleanFailure) {
diff --git a/ninja/src/depfile_parser.cc b/ninja/src/depfile_parser.cc
index 49c7d7b316c..4ca3943ff35 100644
--- a/ninja/src/depfile_parser.cc
+++ b/ninja/src/depfile_parser.cc
@@ -64,7 +64,7 @@ bool DepfileParser::Parse(string* content, string* err) {
0, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 0, 0, 0, 128, 0,
+ 128, 128, 128, 128, 0, 128, 128, 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,
@@ -114,27 +114,29 @@ bool DepfileParser::Parse(string* content, string* err) {
if (yych != '\\') goto yy9;
}
} else {
- if (yych <= 'z') {
+ if (yych <= '{') {
if (yych == '`') goto yy9;
goto yy5;
} else {
- if (yych == '~') goto yy5;
+ if (yych <= '|') goto yy9;
+ if (yych <= '~') goto yy5;
goto yy9;
}
}
}
++in;
- if ((yych = *in) <= '#') {
- if (yych <= '\n') {
+ if ((yych = *in) <= '"') {
+ if (yych <= '\f') {
if (yych <= 0x00) goto yy3;
- if (yych <= '\t') goto yy14;
+ if (yych != '\n') goto yy14;
} else {
+ if (yych <= '\r') goto yy3;
if (yych == ' ') goto yy16;
- if (yych <= '"') goto yy14;
- goto yy16;
+ goto yy14;
}
} else {
if (yych <= 'Z') {
+ if (yych <= '#') goto yy16;
if (yych == '*') goto yy16;
goto yy14;
} else {
@@ -224,7 +226,7 @@ yy16:
} else if (!out_.str_) {
out_ = StringPiece(filename, len);
} else if (out_ != StringPiece(filename, len)) {
- *err = "depfile has multiple output paths.";
+ *err = "depfile has multiple output paths";
return false;
}
}
diff --git a/ninja/src/depfile_parser.in.cc b/ninja/src/depfile_parser.in.cc
index 8bb6d8444d7..b59baf05c93 100644
--- a/ninja/src/depfile_parser.in.cc
+++ b/ninja/src/depfile_parser.in.cc
@@ -67,13 +67,13 @@ bool DepfileParser::Parse(string* content, string* err) {
*out++ = '$';
continue;
}
- '\\' [^\000\n] {
+ '\\' [^\000\r\n] {
// Let backslash before other characters through verbatim.
*out++ = '\\';
*out++ = yych;
continue;
}
- [a-zA-Z0-9+,/_:.~()@=!-]+ {
+ [a-zA-Z0-9+,/_:.~()}{@=!-]+ {
// Got a span of plain text.
int len = (int)(in - start);
// Need to shift it over if we're overwriting backslashes.
@@ -108,7 +108,7 @@ bool DepfileParser::Parse(string* content, string* err) {
} else if (!out_.str_) {
out_ = StringPiece(filename, len);
} else if (out_ != StringPiece(filename, len)) {
- *err = "depfile has multiple output paths.";
+ *err = "depfile has multiple output paths";
return false;
}
}
diff --git a/ninja/src/parser_perftest.cc b/ninja/src/depfile_parser_perftest.cc
index b21522168df..b21522168df 100644
--- a/ninja/src/parser_perftest.cc
+++ b/ninja/src/depfile_parser_perftest.cc
diff --git a/ninja/src/depfile_parser_test.cc b/ninja/src/depfile_parser_test.cc
index 0f6771a9901..a5f33214099 100644
--- a/ninja/src/depfile_parser_test.cc
+++ b/ninja/src/depfile_parser_test.cc
@@ -58,6 +58,17 @@ TEST_F(DepfileParserTest, Continuation) {
EXPECT_EQ(2u, parser_.ins_.size());
}
+TEST_F(DepfileParserTest, CarriageReturnContinuation) {
+ string err;
+ EXPECT_TRUE(Parse(
+"foo.o: \\\r\n"
+" bar.h baz.h\r\n",
+ &err));
+ ASSERT_EQ("", err);
+ EXPECT_EQ("foo.o", parser_.out_.AsString());
+ EXPECT_EQ(2u, parser_.ins_.size());
+}
+
TEST_F(DepfileParserTest, BackSlashes) {
string err;
EXPECT_TRUE(Parse(
@@ -109,16 +120,19 @@ TEST_F(DepfileParserTest, SpecialChars) {
string err;
EXPECT_TRUE(Parse(
"C:/Program\\ Files\\ (x86)/Microsoft\\ crtdefs.h: \n"
-" en@quot.header~ t+t-x!=1",
+" en@quot.header~ t+t-x!=1 \n"
+" openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif",
&err));
ASSERT_EQ("", err);
EXPECT_EQ("C:/Program Files (x86)/Microsoft crtdefs.h",
parser_.out_.AsString());
- ASSERT_EQ(2u, parser_.ins_.size());
+ ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("en@quot.header~",
parser_.ins_[0].AsString());
EXPECT_EQ("t+t-x!=1",
parser_.ins_[1].AsString());
+ EXPECT_EQ("openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif",
+ parser_.ins_[2].AsString());
}
TEST_F(DepfileParserTest, UnifyMultipleOutputs) {
@@ -136,4 +150,5 @@ TEST_F(DepfileParserTest, RejectMultipleDifferentOutputs) {
// check that multiple different outputs are rejected by the parser
string err;
EXPECT_FALSE(Parse("foo bar: x y z", &err));
+ ASSERT_EQ("depfile has multiple output paths", err);
}
diff --git a/ninja/src/disk_interface.cc b/ninja/src/disk_interface.cc
index 3233144c893..4dfae1a3f9c 100644
--- a/ninja/src/disk_interface.cc
+++ b/ninja/src/disk_interface.cc
@@ -14,6 +14,8 @@
#include "disk_interface.h"
+#include <algorithm>
+
#include <errno.h>
#include <stdio.h>
#include <string.h>
@@ -31,15 +33,16 @@ namespace {
string DirName(const string& path) {
#ifdef _WIN32
- const char kPathSeparator = '\\';
+ const char kPathSeparators[] = "\\/";
#else
- const char kPathSeparator = '/';
+ const char kPathSeparators[] = "/";
#endif
-
- string::size_type slash_pos = path.rfind(kPathSeparator);
+ string::size_type slash_pos = path.find_last_of(kPathSeparators);
if (slash_pos == string::npos)
return string(); // Nothing to do.
- while (slash_pos > 0 && path[slash_pos - 1] == kPathSeparator)
+ const char* const kEnd = kPathSeparators + strlen(kPathSeparators);
+ while (slash_pos > 0 &&
+ std::find(kPathSeparators, kEnd, path[slash_pos - 1]) != kEnd)
--slash_pos;
return path.substr(0, slash_pos);
}
@@ -146,6 +149,9 @@ bool RealDiskInterface::WriteFile(const string& path, const string& contents) {
bool RealDiskInterface::MakeDir(const string& path) {
if (::MakeDir(path) < 0) {
+ if (errno == EEXIST) {
+ return true;
+ }
Error("mkdir(%s): %s", path.c_str(), strerror(errno));
return false;
}
diff --git a/ninja/src/disk_interface_test.cc b/ninja/src/disk_interface_test.cc
index 55822a68ea0..51a1d14d894 100644
--- a/ninja/src/disk_interface_test.cc
+++ b/ninja/src/disk_interface_test.cc
@@ -93,7 +93,18 @@ TEST_F(DiskInterfaceTest, ReadFile) {
}
TEST_F(DiskInterfaceTest, MakeDirs) {
- EXPECT_TRUE(disk_.MakeDirs("path/with/double//slash/"));
+ string path = "path/with/double//slash/";
+ EXPECT_TRUE(disk_.MakeDirs(path.c_str()));
+ FILE* f = fopen((path + "a_file").c_str(), "w");
+ EXPECT_TRUE(f);
+ EXPECT_EQ(0, fclose(f));
+#ifdef _WIN32
+ string path2 = "another\\with\\back\\\\slashes\\";
+ EXPECT_TRUE(disk_.MakeDirs(path2.c_str()));
+ FILE* f2 = fopen((path2 + "a_file").c_str(), "w");
+ EXPECT_TRUE(f2);
+ EXPECT_EQ(0, fclose(f2));
+#endif
}
TEST_F(DiskInterfaceTest, RemoveFile) {
diff --git a/ninja/src/graph.cc b/ninja/src/graph.cc
index 65f92442170..71213421449 100644
--- a/ninja/src/graph.cc
+++ b/ninja/src/graph.cc
@@ -305,6 +305,10 @@ bool Edge::is_phony() const {
return rule_ == &State::kPhonyRule;
}
+bool Edge::use_console() const {
+ return pool() == &State::kConsolePool;
+}
+
void Node::Dump(const char* prefix) const {
printf("%s <%s 0x%p> mtime: %d%s, (:%s), ",
prefix, path().c_str(), this,
diff --git a/ninja/src/graph.h b/ninja/src/graph.h
index 868413c3bce..6cd7f25452c 100644
--- a/ninja/src/graph.h
+++ b/ninja/src/graph.h
@@ -183,6 +183,7 @@ struct Edge {
}
bool is_phony() const;
+ bool use_console() const;
};
diff --git a/ninja/src/line_printer.cc b/ninja/src/line_printer.cc
index 3537e886e69..ef1609c28f5 100644
--- a/ninja/src/line_printer.cc
+++ b/ninja/src/line_printer.cc
@@ -26,7 +26,7 @@
#include "util.h"
-LinePrinter::LinePrinter() : have_blank_line_(true) {
+LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
#ifndef _WIN32
const char* term = getenv("TERM");
smart_terminal_ = isatty(1) && term && string(term) != "dumb";
@@ -43,6 +43,12 @@ LinePrinter::LinePrinter() : have_blank_line_(true) {
}
void LinePrinter::Print(string to_print, LineType type) {
+ if (console_locked_) {
+ line_buffer_ = to_print;
+ line_type_ = type;
+ return;
+ }
+
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(console_, &csbi);
@@ -101,13 +107,46 @@ void LinePrinter::Print(string to_print, LineType type) {
}
}
-void LinePrinter::PrintOnNewLine(const string& to_print) {
- if (!have_blank_line_)
- printf("\n");
- if (!to_print.empty()) {
+void LinePrinter::PrintOrBuffer(const char* data, size_t size) {
+ if (console_locked_) {
+ output_buffer_.append(data, size);
+ } else {
// Avoid printf and C strings, since the actual output might contain null
// bytes like UTF-16 does (yuck).
- fwrite(&to_print[0], sizeof(char), to_print.size(), stdout);
+ fwrite(data, 1, size, stdout);
+ }
+}
+
+void LinePrinter::PrintOnNewLine(const string& to_print) {
+ if (console_locked_ && !line_buffer_.empty()) {
+ output_buffer_.append(line_buffer_);
+ output_buffer_.append(1, '\n');
+ line_buffer_.clear();
+ }
+ if (!have_blank_line_) {
+ PrintOrBuffer("\n", 1);
+ }
+ if (!to_print.empty()) {
+ PrintOrBuffer(&to_print[0], to_print.size());
}
have_blank_line_ = to_print.empty() || *to_print.rbegin() == '\n';
}
+
+void LinePrinter::SetConsoleLocked(bool locked) {
+ if (locked == console_locked_)
+ return;
+
+ if (locked)
+ PrintOnNewLine("");
+
+ console_locked_ = locked;
+
+ if (!locked) {
+ PrintOnNewLine(output_buffer_);
+ if (!line_buffer_.empty()) {
+ Print(line_buffer_, line_type_);
+ }
+ output_buffer_.clear();
+ line_buffer_.clear();
+ }
+}
diff --git a/ninja/src/line_printer.h b/ninja/src/line_printer.h
index aea28172729..55225e52117 100644
--- a/ninja/src/line_printer.h
+++ b/ninja/src/line_printer.h
@@ -15,6 +15,7 @@
#ifndef NINJA_LINE_PRINTER_H_
#define NINJA_LINE_PRINTER_H_
+#include <stddef.h>
#include <string>
using namespace std;
@@ -37,6 +38,10 @@ struct LinePrinter {
/// Prints a string on a new line, not overprinting previous output.
void PrintOnNewLine(const string& to_print);
+ /// Lock or unlock the console. Any output sent to the LinePrinter while the
+ /// console is locked will not be printed until it is unlocked.
+ void SetConsoleLocked(bool locked);
+
private:
/// Whether we can do fancy terminal control codes.
bool smart_terminal_;
@@ -44,9 +49,24 @@ struct LinePrinter {
/// Whether the caret is at the beginning of a blank line.
bool have_blank_line_;
+ /// Whether console is locked.
+ bool console_locked_;
+
+ /// Buffered current line while console is locked.
+ string line_buffer_;
+
+ /// Buffered line type while console is locked.
+ LineType line_type_;
+
+ /// Buffered console output while console is locked.
+ string output_buffer_;
+
#ifdef _WIN32
void* console_;
#endif
+
+ /// Print the given data to the console, or buffer it if it is locked.
+ void PrintOrBuffer(const char *data, size_t size);
};
#endif // NINJA_LINE_PRINTER_H_
diff --git a/ninja/src/manifest_parser.cc b/ninja/src/manifest_parser.cc
index 20be7f3777f..6fa4f7c8b44 100644
--- a/ninja/src/manifest_parser.cc
+++ b/ninja/src/manifest_parser.cc
@@ -296,16 +296,17 @@ bool ManifestParser::ParseEdge(string* err) {
if (!ExpectToken(Lexer::NEWLINE, err))
return false;
- // XXX scoped_ptr to handle error case.
- BindingEnv* env = new BindingEnv(env_);
-
- while (lexer_.PeekToken(Lexer::INDENT)) {
+ // Bindings on edges are rare, so allocate per-edge envs only when needed.
+ bool hasIdent = lexer_.PeekToken(Lexer::INDENT);
+ BindingEnv* env = hasIdent ? new BindingEnv(env_) : env_;
+ while (hasIdent) {
string key;
EvalString val;
if (!ParseLet(&key, &val, err))
return false;
env->AddBinding(key, val.Evaluate(env_));
+ hasIdent = lexer_.PeekToken(Lexer::INDENT);
}
Edge* edge = state_->AddEdge(rule);
diff --git a/ninja/src/manifest_parser_perftest.cc b/ninja/src/manifest_parser_perftest.cc
new file mode 100644
index 00000000000..e40468fbdb0
--- /dev/null
+++ b/ninja/src/manifest_parser_perftest.cc
@@ -0,0 +1,114 @@
+// Copyright 2014 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.
+
+// Tests manifest parser performance. Expects to be run in ninja's root
+// directory.
+
+#include <numeric>
+#include <stdio.h>
+
+#ifdef _WIN32
+#include "getopt.h"
+#include <direct.h>
+#else
+#include <getopt.h>
+#include <unistd.h>
+#endif
+
+#include "disk_interface.h"
+#include "graph.h"
+#include "manifest_parser.h"
+#include "metrics.h"
+#include "state.h"
+#include "util.h"
+
+struct RealFileReader : public ManifestParser::FileReader {
+ virtual bool ReadFile(const string& path, string* content, string* err) {
+ return ::ReadFile(path, content, err) == 0;
+ }
+};
+
+bool WriteFakeManifests(const string& dir) {
+ RealDiskInterface disk_interface;
+ if (disk_interface.Stat(dir + "/build.ninja") > 0)
+ return true;
+
+ printf("Creating manifest data..."); fflush(stdout);
+ int err = system(("python misc/write_fake_manifests.py " + dir).c_str());
+ printf("done.\n");
+ return err == 0;
+}
+
+int LoadManifests(bool measure_command_evaluation) {
+ string err;
+ RealFileReader file_reader;
+ State state;
+ ManifestParser parser(&state, &file_reader);
+ if (!parser.Load("build.ninja", &err)) {
+ fprintf(stderr, "Failed to read test data: %s\n", err.c_str());
+ exit(1);
+ }
+ // Doing an empty build involves reading the manifest and evaluating all
+ // commands required for the requested targets. So include command
+ // evaluation in the perftest by default.
+ int optimization_guard = 0;
+ if (measure_command_evaluation)
+ for (size_t i = 0; i < state.edges_.size(); ++i)
+ optimization_guard += state.edges_[i]->EvaluateCommand().size();
+ return optimization_guard;
+}
+
+int main(int argc, char* argv[]) {
+ bool measure_command_evaluation = true;
+ int opt;
+ while ((opt = getopt(argc, argv, const_cast<char*>("fh"))) != -1) {
+ switch (opt) {
+ case 'f':
+ measure_command_evaluation = false;
+ break;
+ case 'h':
+ default:
+ printf("usage: manifest_parser_perftest\n"
+"\n"
+"options:\n"
+" -f only measure manifest load time, not command evaluation time\n"
+ );
+ return 1;
+ }
+ }
+
+ const char kManifestDir[] = "build/manifest_perftest";
+
+ if (!WriteFakeManifests(kManifestDir)) {
+ fprintf(stderr, "Failed to write test data\n");
+ return 1;
+ }
+
+ chdir(kManifestDir);
+
+ const int kNumRepetitions = 5;
+ vector<int> times;
+ for (int i = 0; i < kNumRepetitions; ++i) {
+ int64_t start = GetTimeMillis();
+ int optimization_guard = LoadManifests(measure_command_evaluation);
+ int delta = (int)(GetTimeMillis() - start);
+ printf("%dms (hash: %x)\n", delta, optimization_guard);
+ times.push_back(delta);
+ }
+
+ int min = *min_element(times.begin(), times.end());
+ int max = *max_element(times.begin(), times.end());
+ float total = accumulate(times.begin(), times.end(), 0.0f);
+ printf("min %dms max %dms avg %.1fms\n", min, max, total / times.size());
+}
diff --git a/ninja/src/msvc_helper-win32.cc b/ninja/src/msvc_helper-win32.cc
index d2e2eb536c4..e4652794a7b 100644
--- a/ninja/src/msvc_helper-win32.cc
+++ b/ninja/src/msvc_helper-win32.cc
@@ -141,7 +141,7 @@ int CLWrapper::Run(const string& command, string* output) {
STARTUPINFO startup_info = {};
startup_info.cb = sizeof(STARTUPINFO);
startup_info.hStdInput = nul;
- startup_info.hStdError = stdout_write;
+ startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
startup_info.hStdOutput = stdout_write;
startup_info.dwFlags |= STARTF_USESTDHANDLES;
diff --git a/ninja/src/msvc_helper_test.cc b/ninja/src/msvc_helper_test.cc
index 48fbe21dfee..391c04568c3 100644
--- a/ninja/src/msvc_helper_test.cc
+++ b/ninja/src/msvc_helper_test.cc
@@ -119,3 +119,10 @@ TEST(MSVCHelperTest, EnvBlock) {
cl.Run("cmd /c \"echo foo is %foo%", &output);
ASSERT_EQ("foo is bar\r\n", output);
}
+
+TEST(MSVCHelperTest, NoReadOfStderr) {
+ CLWrapper cl;
+ string output;
+ cl.Run("cmd /c \"echo to stdout&& echo to stderr 1>&2", &output);
+ ASSERT_EQ("to stdout\r\n", output);
+}
diff --git a/ninja/src/ninja.cc b/ninja/src/ninja.cc
index 03ca83b544c..50de43edbab 100644
--- a/ninja/src/ninja.cc
+++ b/ninja/src/ninja.cc
@@ -631,6 +631,8 @@ int NinjaMain::ToolCompilationDatabase(int argc, char* argv[]) {
putchar('[');
for (vector<Edge*>::iterator e = state_.edges_.begin();
e != state_.edges_.end(); ++e) {
+ if ((*e)->inputs_.empty())
+ continue;
for (int i = 0; i != argc; ++i) {
if ((*e)->rule_->name() == argv[i]) {
if (!first)
diff --git a/ninja/src/state.cc b/ninja/src/state.cc
index 33f8423312a..7258272056c 100644
--- a/ninja/src/state.cc
+++ b/ninja/src/state.cc
@@ -69,11 +69,13 @@ bool Pool::WeightedEdgeCmp(const Edge* a, const Edge* b) {
}
Pool State::kDefaultPool("", 0);
+Pool State::kConsolePool("console", 1);
const Rule State::kPhonyRule("phony");
State::State() {
AddRule(&kPhonyRule);
AddPool(&kDefaultPool);
+ AddPool(&kConsolePool);
}
void State::AddRule(const Rule* rule) {
diff --git a/ninja/src/state.h b/ninja/src/state.h
index bcb0eff75ff..c382dc014cc 100644
--- a/ninja/src/state.h
+++ b/ninja/src/state.h
@@ -82,6 +82,7 @@ struct Pool {
/// Global state (file status, loaded rules) for a single run.
struct State {
static Pool kDefaultPool;
+ static Pool kConsolePool;
static const Rule kPhonyRule;
State();
diff --git a/ninja/src/subprocess-posix.cc b/ninja/src/subprocess-posix.cc
index a9af756dca4..743e4066136 100644
--- a/ninja/src/subprocess-posix.cc
+++ b/ninja/src/subprocess-posix.cc
@@ -25,7 +25,8 @@
#include "util.h"
-Subprocess::Subprocess() : fd_(-1), pid_(-1) {
+Subprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1),
+ use_console_(use_console) {
}
Subprocess::~Subprocess() {
if (fd_ >= 0)
@@ -58,29 +59,34 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
// Track which fd we use to report errors on.
int error_pipe = output_pipe[1];
do {
- if (setpgid(0, 0) < 0)
- break;
-
if (sigaction(SIGINT, &set->old_act_, 0) < 0)
break;
if (sigprocmask(SIG_SETMASK, &set->old_mask_, 0) < 0)
break;
- // Open /dev/null over stdin.
- int devnull = open("/dev/null", O_RDONLY);
- if (devnull < 0)
- break;
- if (dup2(devnull, 0) < 0)
- break;
- close(devnull);
-
- if (dup2(output_pipe[1], 1) < 0 ||
- dup2(output_pipe[1], 2) < 0)
- break;
-
- // Now can use stderr for errors.
- error_pipe = 2;
- close(output_pipe[1]);
+ if (!use_console_) {
+ // Put the child in its own process group, so ctrl-c won't reach it.
+ if (setpgid(0, 0) < 0)
+ break;
+
+ // Open /dev/null over stdin.
+ int devnull = open("/dev/null", O_RDONLY);
+ if (devnull < 0)
+ break;
+ if (dup2(devnull, 0) < 0)
+ break;
+ close(devnull);
+
+ if (dup2(output_pipe[1], 1) < 0 ||
+ dup2(output_pipe[1], 2) < 0)
+ break;
+
+ // Now can use stderr for errors.
+ error_pipe = 2;
+ close(output_pipe[1]);
+ }
+ // In the console case, output_pipe is still inherited by the child and
+ // closed when the subprocess finishes, which then notifies ninja.
execl("/bin/sh", "/bin/sh", "-c", command.c_str(), (char *) NULL);
} while (false);
@@ -168,8 +174,8 @@ SubprocessSet::~SubprocessSet() {
Fatal("sigprocmask: %s", strerror(errno));
}
-Subprocess *SubprocessSet::Add(const string& command) {
- Subprocess *subprocess = new Subprocess;
+Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
+ Subprocess *subprocess = new Subprocess(use_console);
if (!subprocess->Start(this, command)) {
delete subprocess;
return 0;
@@ -279,7 +285,10 @@ Subprocess* SubprocessSet::NextFinished() {
void SubprocessSet::Clear() {
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ++i)
- kill(-(*i)->pid_, SIGINT);
+ // Since the foreground process is in our process group, it will receive a
+ // SIGINT at the same time as us.
+ if (!(*i)->use_console_)
+ kill(-(*i)->pid_, SIGINT);
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ++i)
delete *i;
diff --git a/ninja/src/subprocess-win32.cc b/ninja/src/subprocess-win32.cc
index 1b230b640b1..fad66e895c0 100644
--- a/ninja/src/subprocess-win32.cc
+++ b/ninja/src/subprocess-win32.cc
@@ -14,13 +14,16 @@
#include "subprocess.h"
+#include <assert.h>
#include <stdio.h>
#include <algorithm>
#include "util.h"
-Subprocess::Subprocess() : child_(NULL) , overlapped_(), is_reading_(false) {
+Subprocess::Subprocess(bool use_console) : child_(NULL) , overlapped_(),
+ is_reading_(false),
+ use_console_(use_console) {
}
Subprocess::~Subprocess() {
@@ -86,18 +89,25 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
STARTUPINFOA startup_info;
memset(&startup_info, 0, sizeof(startup_info));
startup_info.cb = sizeof(STARTUPINFO);
- startup_info.dwFlags = STARTF_USESTDHANDLES;
- startup_info.hStdInput = nul;
- startup_info.hStdOutput = child_pipe;
- startup_info.hStdError = child_pipe;
+ if (!use_console_) {
+ startup_info.dwFlags = STARTF_USESTDHANDLES;
+ startup_info.hStdInput = nul;
+ startup_info.hStdOutput = child_pipe;
+ startup_info.hStdError = child_pipe;
+ }
+ // In the console case, child_pipe is still inherited by the child and closed
+ // when the subprocess finishes, which then notifies ninja.
PROCESS_INFORMATION process_info;
memset(&process_info, 0, sizeof(process_info));
+ // Ninja handles ctrl-c, except for subprocesses in console pools.
+ DWORD process_flags = use_console_ ? 0 : CREATE_NEW_PROCESS_GROUP;
+
// Do not prepend 'cmd /c' on Windows, this breaks command
// lines greater than 8,191 chars.
if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL,
- /* inherit handles */ TRUE, CREATE_NEW_PROCESS_GROUP,
+ /* inherit handles */ TRUE, process_flags,
NULL, NULL,
&startup_info, &process_info)) {
DWORD error = GetLastError();
@@ -213,8 +223,8 @@ BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) {
return FALSE;
}
-Subprocess *SubprocessSet::Add(const string& command) {
- Subprocess *subprocess = new Subprocess;
+Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
+ Subprocess *subprocess = new Subprocess(use_console);
if (!subprocess->Start(this, command)) {
delete subprocess;
return 0;
@@ -266,7 +276,9 @@ Subprocess* SubprocessSet::NextFinished() {
void SubprocessSet::Clear() {
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ++i) {
- if ((*i)->child_) {
+ // Since the foreground process is in our process group, it will receive a
+ // CTRL_C_EVENT or CTRL_BREAK_EVENT at the same time as us.
+ if ((*i)->child_ && !(*i)->use_console_) {
if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
GetProcessId((*i)->child_))) {
Win32Fatal("GenerateConsoleCtrlEvent");
diff --git a/ninja/src/subprocess.h b/ninja/src/subprocess.h
index 4c1629c89b3..b7a1a4c048e 100644
--- a/ninja/src/subprocess.h
+++ b/ninja/src/subprocess.h
@@ -44,7 +44,7 @@ struct Subprocess {
const string& GetOutput() const;
private:
- Subprocess();
+ Subprocess(bool use_console);
bool Start(struct SubprocessSet* set, const string& command);
void OnPipeReady();
@@ -64,6 +64,7 @@ struct Subprocess {
int fd_;
pid_t pid_;
#endif
+ bool use_console_;
friend struct SubprocessSet;
};
@@ -75,7 +76,7 @@ struct SubprocessSet {
SubprocessSet();
~SubprocessSet();
- Subprocess* Add(const string& command);
+ Subprocess* Add(const string& command, bool use_console = false);
bool DoWork();
Subprocess* NextFinished();
void Clear();
diff --git a/ninja/src/subprocess_test.cc b/ninja/src/subprocess_test.cc
index 9f8dcea68cb..775a13a6c93 100644
--- a/ninja/src/subprocess_test.cc
+++ b/ninja/src/subprocess_test.cc
@@ -95,6 +95,21 @@ TEST_F(SubprocessTest, InterruptParent) {
ADD_FAILURE() << "We should have been interrupted";
}
+TEST_F(SubprocessTest, Console) {
+ // Skip test if we don't have the console ourselves.
+ if (isatty(0) && isatty(1) && isatty(2)) {
+ Subprocess* subproc = subprocs_.Add("test -t 0 -a -t 1 -a -t 2",
+ /*use_console=*/true);
+ ASSERT_NE((Subprocess *) 0, subproc);
+
+ while (!subproc->Done()) {
+ subprocs_.DoWork();
+ }
+
+ EXPECT_EQ(ExitSuccess, subproc->Finish());
+ }
+}
+
#endif
TEST_F(SubprocessTest, SetWithSingle) {