Merge pull request #1196 from danw/ReadFile_opt
Optimize ReadFile allocations
diff --git a/.gitignore b/.gitignore
index 5a85203..11150c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
/ninja.bootstrap
/build_log_perftest
/canon_perftest
+/clparser_perftest
/depfile_parser_perftest
/hash_collision_bench
/ninja_test
@@ -31,3 +32,6 @@
# Ninja output
.ninja_deps
.ninja_log
+
+# Visual Studio Code project files
+/.vscode/
diff --git a/.travis.yml b/.travis.yml
index 216b8b0..70b1fd0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,4 +3,9 @@
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
+ - ./misc/output_test.py
diff --git a/HACKING.md b/HACKING.md
index c9c6601..5c2469b 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -13,14 +13,50 @@
Ninja is built using itself. To bootstrap the first binary, run the
configure script as `./configure.py --bootstrap`. This first compiles
all non-test source files together, then re-builds Ninja using itself.
-You should end up with a `ninja` binary (or `ninja.exe`) in the source root.
+You should end up with a `ninja` binary (or `ninja.exe`) in the project root.
#### Windows
On Windows, you'll need to install Python to run `configure.py`, and
run everything under a Visual Studio Tools Command Prompt (or after
-running `vcvarsall` in a normal command prompt). See below if you
-want to use mingw or some other compiler instead of Visual Studio.
+running `vcvarsall` in a normal command prompt).
+
+For other combinations such as gcc/clang you will need the compiler
+(gcc/cl) in your PATH and you will have to set the appropriate
+platform configuration script.
+
+See below if you want to use mingw or some other compiler instead of
+Visual Studio.
+
+##### Using Visual Studio
+Assuming that you now have python installed, then the steps for building under
+ Windows using Visual Studio are:
+
+Clone and checkout the latest release (or whatever branch you want). You
+can do this in either a command prompt or by opening a git bash prompt:
+
+```
+ $ git clone git://github.com/ninja-build/ninja.git && cd ninja
+ $ git checkout release
+```
+
+Then:
+
+1. Open a Windows command prompt in the folder where you checked out ninja.
+2. Select the Microsoft build environment by running
+`vcvarsall.bat` with the appropriate environment.
+3. Build ninja and test it.
+
+The steps for a Visual Studio 2015 64-bit build are outlined here:
+
+```
+ > "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
+ > python configure.py --bootstrap
+ > ninja --help
+```
+Copy the ninja executable to another location, if desired, e.g. C:\local\Ninja.
+
+Finally add the path where ninja.exe is to the PATH variable.
### Adjusting build flags
@@ -95,7 +131,7 @@
Generally it's the [Google C++ coding style][], but in brief:
* Function name are camelcase.
-* Member methods are camelcase, expect for trivial getters which are
+* Member methods are camelcase, except for trivial getters which are
underscore separated.
* Local variables are underscore separated.
* Member variables are underscore separated and suffixed by an extra
@@ -105,7 +141,7 @@
* 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..da4dbdd 100644
--- a/RELEASING
+++ b/RELEASING
@@ -1,18 +1,20 @@
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
+1. Run afl-fuzz for a day or so (see HACKING.md) and run ninja_test
+2. Consider sending a heads-up to the ninja-build mailing list first
+3. Make sure branches 'master' and 'release' are synced up locally
+4. Update src/version.cc with new version (with ".git"), then
+ git commit -am 'mark this 1.5.0.git'
+5. git checkout release; git merge master
+6. Fix version number in src/version.cc (it will likely conflict in the above)
+7. Fix version in doc/manual.asciidoc (exists only on release branch)
+8. 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
+9. Construct release notes from prior notes
credits: git shortlog -s --no-merges REV..
Release on github:
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..4c64f29
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,40 @@
+version: 1.0.{build}
+image: Visual Studio 2017
+
+environment:
+ CLICOLOR_FORCE: 1
+ CHERE_INVOKING: 1 # Tell Bash to inherit the current working directory
+ matrix:
+ - MSYSTEM: MINGW64
+ - MSYSTEM: MSVC
+
+for:
+ -
+ matrix:
+ only:
+ - MSYSTEM: MINGW64
+ build_script:
+ ps: "C:\\msys64\\usr\\bin\\bash -lc @\"\n
+ pacman -S --quiet --noconfirm --needed re2c 2>&1\n
+ sed -i 's|cmd /c $ar cqs $out.tmp $in && move /Y $out.tmp $out|$ar crs $out $in|g' configure.py\n
+ ./configure.py --bootstrap --platform mingw 2>&1\n
+ ./ninja all\n
+ ./ninja_test 2>&1\n
+ ./misc/ninja_syntax_test.py 2>&1\n\"@"
+ -
+ matrix:
+ only:
+ - MSYSTEM: MSVC
+ build_script:
+ - cmd: >-
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
+
+ python configure.py --bootstrap
+
+ ninja.bootstrap.exe all
+
+ ninja_test
+
+ python misc/ninja_syntax_test.py
+
+test: off
diff --git a/configure.py b/configure.py
index 9ec368f..78cd1de 100755
--- a/configure.py
+++ b/configure.py
@@ -60,11 +60,14 @@
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 @@
return self._platform == 'aix'
def uses_usr_local(self):
- return self._platform in ('freebsd', 'openbsd', 'bitrig')
+ return self._platform in ('freebsd', 'openbsd', 'bitrig', 'dragonfly', 'netbsd')
def supports_ppoll(self):
- return self._platform in ('freebsd', 'linux', 'openbsd', 'bitrig')
+ return self._platform in ('freebsd', 'linux', 'openbsd', 'bitrig',
+ 'dragonfly')
def supports_ninja_browse(self):
return (not self.is_windows()
@@ -252,7 +256,7 @@
if '--bootstrap' in configure_args:
configure_args.remove('--bootstrap')
n.variable('configure_args', ' '.join(configure_args))
-env_keys = set(['CXX', 'AR', 'CFLAGS', 'LDFLAGS'])
+env_keys = set(['CXX', 'AR', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS'])
configure_env = dict((k, os.environ[k]) for k in os.environ if k in env_keys)
if configure_env:
config_str = ' '.join([k + '=' + pipes.quote(configure_env[k])
@@ -302,7 +306,7 @@
'/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',
@@ -352,6 +356,11 @@
if platform.uses_usr_local():
cflags.append('-I/usr/local/include')
ldflags.append('-L/usr/local/lib')
+ if platform.is_aix():
+ # printf formats for int64_t, uint64_t; large file support
+ cflags.append('-D__STDC_FORMAT_MACROS')
+ cflags.append('-D_LARGE_FILES')
+
libs = []
@@ -393,6 +402,10 @@
if 'CFLAGS' in configure_env:
cflags.append(configure_env['CFLAGS'])
+ ldflags.append(configure_env['CFLAGS'])
+if 'CXXFLAGS' in configure_env:
+ cflags.append(configure_env['CXXFLAGS'])
+ ldflags.append(configure_env['CXXFLAGS'])
n.variable('cflags', ' '.join(shell_escape(flag) for flag in cflags))
if 'LDFLAGS' in configure_env:
ldflags.append(configure_env['LDFLAGS'])
@@ -401,7 +414,7 @@
if platform.is_msvc():
n.rule('cxx',
- command='$cxx $cflags -c $in /Fo$out',
+ command='$cxx $cflags -c $in /Fo$out /Fd' + built('$pdb'),
description='CXX $out',
deps='msvc' # /showIncludes is included in $cflags.
)
@@ -472,6 +485,9 @@
n.newline()
n.comment('Core source files all build into ninja library.')
+cxxvariables = []
+if platform.is_msvc():
+ cxxvariables = [('pdb', 'ninja.pdb')]
for name in ['build',
'build_log',
'clean',
@@ -489,17 +505,18 @@
'manifest_parser',
'metrics',
'state',
+ 'string_piece_util',
'util',
'version']:
- objs += cxx(name)
+ objs += cxx(name, variables=cxxvariables)
if platform.is_windows():
for name in ['subprocess-win32',
'includes_normalize-win32',
'msvc_helper-win32',
'msvc_helper_main-win32']:
- objs += cxx(name)
+ objs += cxx(name, variables=cxxvariables)
if platform.is_msvc():
- objs += cxx('minidump-win32')
+ objs += cxx('minidump-win32', variables=cxxvariables)
objs += cc('getopt')
else:
objs += cxx('subprocess-posix')
@@ -522,7 +539,7 @@
all_targets = []
n.comment('Main executable is library plus main() function.')
-objs = cxx('ninja')
+objs = cxx('ninja', variables=cxxvariables)
ninja = n.build(binary('ninja'), 'link', objs, implicit=ninja_lib,
variables=[('libs', libs)])
n.newline()
@@ -537,6 +554,8 @@
n.comment('Tests all build into ninja_test executable.')
objs = []
+if platform.is_msvc():
+ cxxvariables = [('pdb', 'ninja_test.pdb')]
for name in ['build_log_test',
'build_test',
@@ -551,13 +570,14 @@
'manifest_parser_test',
'ninja_test',
'state_test',
+ 'string_piece_util_test',
'subprocess_test',
'test',
'util_test']:
- objs += cxx(name)
+ objs += cxx(name, variables=cxxvariables)
if platform.is_windows():
for name in ['includes_normalize_test', 'msvc_helper_test']:
- objs += cxx(name)
+ objs += cxx(name, variables=cxxvariables)
ninja_test = n.build(binary('ninja_test'), 'link', objs, implicit=ninja_lib,
variables=[('libs', libs)])
@@ -566,21 +586,19 @@
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']:
+ if platform.is_msvc():
+ cxxvariables = [('pdb', name + '.pdb')]
+ objs = cxx(name, variables=cxxvariables)
+ 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/doc/manual.asciidoc b/doc/manual.asciidoc
index d7ec932..86d9aaa 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -279,6 +279,11 @@
by the Clang tooling interface.
_Available since Ninja 1.2._
+`deps`:: show all dependencies stored in the `.ninja_deps` file. When given a
+target, show just the target's dependencies. _Available since Ninja 1.4._
+
+`recompact`:: recompact the `.ninja_deps` file. _Available since Ninja 1.4._
+
Writing your own Ninja files
----------------------------
@@ -588,7 +593,7 @@
to its stdout. Ninja then filters these lines from the displayed
output. No `depfile` attribute is necessary, but the localized string
in front of the the header file path. For instance
- `msvc_deps_prefix = Note: including file: `
+ `msvc_deps_prefix = Note: including file:`
for a English Visual Studio (the default). Should be globally defined.
+
----
diff --git a/misc/ninja-mode.el b/misc/ninja-mode.el
index 639e537..8b975d5 100644
--- a/misc/ninja-mode.el
+++ b/misc/ninja-mode.el
@@ -56,7 +56,7 @@
(save-excursion
(goto-char (line-end-position 0))
(or
- ;; If we're continuting the previous line, it's not a
+ ;; If we're continuing the previous line, it's not a
;; comment.
(not (eq ?$ (char-before)))
;; Except if the previous line is a comment as well, as the
diff --git a/misc/ninja.vim b/misc/ninja.vim
index 190d9ce..c1ffd50 100644
--- a/misc/ninja.vim
+++ b/misc/ninja.vim
@@ -1,8 +1,8 @@
" ninja build file syntax.
" Language: ninja build file as described at
" http://ninja-build.org/manual.html
-" Version: 1.4
-" Last Change: 2014/05/13
+" Version: 1.5
+" Last Change: 2018/04/05
" Maintainer: Nicolas Weber <nicolasweber@gmx.de>
" Version 1.4 of this script is in the upstream vim repository and will be
" included in the next vim release. If you change this, please send your change
@@ -21,7 +21,10 @@
syn case match
-syn match ninjaComment /#.*/ contains=@Spell
+" Comments are only matched when the # is at the beginning of the line (with
+" optional whitespace), as long as the prior line didn't end with a $
+" continuation.
+syn match ninjaComment /\(\$\n\)\@<!\_^\s*#.*$/ contains=@Spell
" Toplevel statements are the ones listed here and
" toplevel variable assignments (ident '=' value).
@@ -38,12 +41,13 @@
" limited set of magic variables, 'build' allows general
" let assignments.
" manifest_parser.cc, ParseRule()
-syn region ninjaRule start="^rule" end="^\ze\S" contains=ALL transparent
-syn keyword ninjaRuleCommand contained command deps depfile description generator
+syn region ninjaRule start="^rule" end="^\ze\S" contains=TOP transparent
+syn keyword ninjaRuleCommand contained containedin=ninjaRule command
+ \ deps depfile description generator
\ pool restat rspfile rspfile_content
-syn region ninjaPool start="^pool" end="^\ze\S" contains=ALL transparent
-syn keyword ninjaPoolCommand contained depth
+syn region ninjaPool start="^pool" end="^\ze\S" contains=TOP transparent
+syn keyword ninjaPoolCommand contained containedin=ninjaPool depth
" Strings are parsed as follows:
" lexer.in.cc, ReadEvalString()
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index 5c52ea2..051bac1 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -60,7 +60,7 @@
self.variable('deps', deps, indent=1)
def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
- variables=None, implicit_outputs=None):
+ variables=None, implicit_outputs=None, pool=None):
outputs = as_list(outputs)
out_outputs = [escape_path(x) for x in outputs]
all_inputs = [escape_path(x) for x in as_list(inputs)]
@@ -81,6 +81,8 @@
self._line('build %s: %s' % (' '.join(out_outputs),
' '.join([rule] + all_inputs)))
+ if pool is not None:
+ self._line(' pool = %s' % pool)
if variables:
if isinstance(variables, dict):
diff --git a/misc/ninja_syntax_test.py b/misc/ninja_syntax_test.py
index 07e3ed3..90ff9c6 100755
--- a/misc/ninja_syntax_test.py
+++ b/misc/ninja_syntax_test.py
@@ -46,13 +46,13 @@
self.out.getvalue())
def test_comment_wrap(self):
- # Filenames shoud not be wrapped
+ # Filenames should not be wrapped
self.n.comment('Hello /usr/local/build-tools/bin')
self.assertEqual('# Hello\n# /usr/local/build-tools/bin\n',
self.out.getvalue())
def test_short_words_indented(self):
- # Test that indent is taking into acount when breaking subsequent lines.
+ # Test that indent is taking into account when breaking subsequent lines.
# The second line should not be ' to tree', as that's longer than the
# test layout width of 8.
self.n._line('line_one to tree')
diff --git a/misc/output_test.py b/misc/output_test.py
new file mode 100755
index 0000000..878de19
--- /dev/null
+++ b/misc/output_test.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+
+"""Runs ./ninja and checks if the output is correct.
+
+In order to simulate a smart terminal it uses the 'script' command.
+"""
+
+import os
+import subprocess
+import sys
+import tempfile
+import unittest
+
+default_env = dict(os.environ)
+if 'NINJA_STATUS' in default_env:
+ del default_env['NINJA_STATUS']
+if 'CLICOLOR_FORCE' in default_env:
+ del default_env['CLICOLOR_FORCE']
+default_env['TERM'] = ''
+
+def run(build_ninja, flags='', pipe=False, env=default_env):
+ with tempfile.NamedTemporaryFile('w') as f:
+ f.write(build_ninja)
+ f.flush()
+ ninja_cmd = './ninja {} -f {}'.format(flags, f.name)
+ try:
+ if pipe:
+ output = subprocess.check_output([ninja_cmd], shell=True, env=env)
+ else:
+ output = subprocess.check_output(['script', '-qfec', ninja_cmd, '/dev/null'],
+ env=env)
+ except subprocess.CalledProcessError as err:
+ sys.stdout.buffer.write(err.output)
+ raise err
+ final_output = ''
+ for line in output.decode('utf-8').splitlines(True):
+ if len(line) > 0 and line[-1] == '\r':
+ continue
+ final_output += line.replace('\r', '')
+ return final_output
+
+class Output(unittest.TestCase):
+ def test_issue_1418(self):
+ self.assertEqual(run(
+'''rule echo
+ command = sleep 0.$delay && echo $out
+ description = echo $out
+
+build a: echo
+ delay = 3
+build b: echo
+ delay = 2
+build c: echo
+ delay = 1
+'''),
+'''[1/3] echo c\x1b[K
+c
+[2/3] echo b\x1b[K
+b
+[3/3] echo a\x1b[K
+a
+''')
+
+ def test_issue_1214(self):
+ print_red = '''rule echo
+ command = printf '\x1b[31mred\x1b[0m'
+ description = echo $out
+
+build a: echo
+'''
+ # Only strip color when ninja's output is piped.
+ self.assertEqual(run(print_red),
+'''[1/1] echo a\x1b[K
+\x1b[31mred\x1b[0m
+''')
+ self.assertEqual(run(print_red, pipe=True),
+'''[1/1] echo a
+red
+''')
+ # Even in verbose mode, colors should still only be stripped when piped.
+ self.assertEqual(run(print_red, flags='-v'),
+'''[1/1] printf '\x1b[31mred\x1b[0m'
+\x1b[31mred\x1b[0m
+''')
+ self.assertEqual(run(print_red, flags='-v', pipe=True),
+'''[1/1] printf '\x1b[31mred\x1b[0m'
+red
+''')
+
+ # CLICOLOR_FORCE=1 can be used to disable escape code stripping.
+ env = default_env.copy()
+ env['CLICOLOR_FORCE'] = '1'
+ self.assertEqual(run(print_red, pipe=True, env=env),
+'''[1/1] echo a
+\x1b[31mred\x1b[0m
+''')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/misc/zsh-completion b/misc/zsh-completion
index 446e269..4cee3b8 100644
--- a/misc/zsh-completion
+++ b/misc/zsh-completion
@@ -14,7 +14,7 @@
# limitations under the License.
# Add the following to your .zshrc to tab-complete ninja targets
-# . path/to/ninja/misc/zsh-completion
+# fpath=(path/to/ninja/misc/zsh-completion $fpath)
__get_targets() {
dir="."
@@ -22,7 +22,12 @@
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.cc b/src/browse.cc
index 14900f8..c08c9f4 100644
--- a/src/browse.cc
+++ b/src/browse.cc
@@ -14,6 +14,7 @@
#include "browse.h"
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -57,7 +58,11 @@
}
command.push_back(NULL);
execvp(command[0], (char**)&command[0]);
- perror("ninja: execvp");
+ if (errno == ENOENT) {
+ printf("ninja: %s is required for the browse tool\n", NINJA_PYTHON);
+ } else {
+ perror("ninja: execvp");
+ }
} while (false);
_exit(1);
} else { // Child.
diff --git a/src/browse.py b/src/browse.py
index 4b4faa8..64a16f2 100755
--- a/src/browse.py
+++ b/src/browse.py
@@ -193,6 +193,8 @@
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.')
@@ -205,9 +207,11 @@
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 64710dd..ed219fd 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -20,6 +20,11 @@
#include <stdlib.h>
#include <functional>
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#endif
+
#if defined(__SVR4) && defined(__sun)
#include <sys/termios.h>
#endif
@@ -149,13 +154,22 @@
// (Launching subprocesses in pseudo ttys doesn't work because there are
// only a few hundred available on some systems, and ninja can launch
// thousands of parallel compile commands.)
- // TODO: There should be a flag to disable escape code stripping.
string final_output;
- if (!printer_.is_smart_terminal())
+ if (!printer_.supports_color())
final_output = StripAnsiEscapeCodes(output);
else
final_output = output;
+
+#ifdef _WIN32
+ // Fix extra CR being added on Windows, writing out CR CR LF (#773)
+ _setmode(_fileno(stdout), _O_BINARY); // Begin Windows extra CR fix
+#endif
+
printer_.PrintOnNewLine(final_output);
+
+#ifdef _WIN32
+ _setmode(_fileno(stdout), _O_TEXT); // End Windows extra CR fix
+#endif
}
}
@@ -275,43 +289,46 @@
Plan::Plan() : command_edges_(0), wanted_edges_(0) {}
-bool Plan::AddTarget(Node* node, string* err) {
- vector<Node*> stack;
- return AddSubTarget(node, &stack, err);
+void Plan::Reset() {
+ command_edges_ = 0;
+ wanted_edges_ = 0;
+ ready_.clear();
+ want_.clear();
}
-bool Plan::AddSubTarget(Node* node, vector<Node*>* stack, string* err) {
+bool Plan::AddTarget(Node* node, string* err) {
+ return AddSubTarget(node, NULL, err);
+}
+
+bool Plan::AddSubTarget(Node* node, Node* dependent, string* err) {
Edge* edge = node->in_edge();
if (!edge) { // Leaf node.
if (node->dirty()) {
string referenced;
- if (!stack->empty())
- referenced = ", needed by '" + stack->back()->path() + "',";
+ if (dependent)
+ referenced = ", needed by '" + dependent->path() + "',";
*err = "'" + node->path() + "'" + referenced + " missing "
"and no known rule to make it";
}
return false;
}
- if (CheckDependencyCycle(node, *stack, err))
- return false;
-
if (edge->outputs_ready())
return false; // Don't need to do anything.
// If an entry in want_ does not already exist for edge, create an entry which
- // maps to false, indicating that we do not want to build this entry itself.
- pair<map<Edge*, bool>::iterator, bool> want_ins =
- want_.insert(make_pair(edge, false));
- bool& want = want_ins.first->second;
+ // maps to kWantNothing, indicating that we do not want to build this entry itself.
+ pair<map<Edge*, Want>::iterator, bool> want_ins =
+ want_.insert(make_pair(edge, kWantNothing));
+ Want& want = want_ins.first->second;
// If we do need to build edge and we haven't already marked it as wanted,
// mark it now.
- if (node->dirty() && !want) {
- want = true;
+ if (node->dirty() && want == kWantNothing) {
+ want = kWantToStart;
++wanted_edges_;
if (edge->AllInputsReady())
- ScheduleWork(edge);
+ ScheduleWork(want_ins.first);
if (!edge->is_phony())
++command_edges_;
}
@@ -319,50 +336,15 @@
if (!want_ins.second)
return true; // We've already processed the inputs.
- stack->push_back(node);
for (vector<Node*>::iterator i = edge->inputs_.begin();
i != edge->inputs_.end(); ++i) {
- if (!AddSubTarget(*i, stack, err) && !err->empty())
+ if (!AddSubTarget(*i, node, err) && !err->empty())
return false;
}
- assert(stack->back() == node);
- stack->pop_back();
return true;
}
-bool Plan::CheckDependencyCycle(Node* node, const vector<Node*>& stack,
- string* err) {
- vector<Node*>::const_iterator start = stack.begin();
- while (start != stack.end() && (*start)->in_edge() != node->in_edge())
- ++start;
- if (start == stack.end())
- return false;
-
- // Build error string for the cycle.
- vector<Node*> cycle(start, stack.end());
- cycle.push_back(node);
-
- if (cycle.front() != cycle.back()) {
- // Consider
- // build a b: cat c
- // build c: cat a
- // stack will contain [b, c], node will be a. To not print b -> c -> a,
- // shift by one to get c -> a -> c which makes the cycle clear.
- cycle.erase(cycle.begin());
- cycle.push_back(cycle.front());
- assert(cycle.front() == cycle.back());
- }
-
- *err = "dependency cycle: ";
- for (vector<Node*>::const_iterator i = cycle.begin(); i != cycle.end(); ++i) {
- if (i != cycle.begin())
- err->append(" -> ");
- err->append((*i)->path());
- }
- return true;
-}
-
Edge* Plan::FindWork() {
if (ready_.empty())
return NULL;
@@ -372,30 +354,32 @@
return edge;
}
-void Plan::ScheduleWork(Edge* edge) {
- set<Edge*>::iterator e = ready_.lower_bound(edge);
- if (e != ready_.end() && !ready_.key_comp()(edge, *e)) {
+void Plan::ScheduleWork(map<Edge*, Want>::iterator want_e) {
+ if (want_e->second == kWantToFinish) {
// This edge has already been scheduled. We can get here again if an edge
// and one of its dependencies share an order-only input, or if a node
// duplicates an out edge (see https://github.com/ninja-build/ninja/pull/519).
// Avoid scheduling the work again.
return;
}
+ assert(want_e->second == kWantToStart);
+ want_e->second = kWantToFinish;
+ Edge* edge = want_e->first;
Pool* pool = edge->pool();
if (pool->ShouldDelayEdge()) {
pool->DelayEdge(edge);
pool->RetrieveReadyEdges(&ready_);
} else {
pool->EdgeScheduled(*edge);
- ready_.insert(e, edge);
+ ready_.insert(edge);
}
}
void Plan::EdgeFinished(Edge* edge, EdgeResult result) {
- map<Edge*, bool>::iterator e = want_.find(edge);
+ map<Edge*, Want>::iterator e = want_.find(edge);
assert(e != want_.end());
- bool directly_wanted = e->second;
+ bool directly_wanted = e->second != kWantNothing;
// See if this job frees up any delayed jobs.
if (directly_wanted)
@@ -422,14 +406,14 @@
// See if we we want any edges from this node.
for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
oe != node->out_edges().end(); ++oe) {
- map<Edge*, bool>::iterator want_e = want_.find(*oe);
+ map<Edge*, Want>::iterator want_e = want_.find(*oe);
if (want_e == want_.end())
continue;
// See if the edge is now ready.
if ((*oe)->AllInputsReady()) {
- if (want_e->second) {
- ScheduleWork(*oe);
+ if (want_e->second != kWantNothing) {
+ ScheduleWork(want_e);
} else {
// We do not need to build this edge, but we might need to build one of
// its dependents.
@@ -445,8 +429,8 @@
for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
oe != node->out_edges().end(); ++oe) {
// Don't process edges that we don't actually want.
- map<Edge*, bool>::iterator want_e = want_.find(*oe);
- if (want_e == want_.end() || !want_e->second)
+ map<Edge*, Want>::iterator want_e = want_.find(*oe);
+ if (want_e == want_.end() || want_e->second == kWantNothing)
continue;
// Don't attempt to clean an edge if it failed to load deps.
@@ -458,7 +442,12 @@
vector<Node*>::iterator
begin = (*oe)->inputs_.begin(),
end = (*oe)->inputs_.end() - (*oe)->order_only_deps_;
- if (find_if(begin, end, mem_fun(&Node::dirty)) == end) {
+#if __cplusplus < 201703L
+#define MEM_FN mem_fun
+#else
+#define MEM_FN mem_fn // mem_fun was removed in C++17.
+#endif
+ if (find_if(begin, end, MEM_FN(&Node::dirty)) == end) {
// Recompute most_recent_input.
Node* most_recent_input = NULL;
for (vector<Node*>::iterator i = begin; i != end; ++i) {
@@ -481,7 +470,7 @@
return false;
}
- want_e->second = false;
+ want_e->second = kWantNothing;
--wanted_edges_;
if (!(*oe)->is_phony())
--command_edges_;
@@ -493,8 +482,8 @@
void Plan::Dump() {
printf("pending: %d\n", (int)want_.size());
- for (map<Edge*, bool>::iterator e = want_.begin(); e != want_.end(); ++e) {
- if (e->second)
+ for (map<Edge*, Want>::iterator e = want_.begin(); e != want_.end(); ++e) {
+ if (e->second != kWantNothing)
printf("want ");
e->first->Dump();
}
@@ -618,9 +607,10 @@
}
bool Builder::AddTarget(Node* node, string* err) {
+ if (!scan_.RecomputeDirty(node, err))
+ return false;
+
if (Edge* in_edge = node->in_edge()) {
- if (!scan_.RecomputeDirty(in_edge, err))
- return false;
if (in_edge->outputs_ready())
return true; // Nothing to do.
}
@@ -793,9 +783,10 @@
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();
@@ -803,7 +794,9 @@
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).
@@ -814,6 +807,7 @@
}
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();
@@ -837,6 +831,8 @@
// 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;
}
}
@@ -849,7 +845,7 @@
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;
}
@@ -918,7 +914,7 @@
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 66ce607..9b90e8a 100644
--- a/src/build.h
+++ b/src/build.h
@@ -71,23 +71,36 @@
/// 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,
- string* err);
+ bool AddSubTarget(Node* node, Node* dependent, string* err);
void NodeFinished(Node* node);
+ /// Enumerate possible steps we want for an edge.
+ enum Want
+ {
+ /// We do not want to build the edge, but we might want to build one of
+ /// its dependents.
+ kWantNothing,
+ /// We want to build the edge, but have not yet scheduled it.
+ kWantToStart,
+ /// We want to build the edge, have scheduled it, and are waiting
+ /// for it to complete.
+ kWantToFinish
+ };
+
/// Submits a ready edge as a candidate for execution.
/// The edge may be delayed from running, for example if it's a member of a
/// currently-full pool.
- void ScheduleWork(Edge* edge);
+ void ScheduleWork(map<Edge*, Want>::iterator want_e);
/// Keep track of which edges we want to build in this plan. If this map does
/// not contain an entry for an edge, we do not want to build the entry or its
- /// dependents. If an entry maps to false, we do not want to build it, but we
- /// might want to build one of its dependents. If the entry maps to true, we
- /// want to build it.
- map<Edge*, bool> want_;
+ /// dependents. If it does contain an entry, the enumeration indicates what
+ /// we want for the edge.
+ map<Edge*, Want> want_;
set<Edge*> ready_;
@@ -177,7 +190,11 @@
State* state_;
const BuildConfig& config_;
Plan plan_;
+#if __cplusplus < 201703L
auto_ptr<CommandRunner> command_runner_;
+#else
+ unique_ptr<CommandRunner> command_runner_; // auto_ptr was removed in C++17.
+#endif
BuildStatus* status_;
private:
diff --git a/src/build_log.cc b/src/build_log.cc
index 8a52514..c4a08a0 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -35,6 +35,9 @@
#include "graph.h"
#include "metrics.h"
#include "util.h"
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+#define strtoll _strtoi64
+#endif
// Implementation details:
// Each run's log appends to the log file.
@@ -76,11 +79,17 @@
switch (len & 7)
{
case 7: h ^= uint64_t(data[6]) << 48;
+ NINJA_FALLTHROUGH;
case 6: h ^= uint64_t(data[5]) << 40;
+ NINJA_FALLTHROUGH;
case 5: h ^= uint64_t(data[4]) << 32;
+ NINJA_FALLTHROUGH;
case 4: h ^= uint64_t(data[3]) << 24;
+ NINJA_FALLTHROUGH;
case 3: h ^= uint64_t(data[2]) << 16;
+ NINJA_FALLTHROUGH;
case 2: h ^= uint64_t(data[1]) << 8;
+ NINJA_FALLTHROUGH;
case 1: h ^= uint64_t(data[0]);
h *= m;
};
@@ -105,7 +114,7 @@
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 +154,7 @@
}
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,11 +171,14 @@
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))
return false;
+ if (fflush(log_file_) != 0) {
+ return false;
+ }
}
}
return true;
@@ -290,7 +302,7 @@
if (!end)
continue;
*end = 0;
- restat_mtime = atol(start);
+ restat_mtime = strtoll(start, NULL, 10);
start = end + 1;
end = (char*)memchr(start, kFieldSeparator, line_end - start);
@@ -314,7 +326,7 @@
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);
@@ -353,8 +365,8 @@
}
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,
+ return fprintf(f, "%d\t%d\t%" PRId64 "\t%s\t%" PRIx64 "\n",
+ 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 @@
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 @@
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 @@
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..e471d13 100644
--- a/src/build_log_perftest.cc
+++ b/src/build_log_perftest.cc
@@ -71,7 +71,7 @@
long_rule_command += "$in -o $out\n";
State state;
- ManifestParser parser(&state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, NULL);
if (!parser.ParseTest("rule cxx\n command = " + long_rule_command, err))
return false;
@@ -92,7 +92,7 @@
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 @@
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 @@
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 @@
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 640e1b0..46ab33e 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -185,59 +185,6 @@
ASSERT_FALSE(edge); // done
}
-TEST_F(PlanTest, DependencyCycle) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat mid\n"
-"build mid: cat in\n"
-"build in: cat pre\n"
-"build pre: cat out\n"));
- GetNode("out")->MarkDirty();
- GetNode("mid")->MarkDirty();
- GetNode("in")->MarkDirty();
- GetNode("pre")->MarkDirty();
-
- string err;
- EXPECT_FALSE(plan_.AddTarget(GetNode("out"), &err));
- ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err);
-}
-
-TEST_F(PlanTest, CycleInEdgesButNotInNodes1) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build a b: cat a\n"));
- EXPECT_FALSE(plan_.AddTarget(GetNode("b"), &err));
- ASSERT_EQ("dependency cycle: a -> a", err);
-}
-
-TEST_F(PlanTest, CycleInEdgesButNotInNodes2) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build b a: cat a\n"));
- EXPECT_FALSE(plan_.AddTarget(GetNode("b"), &err));
- ASSERT_EQ("dependency cycle: a -> a", err);
-}
-
-TEST_F(PlanTest, CycleInEdgesButNotInNodes3) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build a b: cat c\n"
-"build c: cat a\n"));
- EXPECT_FALSE(plan_.AddTarget(GetNode("b"), &err));
- ASSERT_EQ("dependency cycle: c -> a -> c", err);
-}
-
-TEST_F(PlanTest, CycleInEdgesButNotInNodes4) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build d: cat c\n"
-"build c: cat b\n"
-"build b: cat a\n"
-"build a e: cat d\n"
-"build f: cat e\n"));
- EXPECT_FALSE(plan_.AddTarget(GetNode("f"), &err));
- ASSERT_EQ("dependency cycle: d -> c -> b -> a -> d", err);
-}
-
void PlanTest::TestPoolWithDepthOne(const char* test_case) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, test_case));
GetNode("out1")->MarkDirty();
@@ -608,7 +555,8 @@
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 +597,8 @@
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;
@@ -1119,6 +1068,19 @@
EXPECT_TRUE(builder_.AlreadyUpToDate());
}
+// Test a self-referencing phony. Ideally this should not work, but
+// ninja 1.7 and below tolerated and CMake 2.8.12.x and 3.0.x both
+// incorrectly produce it. We tolerate it for compatibility.
+TEST_F(BuildTest, PhonySelfReference) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build a: phony a\n"));
+
+ EXPECT_TRUE(builder_.AddTarget("a", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AlreadyUpToDate());
+}
+
TEST_F(BuildTest, Fail) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule fail\n"
@@ -1258,6 +1220,82 @@
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, RebuildWithNoInputs) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+" command = touch\n"
+"build out1: touch\n"
+"build out2: touch in\n"));
+
+ string err;
+
+ fs_.Create("in", "");
+
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ EXPECT_TRUE(builder_.AddTarget("out2", &err));
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ EXPECT_EQ(2u, command_runner_.commands_ran_.size());
+
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+
+ fs_.Tick();
+
+ fs_.Create("in", "");
+
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ EXPECT_TRUE(builder_.AddTarget("out2", &err));
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+}
+
TEST_F(BuildWithLogTest, RestatTest) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule true\n"
@@ -1438,7 +1476,7 @@
// 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 +1493,7 @@
// 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 {
@@ -1669,8 +1707,8 @@
TEST_F(BuildTest, StatFailureAbortsBuild) {
const string kTooLongToStat(400, 'i');
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-("build " + kTooLongToStat + ": cat " + kTooLongToStat + "\n").c_str()));
- // Also cyclic, for good measure.
+("build " + kTooLongToStat + ": cat in\n").c_str()));
+ fs_.Create("in", "");
// This simulates a stat failure:
fs_.files_[kTooLongToStat].mtime = -1;
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 @@
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/clean.cc b/src/clean.cc
index 1d6ba9e..ce6a575 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -101,6 +101,7 @@
printf("\n");
else
printf(" ");
+ fflush(stdout);
}
void Cleaner::PrintFooter() {
@@ -180,15 +181,22 @@
Reset();
PrintHeader();
for (int i = 0; i < target_count; ++i) {
- const char* target_name = targets[i];
- Node* target = state_->LookupNode(target_name);
- if (target) {
- if (IsVerbose())
- printf("Target %s\n", target_name);
- DoCleanTarget(target);
- } else {
- Error("unknown target '%s'", target_name);
+ string target_name = targets[i];
+ uint64_t slash_bits;
+ string err;
+ if (!CanonicalizePath(&target_name, &slash_bits, &err)) {
+ Error("failed to canonicalize '%s': %s", target_name.c_str(), err.c_str());
status_ = 1;
+ } else {
+ Node* target = state_->LookupNode(target_name);
+ if (target) {
+ if (IsVerbose())
+ printf("Target %s\n", target_name.c_str());
+ DoCleanTarget(target);
+ } else {
+ Error("unknown target '%s'", target_name.c_str());
+ status_ = 1;
+ }
}
}
PrintFooter();
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 @@
// 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 @@
// 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 @@
// 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 @@
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/depfile_parser.cc b/src/depfile_parser.cc
index 7cee892..345fa15 100644
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -1,4 +1,4 @@
-/* Generated by re2c 0.13.5 */
+/* Generated by re2c 0.16 */
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -53,7 +53,7 @@
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
- 0, 128, 0, 0, 0, 0, 0, 0,
+ 0, 128, 0, 0, 0, 128, 0, 0,
128, 128, 0, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 0, 0, 128, 0, 0,
@@ -82,88 +82,37 @@
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
};
-
yych = *in;
- if (yych <= '=') {
- if (yych <= '$') {
- if (yych <= ' ') {
- if (yych <= 0x00) goto yy7;
- goto yy9;
- } else {
- if (yych <= '!') goto yy5;
- if (yych <= '#') goto yy9;
- goto yy4;
- }
- } else {
- if (yych <= '*') {
- if (yych <= '\'') goto yy9;
- if (yych <= ')') goto yy5;
- goto yy9;
- } else {
- if (yych <= ':') goto yy5;
- if (yych <= '<') goto yy9;
- goto yy5;
- }
- }
+ if (yybm[0+yych] & 128) {
+ goto yy6;
+ }
+ if (yych <= '$') {
+ if (yych <= 0x00) goto yy2;
+ if (yych <= '#') goto yy4;
+ goto yy9;
} else {
- if (yych <= '_') {
- if (yych <= '[') {
- if (yych <= '?') goto yy9;
- if (yych <= 'Z') goto yy5;
- goto yy9;
- } else {
- if (yych <= '\\') goto yy2;
- if (yych <= '^') goto yy9;
- goto yy5;
- }
- } else {
- if (yych <= '|') {
- if (yych <= '`') goto yy9;
- if (yych <= '{') goto yy5;
- goto yy9;
- } else {
- if (yych == 0x7F) goto yy9;
- goto yy5;
- }
- }
+ if (yych == '\\') goto yy10;
+ goto yy4;
}
yy2:
++in;
- if ((yych = *in) <= '"') {
- if (yych <= '\f') {
- if (yych <= 0x00) goto yy3;
- if (yych != '\n') goto yy14;
- } else {
- if (yych <= '\r') goto yy3;
- if (yych == ' ') goto yy16;
- goto yy14;
- }
- } else {
- if (yych <= 'Z') {
- if (yych <= '#') goto yy16;
- if (yych == '*') goto yy16;
- goto yy14;
- } else {
- if (yych <= '\\') goto yy16;
- if (yych == '|') goto yy16;
- goto yy14;
- }
+ {
+ break;
}
-yy3:
+yy4:
+ ++in;
+yy5:
{
// For any other character (e.g. whitespace), swallow it here,
// allowing the outer logic to loop around again.
break;
}
-yy4:
- yych = *++in;
- if (yych == '$') goto yy12;
- goto yy3;
-yy5:
+yy6:
++in;
yych = *in;
- goto yy11;
-yy6:
+ if (yybm[0+yych] & 128) {
+ goto yy6;
+ }
{
// Got a span of plain text.
int len = (int)(in - start);
@@ -173,30 +122,41 @@
out += len;
continue;
}
-yy7:
- ++in;
- {
- break;
- }
yy9:
yych = *++in;
- goto yy3;
+ if (yych == '$') goto yy11;
+ goto yy5;
yy10:
- ++in;
- yych = *in;
-yy11:
- if (yybm[0+yych] & 128) {
- goto yy10;
+ yych = *++in;
+ if (yych <= '"') {
+ if (yych <= '\f') {
+ if (yych <= 0x00) goto yy5;
+ if (yych == '\n') goto yy5;
+ goto yy13;
+ } else {
+ if (yych <= '\r') goto yy5;
+ if (yych == ' ') goto yy15;
+ goto yy13;
+ }
+ } else {
+ if (yych <= 'Z') {
+ if (yych <= '#') goto yy15;
+ if (yych == '*') goto yy15;
+ goto yy13;
+ } else {
+ if (yych <= ']') goto yy15;
+ if (yych == '|') goto yy15;
+ goto yy13;
+ }
}
- goto yy6;
-yy12:
+yy11:
++in;
{
// De-escape dollar character.
*out++ = '$';
continue;
}
-yy14:
+yy13:
++in;
{
// Let backslash before other characters through verbatim.
@@ -204,7 +164,7 @@
*out++ = yych;
continue;
}
-yy16:
+yy15:
++in;
{
// De-escape backslashed character.
diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc
index 98c1621..464efda 100644
--- a/src/depfile_parser.in.cc
+++ b/src/depfile_parser.in.cc
@@ -55,7 +55,7 @@
re2c:indent:string = " ";
nul = "\000";
- escape = [ \\#*[|];
+ escape = [ \\#*[|\]];
'\\' escape {
// De-escape backslashed character.
@@ -73,7 +73,7 @@
*out++ = yych;
continue;
}
- [a-zA-Z0-9+,/_:.~()}{@=!\x80-\xFF-]+ {
+ [a-zA-Z0-9+,/_:.~()}{%@=!\x80-\xFF-]+ {
// Got a span of plain text.
int len = (int)(in - start);
// Need to shift it over if we're overwriting backslashes.
diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc
index ee798f8..824073f 100644
--- a/src/depfile_parser_test.cc
+++ b/src/depfile_parser_test.cc
@@ -122,12 +122,13 @@
"C:/Program\\ Files\\ (x86)/Microsoft\\ crtdefs.h: \n"
" en@quot.header~ t+t-x!=1 \n"
" openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif\n"
-" Fu\303\244ball",
+" Fu\303\244ball\n"
+" a\\[1\\]b@2%c",
&err));
ASSERT_EQ("", err);
EXPECT_EQ("C:/Program Files (x86)/Microsoft crtdefs.h",
parser_.out_.AsString());
- ASSERT_EQ(4u, parser_.ins_.size());
+ ASSERT_EQ(5u, parser_.ins_.size());
EXPECT_EQ("en@quot.header~",
parser_.ins_[0].AsString());
EXPECT_EQ("t+t-x!=1",
@@ -136,6 +137,8 @@
parser_.ins_[2].AsString());
EXPECT_EQ("Fu\303\244ball",
parser_.ins_[3].AsString());
+ EXPECT_EQ("a[1]b@2%c",
+ parser_.ins_[4].AsString());
}
TEST_F(DepfileParserTest, UnifyMultipleOutputs) {
diff --git a/src/deps_log.cc b/src/deps_log.cc
index 89c6023..0bb96f3 100644
--- a/src/deps_log.cc
+++ b/src/deps_log.cc
@@ -20,6 +20,9 @@
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
+#elif defined(_MSC_VER) && (_MSC_VER < 1900)
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
#endif
#include "graph.h"
@@ -30,7 +33,7 @@
// The version is stored as 4 bytes after the signature and also serves as a
// byte order mark. Signature and version combined are 16 bytes long.
const char kFileSignature[] = "# ninjadeps\n";
-const int kCurrentVersion = 3;
+const int kCurrentVersion = 4;
// Record size is currently limited to less than the full 32 bit, due to
// internal buffers having to have this size.
@@ -124,7 +127,7 @@
return true;
// Update on-disk representation.
- unsigned size = 4 * (1 + 1 + node_count);
+ unsigned size = 4 * (1 + 2 + node_count);
if (size > kMaxRecordSize) {
errno = ERANGE;
return false;
@@ -135,8 +138,11 @@
int id = node->id();
if (fwrite(&id, 4, 1, file_) < 1)
return false;
- int timestamp = mtime;
- if (fwrite(×tamp, 4, 1, file_) < 1)
+ uint32_t mtime_part = static_cast<uint32_t>(mtime & 0xffffffff);
+ if (fwrite(&mtime_part, 4, 1, file_) < 1)
+ return false;
+ mtime_part = static_cast<uint32_t>((mtime >> 32) & 0xffffffff);
+ if (fwrite(&mtime_part, 4, 1, file_) < 1)
return false;
for (int i = 0; i < node_count; ++i) {
id = nodes[i]->id();
@@ -209,7 +215,7 @@
bool is_deps = (size >> 31) != 0;
size = size & 0x7FFFFFFF;
- if (fread(buf, size, 1, f) < 1 || size > kMaxRecordSize) {
+ if (size > kMaxRecordSize || fread(buf, size, 1, f) < 1) {
read_failed = true;
break;
}
@@ -218,9 +224,11 @@
assert(size % 4 == 0);
int* deps_data = reinterpret_cast<int*>(buf);
int out_id = deps_data[0];
- int mtime = deps_data[1];
- deps_data += 2;
- int deps_count = (size / 4) - 2;
+ TimeStamp mtime;
+ mtime = (TimeStamp)(((uint64_t)(unsigned int)deps_data[2] << 32) |
+ (uint64_t)(unsigned int)deps_data[1]);
+ deps_data += 3;
+ int deps_count = (size / 4) - 3;
Deps* deps = new Deps(mtime, deps_count);
for (int i = 0; i < deps_count; ++i) {
diff --git a/src/deps_log.h b/src/deps_log.h
index cec0257..3812a28 100644
--- a/src/deps_log.h
+++ b/src/deps_log.h
@@ -57,7 +57,9 @@
/// one's complement of the expected index of the record (to detect
/// concurrent writes of multiple ninja processes to the log).
/// dependency records are an array of 4-byte integers
-/// [output path id, output path mtime, input path id, input path id...]
+/// [output path id,
+/// output path mtime (lower 4 bytes), output path mtime (upper 4 bytes),
+/// input path id, input path id...]
/// (The mtime is compared against the on-disk output path mtime
/// to verify the stored data is up-to-date.)
/// If two records reference the same output the latter one in the file
@@ -75,10 +77,10 @@
// Reading (startup-time) interface.
struct Deps {
- Deps(int mtime, int node_count)
+ Deps(int64_t mtime, int node_count)
: mtime(mtime), node_count(node_count), nodes(new Node*[node_count]) {}
~Deps() { delete [] nodes; }
- int mtime;
+ TimeStamp mtime;
int node_count;
Node** nodes;
};
diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc
index 89f7be1..0cdeb45 100644
--- a/src/deps_log_test.cc
+++ b/src/deps_log_test.cc
@@ -143,7 +143,7 @@
ASSERT_GT(file_size, 0);
}
- // Now reload the file, and readd the same deps.
+ // Now reload the file, and read the same deps.
{
State state;
DepsLog log;
@@ -203,7 +203,7 @@
ASSERT_GT(file_size, 0);
}
- // Now reload the file, and add slighly different deps.
+ // Now reload the file, and add slightly different deps.
int file_size_2;
{
State state;
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index 451a9b4..d4c2fb0 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -28,20 +28,22 @@
#include <direct.h> // _mkdir
#endif
+#include "metrics.h"
#include "util.h"
namespace {
string DirName(const string& path) {
#ifdef _WIN32
- const char kPathSeparators[] = "\\/";
+ static const char kPathSeparators[] = "\\/";
#else
- const char kPathSeparators[] = "/";
+ static const char kPathSeparators[] = "/";
#endif
+ static const char* const kEnd = kPathSeparators + sizeof(kPathSeparators) - 1;
+
string::size_type slash_pos = path.find_last_of(kPathSeparators);
if (slash_pos == string::npos)
return string(); // Nothing to do.
- const char* const kEnd = kPathSeparators + strlen(kPathSeparators);
while (slash_pos > 0 &&
std::find(kPathSeparators, kEnd, path[slash_pos - 1]) != kEnd)
--slash_pos;
@@ -60,17 +62,16 @@
TimeStamp TimeStampFromFileTime(const FILETIME& filetime) {
// FILETIME is in 100-nanosecond increments since the Windows epoch.
// We don't much care about epoch correctness but we do want the
- // resulting value to fit in an integer.
+ // resulting value to fit in a 64-bit integer.
uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) |
((uint64_t)filetime.dwLowDateTime);
- mtime /= 1000000000LL / 100; // 100ns -> s.
- mtime -= 12622770400LL; // 1600 epoch -> 2000 epoch (subtract 400 years).
- return (TimeStamp)mtime;
+ // 1600 epoch -> 2000 epoch (subtract 400 years).
+ return (TimeStamp)mtime - 12622770400LL * (1000000000LL / 100);
}
TimeStamp StatSingleFile(const string& path, string* err) {
WIN32_FILE_ATTRIBUTE_DATA attrs;
- if (!GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &attrs)) {
+ if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) {
DWORD win_err = GetLastError();
if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND)
return 0;
@@ -80,20 +81,15 @@
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) {
@@ -117,6 +113,11 @@
}
do {
string lowername = ffd.cFileName;
+ if (lowername == "..") {
+ // Seems to just copy the timestamp for ".." from ".", which is wrong.
+ // This is the case at least on NTFS under Windows 7.
+ continue;
+ }
transform(lowername.begin(), lowername.end(), lowername.begin(), ::tolower);
stamps->insert(make_pair(lowername,
TimeStampFromFileTime(ffd.ftLastWriteTime)));
@@ -153,6 +154,7 @@
// 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
@@ -168,6 +170,11 @@
string dir = DirName(path);
string base(path.substr(dir.size() ? dir.size() + 1 : 0));
+ if (base == "..") {
+ // StatAllFilesInDir does not report any information for base = "..".
+ base = ".";
+ dir = path;
+ }
transform(dir.begin(), dir.end(), dir.begin(), ::tolower);
transform(base.begin(), base.end(), base.begin(), ::tolower);
@@ -190,7 +197,27 @@
*err = "stat(" + path + "): " + strerror(errno);
return -1;
}
- return st.st_mtime;
+ // Some users (Flatpak) set mtime to 0, this should be harmless
+ // and avoids conflicting with our return value of 0 meaning
+ // that it doesn't exist.
+ if (st.st_mtime == 0)
+ return 1;
+#if defined(__APPLE__) && !defined(_POSIX_C_SOURCE)
+ return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL +
+ st.st_mtimespec.tv_nsec);
+#elif (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700 || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || \
+ defined(__BIONIC__) || (defined (__SVR4) && defined (__sun)) || defined(__FreeBSD__))
+ // For glibc, see "Timestamp files" in the Notes of http://www.kernel.org/doc/man-pages/online/pages/man2/stat.2.html
+ // newlib, uClibc and musl follow the kernel (or Cygwin) headers and define the right macro values above.
+ // For bsd, see https://github.com/freebsd/freebsd/blob/master/sys/sys/stat.h and similar
+ // For bionic, C and POSIX API is always enabled.
+ // For solaris, see https://docs.oracle.com/cd/E88353_01/html/E37841/stat-2.html.
+ return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
+#elif defined(_AIX)
+ return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n;
+#else
+ return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec;
+#endif
#endif
}
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index 7187bdf..2de4f28 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -87,6 +87,8 @@
string err;
ASSERT_TRUE(disk_.MakeDir("subdir"));
ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir"));
+ EXPECT_GT(disk_.Stat("..", &err), 1);
+ EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat(".", &err), 1);
EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat("subdir", &err), 1);
@@ -105,7 +107,6 @@
#ifdef _WIN32
TEST_F(DiskInterfaceTest, StatCache) {
string err;
- disk_.AllowStatCache(true);
ASSERT_TRUE(Touch("file1"));
ASSERT_TRUE(Touch("fiLE2"));
@@ -115,6 +116,10 @@
ASSERT_TRUE(Touch("subdir\\SUBFILE2"));
ASSERT_TRUE(Touch("subdir\\SUBFILE3"));
+ disk_.AllowStatCache(false);
+ TimeStamp parent_stat_uncached = disk_.Stat("..", &err);
+ disk_.AllowStatCache(true);
+
EXPECT_GT(disk_.Stat("FIle1", &err), 1);
EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat("file1", &err), 1);
@@ -125,6 +130,8 @@
EXPECT_GT(disk_.Stat("sUbdir\\suBFile1", &err), 1);
EXPECT_EQ("", err);
+ EXPECT_GT(disk_.Stat("..", &err), 1);
+ EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat(".", &err), 1);
EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat("subdir", &err), 1);
@@ -132,11 +139,15 @@
EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1);
EXPECT_EQ("", err);
+#ifndef _MSC_VER // TODO: Investigate why. Also see https://github.com/ninja-build/ninja/pull/1423
EXPECT_EQ(disk_.Stat("subdir", &err),
disk_.Stat("subdir/.", &err));
EXPECT_EQ("", err);
EXPECT_EQ(disk_.Stat("subdir", &err),
disk_.Stat("subdir/subsubdir/..", &err));
+#endif
+ EXPECT_EQ("", err);
+ EXPECT_EQ(disk_.Stat("..", &err), parent_stat_uncached);
EXPECT_EQ("", err);
EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err),
disk_.Stat("subdir/subsubdir/.", &err));
@@ -245,7 +256,7 @@
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out->in_edge(), NULL);
+ scan_.RecomputeDirty(out, NULL);
ASSERT_EQ(2u, stats_.size());
ASSERT_EQ("out", stats_[0]);
ASSERT_EQ("in", stats_[1]);
@@ -261,7 +272,7 @@
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out->in_edge(), NULL);
+ scan_.RecomputeDirty(out, NULL);
ASSERT_EQ(3u, stats_.size());
ASSERT_EQ("out", stats_[0]);
ASSERT_TRUE(GetNode("out")->dirty());
@@ -281,7 +292,7 @@
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out->in_edge(), NULL);
+ scan_.RecomputeDirty(out, NULL);
ASSERT_EQ(1u + 6u, stats_.size());
ASSERT_EQ("mid1", stats_[1]);
ASSERT_TRUE(GetNode("mid1")->dirty());
@@ -302,7 +313,7 @@
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out->in_edge(), NULL);
+ scan_.RecomputeDirty(out, NULL);
ASSERT_FALSE(GetNode("in")->dirty());
ASSERT_TRUE(GetNode("mid")->dirty());
ASSERT_TRUE(GetNode("out")->dirty());
diff --git a/src/graph.cc b/src/graph.cc
index f1d9ca2..b41c247 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -28,24 +28,47 @@
#include "util.h"
bool Node::Stat(DiskInterface* disk_interface, string* err) {
- METRIC_RECORD("node stat");
return (mtime_ = disk_interface->Stat(path_, err)) != -1;
}
-bool DependencyScan::RecomputeDirty(Edge* edge, string* err) {
+bool DependencyScan::RecomputeDirty(Node* node, string* err) {
+ vector<Node*> stack;
+ return RecomputeDirty(node, &stack, err);
+}
+
+bool DependencyScan::RecomputeDirty(Node* node, vector<Node*>* stack,
+ string* err) {
+ Edge* edge = node->in_edge();
+ if (!edge) {
+ // If we already visited this leaf node then we are done.
+ if (node->status_known())
+ return true;
+ // This node has no in-edge; it is dirty if it is missing.
+ if (!node->StatIfNecessary(disk_interface_, err))
+ return false;
+ if (!node->exists())
+ EXPLAIN("%s has no in-edge and is missing", node->path().c_str());
+ node->set_dirty(!node->exists());
+ return true;
+ }
+
+ // If we already finished this edge then we are done.
+ if (edge->mark_ == Edge::VisitDone)
+ return true;
+
+ // If we encountered this edge earlier in the call stack we have a cycle.
+ if (!VerifyDAG(node, stack, err))
+ return false;
+
+ // Mark the edge temporarily while in the call stack.
+ edge->mark_ = Edge::VisitInStack;
+ stack->push_back(node);
+
bool dirty = false;
edge->outputs_ready_ = true;
edge->deps_missing_ = false;
// Load output mtimes so we can compare them to the most recent input below.
- // RecomputeDirty() recursively walks the graph following the input nodes
- // of |edge| and the in_edges of these nodes. It uses the stat state of each
- // node to mark nodes as visited and doesn't traverse across nodes that have
- // been visited already. To make sure that every edge is visited only once
- // (important because an edge's deps are loaded every time it's visited), mark
- // all outputs of |edge| visited as a first step. This ensures that edges
- // with multiple inputs and outputs are visited only once, even in cyclic
- // graphs.
for (vector<Node*>::iterator o = edge->outputs_.begin();
o != edge->outputs_.end(); ++o) {
if (!(*o)->StatIfNecessary(disk_interface_, err))
@@ -64,19 +87,9 @@
Node* most_recent_input = NULL;
for (vector<Node*>::iterator i = edge->inputs_.begin();
i != edge->inputs_.end(); ++i) {
- if (!(*i)->status_known()) {
- if (!(*i)->StatIfNecessary(disk_interface_, err))
- return false;
- if (Edge* in_edge = (*i)->in_edge()) {
- if (!RecomputeDirty(in_edge, err))
- return false;
- } else {
- // This input has no in-edge; it is dirty if it is missing.
- if (!(*i)->exists())
- EXPLAIN("%s has no in-edge and is missing", (*i)->path().c_str());
- (*i)->set_dirty(!(*i)->exists());
- }
- }
+ // Visit this input.
+ if (!RecomputeDirty(*i, stack, err))
+ return false;
// If an input is not ready, neither are our outputs.
if (Edge* in_edge = (*i)->in_edge()) {
@@ -119,9 +132,54 @@
if (dirty && !(edge->is_phony() && edge->inputs_.empty()))
edge->outputs_ready_ = false;
+ // Mark the edge as finished during this walk now that it will no longer
+ // be in the call stack.
+ edge->mark_ = Edge::VisitDone;
+ assert(stack->back() == node);
+ stack->pop_back();
+
return true;
}
+bool DependencyScan::VerifyDAG(Node* node, vector<Node*>* stack, string* err) {
+ Edge* edge = node->in_edge();
+ assert(edge != NULL);
+
+ // If we have no temporary mark on the edge then we do not yet have a cycle.
+ if (edge->mark_ != Edge::VisitInStack)
+ return true;
+
+ // We have this edge earlier in the call stack. Find it.
+ vector<Node*>::iterator start = stack->begin();
+ while (start != stack->end() && (*start)->in_edge() != edge)
+ ++start;
+ assert(start != stack->end());
+
+ // Make the cycle clear by reporting its start as the node at its end
+ // instead of some other output of the starting edge. For example,
+ // running 'ninja b' on
+ // build a b: cat c
+ // build c: cat a
+ // should report a -> c -> a instead of b -> c -> a.
+ *start = node;
+
+ // Construct the error message rejecting the cycle.
+ *err = "dependency cycle: ";
+ for (vector<Node*>::const_iterator i = start; i != stack->end(); ++i) {
+ err->append((*i)->path());
+ err->append(" -> ");
+ }
+ err->append((*start)->path());
+
+ if ((start + 1) == stack->end() && edge->maybe_phonycycle_diagnostic()) {
+ // The manifest parser would have filtered out the self-referencing
+ // input if it were not configured to allow the error.
+ err->append(" [-w phonycycle=err]");
+ }
+
+ return false;
+}
+
bool DependencyScan::RecomputeOutputsDirty(Edge* edge, Node* most_recent_input,
bool* outputs_dirty, string* err) {
string command = edge->EvaluateCommand(/*incl_rsp_file=*/true);
@@ -169,13 +227,13 @@
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;
}
if (output_mtime < most_recent_input->mtime()) {
EXPLAIN("%soutput %s older than most recent input %s "
- "(%d vs %d)",
+ "(%" PRId64 " vs %" PRId64 ")",
used_restat ? "restat of " : "", output->path().c_str(),
most_recent_input->path().c_str(),
output_mtime, most_recent_input->mtime());
@@ -183,17 +241,29 @@
}
}
- // 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 (most_recent_input && 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 (%" PRId64 " vs %" PRId64 ")",
+ 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;
}
@@ -347,11 +417,19 @@
return pool() == &State::kConsolePool;
}
+bool Edge::maybe_phonycycle_diagnostic() const {
+ // CMake 2.8.12.x and 3.0.x produced self-referencing phony rules
+ // of the form "build a: phony ... a ...". Restrict our
+ // "phonycycle" diagnostic option to the form it used.
+ return is_phony() && outputs_.size() == 1 && implicit_outs_ == 0 &&
+ implicit_deps_ == 0;
+}
+
// 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 = '\\';
@@ -363,7 +441,7 @@
}
void Node::Dump(const char* prefix) const {
- printf("%s <%s 0x%p> mtime: %d%s, (:%s), ",
+ printf("%s <%s 0x%p> mtime: %" PRId64 "%s, (:%s), ",
prefix, path().c_str(), this,
mtime(), mtime() ? "" : " (:missing)",
dirty() ? " dirty" : " clean");
@@ -420,10 +498,12 @@
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 +522,7 @@
// 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;
@@ -467,7 +547,7 @@
// Deps are invalid if the output is newer than the deps.
if (output->mtime() > deps->mtime) {
- EXPLAIN("stored deps info out of date for '%s' (%d vs %d)",
+ EXPLAIN("stored deps info out of date for '%s' (%" PRId64 " vs %" PRId64 ")",
output->path().c_str(), deps->mtime, output->mtime());
return false;
}
diff --git a/src/graph.h b/src/graph.h
index add8d3d..a8f0641 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -21,6 +21,7 @@
#include "eval_env.h"
#include "timestamp.h"
+#include "util.h"
struct BuildLog;
struct DiskInterface;
@@ -33,7 +34,7 @@
/// 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 @@
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 @@
/// 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
@@ -127,7 +128,13 @@
/// An edge in the dependency graph; links between Nodes using Rules.
struct Edge {
- Edge() : rule_(NULL), pool_(NULL), env_(NULL),
+ enum VisitMark {
+ VisitNone,
+ VisitInStack,
+ VisitDone
+ };
+
+ Edge() : rule_(NULL), pool_(NULL), env_(NULL), mark_(VisitNone),
outputs_ready_(false), deps_missing_(false),
implicit_deps_(0), order_only_deps_(0), implicit_outs_(0) {}
@@ -155,6 +162,7 @@
vector<Node*> inputs_;
vector<Node*> outputs_;
BindingEnv* env_;
+ VisitMark mark_;
bool outputs_ready_;
bool deps_missing_;
@@ -193,6 +201,7 @@
bool is_phony() const;
bool use_console() const;
+ bool maybe_phonycycle_diagnostic() const;
};
@@ -245,11 +254,12 @@
disk_interface_(disk_interface),
dep_loader_(state, deps_log, disk_interface) {}
+ /// Update the |dirty_| state of the given node by inspecting its input edge.
/// Examine inputs, outputs, and command lines to judge whether an edge
/// needs to be re-run, and update outputs_ready_ and each outputs' |dirty_|
/// state accordingly.
/// Returns false on failure.
- bool RecomputeDirty(Edge* edge, string* err);
+ bool RecomputeDirty(Node* node, string* err);
/// Recompute whether any output of the edge is dirty, if so sets |*dirty|.
/// Returns false on failure.
@@ -268,6 +278,9 @@
}
private:
+ bool RecomputeDirty(Node* node, vector<Node*>* stack, string* err);
+ bool VerifyDAG(Node* node, vector<Node*>* stack, string* err);
+
/// Recompute whether a given single output should be marked dirty.
/// Returns true if so.
bool RecomputeOutputDirty(Edge* edge, Node* most_recent_input,
diff --git a/src/graph_test.cc b/src/graph_test.cc
index be08b19..422bc9a 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -30,9 +30,8 @@
fs_.Create("in", "");
fs_.Create("out", "");
- Edge* edge = GetNode("out")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
ASSERT_EQ("", err);
// A missing implicit dep *should* make the output dirty.
@@ -49,9 +48,8 @@
fs_.Tick();
fs_.Create("implicit", "");
- Edge* edge = GetNode("out")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
ASSERT_EQ("", err);
// A modified implicit dep should make the output dirty.
@@ -70,9 +68,8 @@
fs_.Tick();
fs_.Create("implicit.h", "");
- Edge* edge = GetNode("out.o")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
ASSERT_EQ("", err);
// implicit.h has changed, though our depfile refers to it with a
@@ -94,9 +91,8 @@
fs_.Tick();
fs_.Create("data", "");
- Edge* edge = GetNode("out.o")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
ASSERT_EQ("", err);
// We have both an implicit and an explicit dep on implicit.h.
@@ -123,9 +119,8 @@
fs_.Create("in", "");
fs_.Create("out", "");
- Edge* edge = GetNode("out")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out")->dirty());
@@ -140,9 +135,8 @@
fs_.Create("in", "");
fs_.Create("out", "");
- Edge* edge = GetNode("out")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out")->dirty());
@@ -165,9 +159,8 @@
"build | out.imp: cat in\n"));
fs_.Create("in", "");
- Edge* edge = GetNode("out.imp")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out.imp")->dirty());
@@ -180,9 +173,8 @@
fs_.Tick();
fs_.Create("in", "");
- Edge* edge = GetNode("out.imp")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out.imp")->dirty());
@@ -198,9 +190,8 @@
fs_.Create("out.o.d", "out.o: foo.cc\n");
fs_.Create("out.o", "");
- Edge* edge = GetNode("out.o")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("out.o")->dirty());
@@ -247,9 +238,8 @@
fs_.Create("out.o.d", "out.o: bar/../foo.cc\n");
fs_.Create("out.o", "");
- Edge* edge = GetNode("out.o")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("out.o")->dirty());
@@ -268,15 +258,14 @@
fs_.Create("out.o.d", "out.o: foo.h\n");
fs_.Create("out.o", "");
- Edge* edge = GetNode("out.o")->in_edge();
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("out.o")->dirty());
state_.Reset();
fs_.RemoveFile("out.o.d");
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out.o")->dirty());
}
@@ -323,8 +312,7 @@
"build n2: phony n1\n"
);
string err;
- Edge* edge = GetNode("n2")->in_edge();
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("n2"), &err));
ASSERT_EQ("", err);
Plan plan_;
@@ -335,6 +323,67 @@
ASSERT_FALSE(plan_.more_to_do());
}
+TEST_F(GraphTest, PhonySelfReferenceError) {
+ ManifestParserOptions parser_opts;
+ parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
+ AssertParse(&state_,
+"build a: phony a\n",
+ parser_opts);
+
+ string err;
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), &err));
+ ASSERT_EQ("dependency cycle: a -> a [-w phonycycle=err]", err);
+}
+
+TEST_F(GraphTest, DependencyCycle) {
+ AssertParse(&state_,
+"build out: cat mid\n"
+"build mid: cat in\n"
+"build in: cat pre\n"
+"build pre: cat out\n");
+
+ string err;
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+ ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err);
+}
+
+TEST_F(GraphTest, CycleInEdgesButNotInNodes1) {
+ string err;
+ AssertParse(&state_,
+"build a b: cat a\n");
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), &err));
+ ASSERT_EQ("dependency cycle: a -> a", err);
+}
+
+TEST_F(GraphTest, CycleInEdgesButNotInNodes2) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build b a: cat a\n"));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), &err));
+ ASSERT_EQ("dependency cycle: a -> a", err);
+}
+
+TEST_F(GraphTest, CycleInEdgesButNotInNodes3) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build a b: cat c\n"
+"build c: cat a\n"));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), &err));
+ ASSERT_EQ("dependency cycle: a -> c -> a", err);
+}
+
+TEST_F(GraphTest, CycleInEdgesButNotInNodes4) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build d: cat c\n"
+"build c: cat b\n"
+"build b: cat a\n"
+"build a e: cat d\n"
+"build f: cat e\n"));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("f"), &err));
+ ASSERT_EQ("dependency cycle: a -> d -> c -> b -> a", err);
+}
+
// Verify that cycles in graphs with multiple outputs are handled correctly
// in RecomputeDirty() and don't cause deps to be loaded multiple times.
TEST_F(GraphTest, CycleWithLengthZeroFromDepfile) {
@@ -347,13 +396,13 @@
fs_.Create("dep.d", "a: b\n");
string err;
- Edge* edge = GetNode("a")->in_edge();
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
- ASSERT_EQ("", err);
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), &err));
+ ASSERT_EQ("dependency cycle: b -> b", err);
// Despite the depfile causing edge to be a cycle (it has outputs a and b,
// but the depfile also adds b as an input), the deps should have been loaded
// only once:
+ Edge* edge = GetNode("a")->in_edge();
EXPECT_EQ(1, edge->inputs_.size());
EXPECT_EQ("b", edge->inputs_[0]->path());
}
@@ -372,13 +421,13 @@
fs_.Create("dep.d", "a: c\n");
string err;
- Edge* edge = GetNode("a")->in_edge();
- EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
- ASSERT_EQ("", err);
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), &err));
+ ASSERT_EQ("dependency cycle: b -> c -> b", err);
// Despite the depfile causing edge to be a cycle (|edge| has outputs a and b,
// but c's in_edge has b as input but the depfile also adds |edge| as
// output)), the deps should have been loaded only once:
+ Edge* edge = GetNode("a")->in_edge();
EXPECT_EQ(1, edge->inputs_.size());
EXPECT_EQ("c", edge->inputs_[0]->path());
}
@@ -399,8 +448,8 @@
fs_.Create("dep.d", "a: c\n");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("d")->in_edge(), &err));
- ASSERT_EQ("", err);
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("d"), &err));
+ ASSERT_EQ("dependency cycle: b -> c -> b", err);
// Despite the depfile causing edge to be a cycle (|edge| has outputs a and b,
// but c's in_edge has b as input but the depfile also adds |edge| as
diff --git a/src/hash_map.h b/src/hash_map.h
index a91aeb9..55d2c9d 100644
--- a/src/hash_map.h
+++ b/src/hash_map.h
@@ -18,6 +18,7 @@
#include <algorithm>
#include <string.h>
#include "string_piece.h"
+#include "util.h"
// MurmurHash2, by Austin Appleby
static inline
@@ -40,7 +41,9 @@
}
switch (len) {
case 3: h ^= data[2] << 16;
+ NINJA_FALLTHROUGH;
case 2: h ^= data[1] << 8;
+ NINJA_FALLTHROUGH;
case 1: h ^= data[0];
h *= m;
};
diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc
index ca35012..79bf5b4 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,12 +26,62 @@
namespace {
-/// Return true if paths a and b are on the same Windows drive.
-bool SameDrive(StringPiece a, StringPiece b) {
+bool InternalGetFullPathName(const StringPiece& file_name, char* buffer,
+ size_t buffer_length, string *err) {
+ DWORD result_size = GetFullPathNameA(file_name.AsString().c_str(),
+ buffer_length, buffer, NULL);
+ if (result_size == 0) {
+ *err = "GetFullPathNameA(" + file_name.AsString() + "): " +
+ GetLastErrorString();
+ return false;
+ } else if (result_size > buffer_length) {
+ *err = "path too long";
+ return false;
+ }
+ return true;
+}
+
+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, string* err) {
+ 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);
- GetFullPathName(b.AsString().c_str(), sizeof(b_absolute), b_absolute, NULL);
+ if (!InternalGetFullPathName(a, a_absolute, sizeof(a_absolute), err)) {
+ return false;
+ }
+ if (!InternalGetFullPathName(b, b_absolute, sizeof(b_absolute), err)) {
+ return false;
+ }
char a_drive[_MAX_DIR];
char b_drive[_MAX_DIR];
_splitpath(a_absolute, a_drive, NULL, NULL, NULL);
@@ -38,64 +89,98 @@
return _stricmp(a_drive, b_drive) == 0;
}
+// 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;
+ }
+
+ // Check "..".
+ if (i + 2 < s.size() && s[i+1] == '.' && s[i+2] == '.' &&
+ (i + 3 >= s.size() || IsPathSeparator(s[i+3]))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
} // anonymous namespace
-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;
+IncludesNormalize::IncludesNormalize(const string& relative_to) {
+ string err;
+ relative_to_ = AbsPath(relative_to, &err);
+ if (!err.empty()) {
+ Fatal("Initializing IncludesNormalize(): %s", err.c_str());
}
- return ret;
+ split_relative_to_ = SplitStringPiece(relative_to_, '/');
}
-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;
-}
+string IncludesNormalize::AbsPath(StringPiece s, string* err) {
+ if (IsFullPathName(s)) {
+ string result = s.AsString();
+ for (size_t i = 0; i < result.size(); ++i) {
+ if (result[i] == '\\') {
+ result[i] = '/';
+ }
+ }
+ return result;
+ }
-string IncludesNormalize::ToLower(const string& s) {
- string ret;
- transform(s.begin(), s.end(), back_inserter(ret), ::tolower);
- return ret;
-}
-
-string IncludesNormalize::AbsPath(StringPiece s) {
char result[_MAX_PATH];
- GetFullPathName(s.AsString().c_str(), sizeof(result), result, NULL);
+ if (!InternalGetFullPathName(s, result, sizeof(result), err)) {
+ return "";
+ }
for (char* c = result; *c; ++c)
if (*c == '\\')
*c = '/';
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* err) {
+ string abs_path = AbsPath(path, err);
+ if (!err->empty())
+ return "";
+ 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 +188,22 @@
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, err);
+ if (!err->empty())
+ return false;
- string curdir;
- if (!relative_to) {
- curdir = AbsPath(".");
- relative_to = curdir.c_str();
- }
- if (!SameDrive(partially_fixed, relative_to)) {
+ if (!SameDrive(abs_input, relative_to_, err)) {
+ if (!err->empty())
+ return false;
*result = partially_fixed.AsString();
return true;
}
- *result = Relativize(partially_fixed, relative_to);
+ *result = Relativize(abs_input, split_relative_to_, err);
+ if (!err->empty())
+ return false;
return true;
}
diff --git a/src/includes_normalize.h b/src/includes_normalize.h
index 98e912f..0339581 100644
--- a/src/includes_normalize.h
+++ b/src/includes_normalize.h
@@ -21,15 +21,19 @@
/// 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 AbsPath(StringPiece s, string* err);
+ static string Relativize(StringPiece path,
+ const vector<StringPiece>& start_list, string* err);
/// 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..dbcdbe0 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 @@
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 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;
}
@@ -56,9 +58,12 @@
}
TEST(IncludesNormalize, WithRelative) {
+ string err;
string currentdir = GetCurDir();
EXPECT_EQ("c", NormalizeRelativeAndCheckNoError("a/b/c", "a/b"));
- EXPECT_EQ("a", NormalizeAndCheckNoError(IncludesNormalize::AbsPath("a")));
+ EXPECT_EQ("a",
+ NormalizeAndCheckNoError(IncludesNormalize::AbsPath("a", &err)));
+ EXPECT_EQ("", err);
EXPECT_EQ(string("../") + currentdir + string("/a"),
NormalizeRelativeAndCheckNoError("a", "../b"));
EXPECT_EQ(string("../") + currentdir + string("/a/b"),
@@ -76,34 +81,6 @@
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,40 +106,62 @@
"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[] =
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "012345678\\"
- "0123456789";
+
+ // Construct max size path having cwd prefix.
+ // kExactlyMaxPath = "$cwd\\a\\aaaa...aaaa\0";
+ char kExactlyMaxPath[_MAX_PATH + 1];
+ ASSERT_NE(_getcwd(kExactlyMaxPath, sizeof kExactlyMaxPath), NULL);
+
+ int cwd_len = strlen(kExactlyMaxPath);
+ ASSERT_LE(cwd_len + 3 + 1, _MAX_PATH)
+ kExactlyMaxPath[cwd_len] = '\\';
+ kExactlyMaxPath[cwd_len + 1] = 'a';
+ kExactlyMaxPath[cwd_len + 2] = '\\';
+
+ kExactlyMaxPath[cwd_len + 3] = 'a';
+
+ for (int i = cwd_len + 4; i < _MAX_PATH; ++i) {
+ if (i > cwd_len + 4 && i < _MAX_PATH - 1 && i % 10 == 0)
+ kExactlyMaxPath[i] = '\\';
+ else
+ kExactlyMaxPath[i] = 'a';
+ }
+
+ kExactlyMaxPath[_MAX_PATH] = '\0';
+ EXPECT_EQ(strlen(kExactlyMaxPath), _MAX_PATH);
+
string forward_slashes(kExactlyMaxPath);
replace(forward_slashes.begin(), forward_slashes.end(), '\\', '/');
// Make sure a path that's exactly _MAX_PATH long is canonicalized.
- EXPECT_EQ(forward_slashes,
+ EXPECT_EQ(forward_slashes.substr(cwd_len + 1),
NormalizeAndCheckNoError(kExactlyMaxPath));
}
+
+TEST(IncludesNormalize, ShortRelativeButTooLongAbsolutePath) {
+ string result, err;
+ IncludesNormalize normalizer(".");
+ // A short path should work
+ EXPECT_TRUE(normalizer.Normalize("a", &result, &err));
+ EXPECT_EQ("", err);
+
+ // Construct max size path having cwd prefix.
+ // kExactlyMaxPath = "aaaa\\aaaa...aaaa\0";
+ char kExactlyMaxPath[_MAX_PATH + 1];
+ for (int i = 0; i < _MAX_PATH; ++i) {
+ if (i < _MAX_PATH - 1 && i % 10 == 4)
+ kExactlyMaxPath[i] = '\\';
+ else
+ kExactlyMaxPath[i] = 'a';
+ }
+ kExactlyMaxPath[_MAX_PATH] = '\0';
+ EXPECT_EQ(strlen(kExactlyMaxPath), _MAX_PATH);
+
+ // Make sure a path that's exactly _MAX_PATH long fails with a proper error.
+ EXPECT_FALSE(normalizer.Normalize(kExactlyMaxPath, &result, &err));
+ EXPECT_TRUE(err.find("GetFullPathName") != string::npos);
+}
diff --git a/src/lexer.cc b/src/lexer.cc
index 37b8678..35ae97b 100644
--- a/src/lexer.cc
+++ b/src/lexer.cc
@@ -1,4 +1,4 @@
-/* Generated by re2c 0.13.5 */
+/* Generated by re2c 0.16 */
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,14 +23,14 @@
bool Lexer::Error(const string& message, string* err) {
// Compute line/column.
int line = 1;
- const char* context = input_.str_;
+ const char* line_start = input_.str_;
for (const char* p = input_.str_; p < last_token_; ++p) {
if (*p == '\n') {
++line;
- context = p + 1;
+ line_start = p + 1;
}
}
- int col = last_token_ ? (int)(last_token_ - context) : 0;
+ int col = last_token_ ? (int)(last_token_ - line_start) : 0;
char buf[1024];
snprintf(buf, sizeof(buf), "%s:%d: ", filename_.AsString().c_str(), line);
@@ -43,12 +43,12 @@
int len;
bool truncated = true;
for (len = 0; len < kTruncateColumn; ++len) {
- if (context[len] == 0 || context[len] == '\n') {
+ if (line_start[len] == 0 || line_start[len] == '\n') {
truncated = false;
break;
}
}
- *err += string(context, len);
+ *err += string(line_start, len);
if (truncated)
*err += "...";
*err += "\n";
@@ -126,305 +126,325 @@
unsigned char yych;
unsigned int yyaccept = 0;
static const unsigned char yybm[] = {
- 0, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 0, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 192, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 96, 96, 64,
- 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 64, 64, 64, 64, 64, 64,
- 64, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 64, 64, 64, 64, 96,
- 64, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64,
+ 0, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 0, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128,
+ 160, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 192, 192, 128,
+ 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 128, 128, 128, 128, 128, 128,
+ 128, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 128, 128, 128, 128, 192,
+ 128, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 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, 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, 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, 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, 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, 128, 128, 128,
};
-
yych = *p;
- if (yych <= 'Z') {
- if (yych <= '#') {
+ if (yybm[0+yych] & 32) {
+ goto yy9;
+ }
+ if (yych <= '^') {
+ if (yych <= ',') {
if (yych <= '\f') {
- if (yych <= 0x00) goto yy23;
- if (yych == '\n') goto yy7;
- goto yy25;
+ if (yych <= 0x00) goto yy2;
+ if (yych == '\n') goto yy6;
+ goto yy4;
} else {
- if (yych <= 0x1F) {
- if (yych <= '\r') goto yy6;
- goto yy25;
- } else {
- if (yych <= ' ') goto yy2;
- if (yych <= '"') goto yy25;
- goto yy4;
- }
+ if (yych <= '\r') goto yy8;
+ if (yych == '#') goto yy12;
+ goto yy4;
}
} else {
- if (yych <= '9') {
- if (yych <= ',') goto yy25;
- if (yych == '/') goto yy25;
- goto yy22;
+ if (yych <= ':') {
+ if (yych == '/') goto yy4;
+ if (yych <= '9') goto yy13;
+ goto yy16;
} else {
- if (yych <= '<') {
- if (yych <= ':') goto yy16;
- goto yy25;
+ if (yych <= '=') {
+ if (yych <= '<') goto yy4;
+ goto yy18;
} else {
- if (yych <= '=') goto yy14;
- if (yych <= '@') goto yy25;
- goto yy22;
+ if (yych <= '@') goto yy4;
+ if (yych <= 'Z') goto yy13;
+ goto yy4;
}
}
}
} else {
if (yych <= 'i') {
- if (yych <= 'a') {
- if (yych == '_') goto yy22;
- if (yych <= '`') goto yy25;
- goto yy22;
+ if (yych <= 'b') {
+ if (yych == '`') goto yy4;
+ if (yych <= 'a') goto yy13;
+ goto yy20;
} else {
- if (yych <= 'c') {
- if (yych <= 'b') goto yy9;
- goto yy22;
- } else {
- if (yych <= 'd') goto yy13;
- if (yych <= 'h') goto yy22;
- goto yy20;
- }
+ if (yych == 'd') goto yy21;
+ if (yych <= 'h') goto yy13;
+ goto yy22;
}
} else {
if (yych <= 'r') {
- if (yych == 'p') goto yy11;
- if (yych <= 'q') goto yy22;
- goto yy12;
+ if (yych == 'p') goto yy23;
+ if (yych <= 'q') goto yy13;
+ goto yy24;
} else {
if (yych <= 'z') {
- if (yych <= 's') goto yy21;
- goto yy22;
+ if (yych <= 's') goto yy25;
+ goto yy13;
} else {
- if (yych == '|') goto yy18;
- goto yy25;
+ if (yych == '|') goto yy26;
+ goto yy4;
}
}
}
}
yy2:
- yyaccept = 0;
- yych = *(q = ++p);
- goto yy73;
-yy3:
- { token = INDENT; break; }
+ ++p;
+ { token = TEOF; break; }
yy4:
- yyaccept = 1;
- yych = *(q = ++p);
- if (yych >= 0x01) goto yy68;
+ ++p;
yy5:
{ token = ERROR; break; }
yy6:
- yych = *++p;
- if (yych == '\n') goto yy65;
- goto yy5;
-yy7:
++p;
-yy8:
{ token = NEWLINE; break; }
+yy8:
+ yych = *++p;
+ if (yych == '\n') goto yy28;
+ goto yy5;
yy9:
- ++p;
- if ((yych = *p) == 'u') goto yy60;
- goto yy27;
-yy10:
- { token = IDENT; break; }
+ yyaccept = 0;
+ q = ++p;
+ yych = *p;
+ if (yybm[0+yych] & 32) {
+ goto yy9;
+ }
+ if (yych <= '\f') {
+ if (yych == '\n') goto yy6;
+ } else {
+ if (yych <= '\r') goto yy30;
+ if (yych == '#') goto yy32;
+ }
yy11:
- yych = *++p;
- if (yych == 'o') goto yy56;
- goto yy27;
+ { token = INDENT; break; }
yy12:
- yych = *++p;
- if (yych == 'u') goto yy52;
- goto yy27;
+ yyaccept = 1;
+ yych = *(q = ++p);
+ if (yych <= 0x00) goto yy5;
+ goto yy33;
yy13:
- yych = *++p;
- if (yych == 'e') goto yy45;
- goto yy27;
-yy14:
++p;
- { token = EQUALS; break; }
+ yych = *p;
+yy14:
+ if (yybm[0+yych] & 64) {
+ goto yy13;
+ }
+ { token = IDENT; break; }
yy16:
++p;
{ token = COLON; break; }
yy18:
++p;
- if ((yych = *p) == '|') goto yy43;
- { token = PIPE; break; }
+ { token = EQUALS; break; }
yy20:
yych = *++p;
- if (yych == 'n') goto yy36;
- goto yy27;
+ if (yych == 'u') goto yy36;
+ goto yy14;
yy21:
yych = *++p;
- if (yych == 'u') goto yy28;
- goto yy27;
+ if (yych == 'e') goto yy37;
+ goto yy14;
yy22:
yych = *++p;
- goto yy27;
+ if (yych == 'n') goto yy38;
+ goto yy14;
yy23:
- ++p;
- { token = TEOF; break; }
+ yych = *++p;
+ if (yych == 'o') goto yy39;
+ goto yy14;
+yy24:
+ yych = *++p;
+ if (yych == 'u') goto yy40;
+ goto yy14;
yy25:
yych = *++p;
- goto yy5;
+ if (yych == 'u') goto yy41;
+ goto yy14;
yy26:
++p;
- yych = *p;
-yy27:
- if (yybm[0+yych] & 32) {
- goto yy26;
- }
- goto yy10;
+ if ((yych = *p) == '|') goto yy42;
+ { token = PIPE; break; }
yy28:
- yych = *++p;
- if (yych != 'b') goto yy27;
- yych = *++p;
- if (yych != 'n') goto yy27;
- yych = *++p;
- if (yych != 'i') goto yy27;
- yych = *++p;
- if (yych != 'n') goto yy27;
- yych = *++p;
- if (yych != 'j') goto yy27;
- yych = *++p;
- if (yych != 'a') goto yy27;
- ++p;
- if (yybm[0+(yych = *p)] & 32) {
- goto yy26;
- }
- { token = SUBNINJA; break; }
-yy36:
- yych = *++p;
- if (yych != 'c') goto yy27;
- yych = *++p;
- if (yych != 'l') goto yy27;
- yych = *++p;
- if (yych != 'u') goto yy27;
- yych = *++p;
- if (yych != 'd') goto yy27;
- yych = *++p;
- if (yych != 'e') goto yy27;
- ++p;
- if (yybm[0+(yych = *p)] & 32) {
- goto yy26;
- }
- { token = INCLUDE; break; }
-yy43:
- ++p;
- { token = PIPE2; break; }
-yy45:
- yych = *++p;
- if (yych != 'f') goto yy27;
- yych = *++p;
- if (yych != 'a') goto yy27;
- yych = *++p;
- if (yych != 'u') goto yy27;
- yych = *++p;
- if (yych != 'l') goto yy27;
- yych = *++p;
- if (yych != 't') goto yy27;
- ++p;
- if (yybm[0+(yych = *p)] & 32) {
- goto yy26;
- }
- { token = DEFAULT; break; }
-yy52:
- yych = *++p;
- if (yych != 'l') goto yy27;
- yych = *++p;
- if (yych != 'e') goto yy27;
- ++p;
- if (yybm[0+(yych = *p)] & 32) {
- goto yy26;
- }
- { token = RULE; break; }
-yy56:
- yych = *++p;
- if (yych != 'o') goto yy27;
- yych = *++p;
- if (yych != 'l') goto yy27;
- ++p;
- if (yybm[0+(yych = *p)] & 32) {
- goto yy26;
- }
- { token = POOL; break; }
-yy60:
- yych = *++p;
- if (yych != 'i') goto yy27;
- yych = *++p;
- if (yych != 'l') goto yy27;
- yych = *++p;
- if (yych != 'd') goto yy27;
- ++p;
- if (yybm[0+(yych = *p)] & 32) {
- goto yy26;
- }
- { token = BUILD; break; }
-yy65:
++p;
{ token = NEWLINE; break; }
-yy67:
- ++p;
- yych = *p;
-yy68:
- if (yybm[0+yych] & 64) {
- goto yy67;
- }
- if (yych >= 0x01) goto yy70;
-yy69:
+yy30:
+ yych = *++p;
+ if (yych == '\n') goto yy28;
+yy31:
p = q;
- if (yyaccept <= 0) {
- goto yy3;
+ if (yyaccept == 0) {
+ goto yy11;
} else {
goto yy5;
}
-yy70:
+yy32:
+ ++p;
+ yych = *p;
+yy33:
+ if (yybm[0+yych] & 128) {
+ goto yy32;
+ }
+ if (yych <= 0x00) goto yy31;
++p;
{ continue; }
-yy72:
- yyaccept = 0;
- q = ++p;
- yych = *p;
-yy73:
- if (yybm[0+yych] & 128) {
- goto yy72;
- }
- if (yych <= '\f') {
- if (yych != '\n') goto yy3;
- } else {
- if (yych <= '\r') goto yy75;
- if (yych == '#') goto yy67;
- goto yy3;
- }
+yy36:
yych = *++p;
- goto yy8;
-yy75:
+ if (yych == 'i') goto yy44;
+ goto yy14;
+yy37:
+ yych = *++p;
+ if (yych == 'f') goto yy45;
+ goto yy14;
+yy38:
+ yych = *++p;
+ if (yych == 'c') goto yy46;
+ goto yy14;
+yy39:
+ yych = *++p;
+ if (yych == 'o') goto yy47;
+ goto yy14;
+yy40:
+ yych = *++p;
+ if (yych == 'l') goto yy48;
+ goto yy14;
+yy41:
+ yych = *++p;
+ if (yych == 'b') goto yy49;
+ goto yy14;
+yy42:
++p;
- if ((yych = *p) == '\n') goto yy65;
- goto yy69;
+ { token = PIPE2; break; }
+yy44:
+ yych = *++p;
+ if (yych == 'l') goto yy50;
+ goto yy14;
+yy45:
+ yych = *++p;
+ if (yych == 'a') goto yy51;
+ goto yy14;
+yy46:
+ yych = *++p;
+ if (yych == 'l') goto yy52;
+ goto yy14;
+yy47:
+ yych = *++p;
+ if (yych == 'l') goto yy53;
+ goto yy14;
+yy48:
+ yych = *++p;
+ if (yych == 'e') goto yy55;
+ goto yy14;
+yy49:
+ yych = *++p;
+ if (yych == 'n') goto yy57;
+ goto yy14;
+yy50:
+ yych = *++p;
+ if (yych == 'd') goto yy58;
+ goto yy14;
+yy51:
+ yych = *++p;
+ if (yych == 'u') goto yy60;
+ goto yy14;
+yy52:
+ yych = *++p;
+ if (yych == 'u') goto yy61;
+ goto yy14;
+yy53:
+ ++p;
+ if (yybm[0+(yych = *p)] & 64) {
+ goto yy13;
+ }
+ { token = POOL; break; }
+yy55:
+ ++p;
+ if (yybm[0+(yych = *p)] & 64) {
+ goto yy13;
+ }
+ { token = RULE; break; }
+yy57:
+ yych = *++p;
+ if (yych == 'i') goto yy62;
+ goto yy14;
+yy58:
+ ++p;
+ if (yybm[0+(yych = *p)] & 64) {
+ goto yy13;
+ }
+ { token = BUILD; break; }
+yy60:
+ yych = *++p;
+ if (yych == 'l') goto yy63;
+ goto yy14;
+yy61:
+ yych = *++p;
+ if (yych == 'd') goto yy64;
+ goto yy14;
+yy62:
+ yych = *++p;
+ if (yych == 'n') goto yy65;
+ goto yy14;
+yy63:
+ yych = *++p;
+ if (yych == 't') goto yy66;
+ goto yy14;
+yy64:
+ yych = *++p;
+ if (yych == 'e') goto yy68;
+ goto yy14;
+yy65:
+ yych = *++p;
+ if (yych == 'j') goto yy70;
+ goto yy14;
+yy66:
+ ++p;
+ if (yybm[0+(yych = *p)] & 64) {
+ goto yy13;
+ }
+ { token = DEFAULT; break; }
+yy68:
+ ++p;
+ if (yybm[0+(yych = *p)] & 64) {
+ goto yy13;
+ }
+ { token = INCLUDE; break; }
+yy70:
+ yych = *++p;
+ if (yych != 'a') goto yy14;
+ ++p;
+ if (yybm[0+(yych = *p)] & 64) {
+ goto yy13;
+ }
+ { token = SUBNINJA; break; }
}
}
@@ -487,49 +507,42 @@
0, 0, 0, 0, 0, 0, 0, 0,
};
yych = *p;
- if (yych <= ' ') {
- if (yych <= 0x00) goto yy82;
- if (yych <= 0x1F) goto yy84;
- } else {
- if (yych == '$') goto yy80;
- goto yy84;
- }
- ++p;
- yych = *p;
- goto yy92;
-yy79:
- { continue; }
-yy80:
- yych = *(q = ++p);
- if (yych == '\n') goto yy85;
- if (yych == '\r') goto yy87;
-yy81:
- { break; }
-yy82:
- ++p;
- { break; }
-yy84:
- yych = *++p;
- goto yy81;
-yy85:
- ++p;
- { continue; }
-yy87:
- yych = *++p;
- if (yych == '\n') goto yy89;
- p = q;
- goto yy81;
-yy89:
- ++p;
- { continue; }
-yy91:
- ++p;
- yych = *p;
-yy92:
if (yybm[0+yych] & 128) {
- goto yy91;
+ goto yy79;
}
- goto yy79;
+ if (yych <= 0x00) goto yy75;
+ if (yych == '$') goto yy82;
+ goto yy77;
+yy75:
+ ++p;
+ { break; }
+yy77:
+ ++p;
+yy78:
+ { break; }
+yy79:
+ ++p;
+ yych = *p;
+ if (yybm[0+yych] & 128) {
+ goto yy79;
+ }
+ { continue; }
+yy82:
+ yych = *(q = ++p);
+ if (yych == '\n') goto yy83;
+ if (yych == '\r') goto yy85;
+ goto yy78;
+yy83:
+ ++p;
+ { continue; }
+yy85:
+ yych = *++p;
+ if (yych == '\n') goto yy87;
+ p = q;
+ goto yy78;
+yy87:
+ ++p;
+ { continue; }
}
}
@@ -537,8 +550,9 @@
bool Lexer::ReadIdent(string* out) {
const char* p = ofs_;
+ const char* start;
for (;;) {
- const char* start = p;
+ start = p;
{
unsigned char yych;
@@ -577,45 +591,28 @@
0, 0, 0, 0, 0, 0, 0, 0,
};
yych = *p;
- if (yych <= '@') {
- if (yych <= '.') {
- if (yych <= ',') goto yy97;
- } else {
- if (yych <= '/') goto yy97;
- if (yych >= ':') goto yy97;
- }
- } else {
- if (yych <= '_') {
- if (yych <= 'Z') goto yy95;
- if (yych <= '^') goto yy97;
- } else {
- if (yych <= '`') goto yy97;
- if (yych >= '{') goto yy97;
- }
+ if (yybm[0+yych] & 128) {
+ goto yy93;
}
-yy95:
+ ++p;
+ {
+ last_token_ = start;
+ return false;
+ }
+yy93:
++p;
yych = *p;
- goto yy100;
-yy96:
+ if (yybm[0+yych] & 128) {
+ goto yy93;
+ }
{
out->assign(start, p - start);
break;
}
-yy97:
- ++p;
- { return false; }
-yy99:
- ++p;
- yych = *p;
-yy100:
- if (yybm[0+yych] & 128) {
- goto yy99;
- }
- goto yy96;
}
}
+ last_token_ = start;
ofs_ = p;
EatWhitespace();
return true;
@@ -631,72 +628,69 @@
{
unsigned char yych;
static const unsigned char yybm[] = {
- 0, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 0, 128, 128, 0, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 16, 128, 128, 128, 0, 128, 128, 128,
- 128, 128, 128, 128, 128, 224, 160, 128,
- 224, 224, 224, 224, 224, 224, 224, 224,
- 224, 224, 0, 128, 128, 128, 128, 128,
- 128, 224, 224, 224, 224, 224, 224, 224,
- 224, 224, 224, 224, 224, 224, 224, 224,
- 224, 224, 224, 224, 224, 224, 224, 224,
- 224, 224, 224, 128, 128, 128, 128, 224,
- 128, 224, 224, 224, 224, 224, 224, 224,
- 224, 224, 224, 224, 224, 224, 224, 224,
- 224, 224, 224, 224, 224, 224, 224, 224,
- 224, 224, 224, 128, 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, 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, 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, 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, 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, 128,
+ 0, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 0, 16, 16, 0, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 32, 16, 16, 16, 0, 16, 16, 16,
+ 16, 16, 16, 16, 16, 208, 144, 16,
+ 208, 208, 208, 208, 208, 208, 208, 208,
+ 208, 208, 0, 16, 16, 16, 16, 16,
+ 16, 208, 208, 208, 208, 208, 208, 208,
+ 208, 208, 208, 208, 208, 208, 208, 208,
+ 208, 208, 208, 208, 208, 208, 208, 208,
+ 208, 208, 208, 16, 16, 16, 16, 208,
+ 16, 208, 208, 208, 208, 208, 208, 208,
+ 208, 208, 208, 208, 208, 208, 208, 208,
+ 208, 208, 208, 208, 208, 208, 208, 208,
+ 208, 208, 208, 16, 0, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16,
};
yych = *p;
- if (yych <= ' ') {
- if (yych <= '\n') {
- if (yych <= 0x00) goto yy110;
- if (yych >= '\n') goto yy107;
- } else {
- if (yych == '\r') goto yy105;
- if (yych >= ' ') goto yy107;
- }
- } else {
- if (yych <= '9') {
- if (yych == '$') goto yy109;
- } else {
- if (yych <= ':') goto yy107;
- if (yych == '|') goto yy107;
- }
+ if (yybm[0+yych] & 16) {
+ goto yy100;
}
+ if (yych <= '\r') {
+ if (yych <= 0x00) goto yy98;
+ if (yych <= '\n') goto yy103;
+ goto yy105;
+ } else {
+ if (yych <= ' ') goto yy103;
+ if (yych <= '$') goto yy107;
+ goto yy103;
+ }
+yy98:
+ ++p;
+ {
+ last_token_ = start;
+ return Error("unexpected EOF", err);
+ }
+yy100:
++p;
yych = *p;
- goto yy140;
-yy104:
+ if (yybm[0+yych] & 16) {
+ goto yy100;
+ }
{
eval->AddText(StringPiece(start, p - start));
continue;
}
-yy105:
- ++p;
- if ((yych = *p) == '\n') goto yy137;
- {
- last_token_ = start;
- return Error(DescribeLastError(), err);
- }
-yy107:
+yy103:
++p;
{
if (path) {
@@ -709,152 +703,121 @@
continue;
}
}
-yy109:
+yy105:
+ ++p;
+ if ((yych = *p) == '\n') goto yy108;
+ {
+ last_token_ = start;
+ return Error(DescribeLastError(), err);
+ }
+yy107:
yych = *++p;
- if (yych <= '-') {
- if (yych <= 0x1F) {
- if (yych <= '\n') {
- if (yych <= '\t') goto yy112;
- goto yy124;
- } else {
- if (yych == '\r') goto yy114;
- goto yy112;
- }
+ if (yybm[0+yych] & 64) {
+ goto yy120;
+ }
+ if (yych <= ' ') {
+ if (yych <= '\f') {
+ if (yych == '\n') goto yy112;
+ goto yy110;
} else {
- if (yych <= '#') {
- if (yych <= ' ') goto yy115;
- goto yy112;
- } else {
- if (yych <= '$') goto yy117;
- if (yych <= ',') goto yy112;
- goto yy119;
- }
+ if (yych <= '\r') goto yy115;
+ if (yych <= 0x1F) goto yy110;
+ goto yy116;
}
} else {
- if (yych <= 'Z') {
- if (yych <= '9') {
- if (yych <= '/') goto yy112;
- goto yy119;
- } else {
- if (yych <= ':') goto yy121;
- if (yych <= '@') goto yy112;
- goto yy119;
- }
+ if (yych <= '/') {
+ if (yych == '$') goto yy118;
+ goto yy110;
} else {
- if (yych <= '`') {
- if (yych == '_') goto yy119;
- goto yy112;
- } else {
- if (yych <= 'z') goto yy119;
- if (yych <= '{') goto yy123;
- goto yy112;
- }
+ if (yych <= ':') goto yy123;
+ if (yych <= '`') goto yy110;
+ if (yych <= '{') goto yy125;
+ goto yy110;
}
}
-yy110:
- ++p;
- {
- last_token_ = start;
- return Error("unexpected EOF", err);
- }
-yy112:
- ++p;
-yy113:
- {
- last_token_ = start;
- return Error("bad $-escape (literal $ must be written as $$)", err);
- }
-yy114:
- yych = *++p;
- if (yych == '\n') goto yy134;
- goto yy113;
-yy115:
- ++p;
- {
- eval->AddText(StringPiece(" ", 1));
- continue;
- }
-yy117:
- ++p;
- {
- eval->AddText(StringPiece("$", 1));
- continue;
- }
-yy119:
- ++p;
- yych = *p;
- goto yy133;
-yy120:
- {
- eval->AddSpecial(StringPiece(start + 1, p - start - 1));
- continue;
- }
-yy121:
- ++p;
- {
- eval->AddText(StringPiece(":", 1));
- continue;
- }
-yy123:
- yych = *(q = ++p);
- if (yybm[0+yych] & 32) {
- goto yy127;
- }
- goto yy113;
-yy124:
- ++p;
- yych = *p;
- if (yybm[0+yych] & 16) {
- goto yy124;
- }
- {
- continue;
- }
-yy127:
- ++p;
- yych = *p;
- if (yybm[0+yych] & 32) {
- goto yy127;
- }
- if (yych == '}') goto yy130;
- p = q;
- goto yy113;
-yy130:
- ++p;
- {
- eval->AddSpecial(StringPiece(start + 2, p - start - 3));
- continue;
- }
-yy132:
- ++p;
- yych = *p;
-yy133:
- if (yybm[0+yych] & 64) {
- goto yy132;
- }
- goto yy120;
-yy134:
- ++p;
- yych = *p;
- if (yych == ' ') goto yy134;
- {
- continue;
- }
-yy137:
+yy108:
++p;
{
if (path)
p = start;
break;
}
-yy139:
+yy110:
+ ++p;
+yy111:
+ {
+ last_token_ = start;
+ return Error("bad $-escape (literal $ must be written as $$)", err);
+ }
+yy112:
++p;
yych = *p;
-yy140:
- if (yybm[0+yych] & 128) {
- goto yy139;
+ if (yybm[0+yych] & 32) {
+ goto yy112;
}
- goto yy104;
+ {
+ continue;
+ }
+yy115:
+ yych = *++p;
+ if (yych == '\n') goto yy126;
+ goto yy111;
+yy116:
+ ++p;
+ {
+ eval->AddText(StringPiece(" ", 1));
+ continue;
+ }
+yy118:
+ ++p;
+ {
+ eval->AddText(StringPiece("$", 1));
+ continue;
+ }
+yy120:
+ ++p;
+ yych = *p;
+ if (yybm[0+yych] & 64) {
+ goto yy120;
+ }
+ {
+ eval->AddSpecial(StringPiece(start + 1, p - start - 1));
+ continue;
+ }
+yy123:
+ ++p;
+ {
+ eval->AddText(StringPiece(":", 1));
+ continue;
+ }
+yy125:
+ yych = *(q = ++p);
+ if (yybm[0+yych] & 128) {
+ goto yy129;
+ }
+ goto yy111;
+yy126:
+ ++p;
+ yych = *p;
+ if (yych == ' ') goto yy126;
+ {
+ continue;
+ }
+yy129:
+ ++p;
+ yych = *p;
+ if (yybm[0+yych] & 128) {
+ goto yy129;
+ }
+ if (yych == '}') goto yy132;
+ p = q;
+ goto yy111;
+yy132:
+ ++p;
+ {
+ eval->AddSpecial(StringPiece(start + 2, p - start - 3));
+ continue;
+ }
}
}
diff --git a/src/lexer.in.cc b/src/lexer.in.cc
index f861239..c1fb822 100644
--- a/src/lexer.in.cc
+++ b/src/lexer.in.cc
@@ -22,14 +22,14 @@
bool Lexer::Error(const string& message, string* err) {
// Compute line/column.
int line = 1;
- const char* context = input_.str_;
+ const char* line_start = input_.str_;
for (const char* p = input_.str_; p < last_token_; ++p) {
if (*p == '\n') {
++line;
- context = p + 1;
+ line_start = p + 1;
}
}
- int col = last_token_ ? (int)(last_token_ - context) : 0;
+ int col = last_token_ ? (int)(last_token_ - line_start) : 0;
char buf[1024];
snprintf(buf, sizeof(buf), "%s:%d: ", filename_.AsString().c_str(), line);
@@ -42,12 +42,12 @@
int len;
bool truncated = true;
for (len = 0; len < kTruncateColumn; ++len) {
- if (context[len] == 0 || context[len] == '\n') {
+ if (line_start[len] == 0 || line_start[len] == '\n') {
truncated = false;
break;
}
}
- *err += string(context, len);
+ *err += string(line_start, len);
if (truncated)
*err += "...";
*err += "\n";
@@ -182,16 +182,21 @@
bool Lexer::ReadIdent(string* out) {
const char* p = ofs_;
+ const char* start;
for (;;) {
- const char* start = p;
+ start = p;
/*!re2c
varname {
out->assign(start, p - start);
break;
}
- [^] { return false; }
+ [^] {
+ last_token_ = start;
+ return false;
+ }
*/
}
+ last_token_ = start;
ofs_ = p;
EatWhitespace();
return true;
diff --git a/src/line_printer.cc b/src/line_printer.cc
index 2cd3e17..6effca6 100644
--- a/src/line_printer.cc
+++ b/src/line_printer.cc
@@ -41,6 +41,11 @@
CONSOLE_SCREEN_BUFFER_INFO csbi;
smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
#endif
+ supports_color_ = smart_terminal_;
+ if (!supports_color_) {
+ const char* clicolor_force = getenv("CLICOLOR_FORCE");
+ supports_color_ = clicolor_force && string(clicolor_force) != "0";
+ }
}
void LinePrinter::Print(string to_print, LineType type) {
@@ -82,7 +87,7 @@
// Limit output to width of the terminal if provided so we don't cause
// line-wrapping.
winsize size;
- if ((ioctl(0, TIOCGWINSZ, &size) == 0) && size.ws_col) {
+ if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == 0) && size.ws_col) {
to_print = ElideMiddle(to_print, size.ws_col);
}
printf("%s", to_print.c_str());
diff --git a/src/line_printer.h b/src/line_printer.h
index 55225e5..92d4dc4 100644
--- a/src/line_printer.h
+++ b/src/line_printer.h
@@ -27,6 +27,8 @@
bool is_smart_terminal() const { return smart_terminal_; }
void set_smart_terminal(bool smart) { smart_terminal_ = smart; }
+ bool supports_color() const { return supports_color_; }
+
enum LineType {
FULL,
ELIDE
@@ -46,6 +48,9 @@
/// Whether we can do fancy terminal control codes.
bool smart_terminal_;
+ /// Whether we can use ISO 6429 (ANSI) color sequences.
+ bool supports_color_;
+
/// Whether the caret is at the beginning of a blank line.
bool have_blank_line_;
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index d6dcf22..27c423b 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -26,9 +26,9 @@
#include "version.h"
ManifestParser::ManifestParser(State* state, FileReader* file_reader,
- DupeEdgeAction dupe_edge_action)
+ ManifestParserOptions options)
: state_(state), file_reader_(file_reader),
- dupe_edge_action_(dupe_edge_action), quiet_(false) {
+ options_(options), quiet_(false) {
env_ = &state->bindings_;
}
@@ -212,7 +212,7 @@
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))
@@ -342,11 +342,11 @@
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)) {
- if (dupe_edge_action_ == kDupeEdgeActionError) {
+ if (options_.dupe_edge_action_ == kDupeEdgeActionError) {
lexer_.Error("multiple rules generate " + path + " [-w dupbuild=err]",
err);
return false;
@@ -375,7 +375,7 @@
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);
@@ -383,6 +383,25 @@
edge->implicit_deps_ = implicit;
edge->order_only_deps_ = order_only;
+ if (options_.phony_cycle_action_ == kPhonyCycleActionWarn &&
+ edge->maybe_phonycycle_diagnostic()) {
+ // CMake 2.8.12.x and 3.0.x incorrectly write phony build statements
+ // that reference themselves. Ninja used to tolerate these in the
+ // build graph but that has since been fixed. Filter them out to
+ // support users of those old CMake versions.
+ Node* out = edge->outputs_[0];
+ vector<Node*>::iterator new_end =
+ remove(edge->inputs_.begin(), edge->inputs_.end(), out);
+ if (new_end != edge->inputs_.end()) {
+ edge->inputs_.erase(new_end, edge->inputs_.end());
+ if (!quiet_) {
+ Warning("phony target '%s' names itself as an input; "
+ "ignoring [-w phonycycle=warn]",
+ out->path().c_str());
+ }
+ }
+ }
+
// Multiple outputs aren't (yet?) supported with depslog.
string deps_type = edge->GetBinding("deps");
if (!deps_type.empty() && edge->outputs_.size() > 1) {
@@ -400,7 +419,7 @@
return false;
string path = eval.Evaluate(env_);
- ManifestParser subparser(state_, file_reader_, dupe_edge_action_);
+ ManifestParser subparser(state_, file_reader_, options_);
if (new_scope) {
subparser.env_ = new BindingEnv(env_);
} else {
diff --git a/src/manifest_parser.h b/src/manifest_parser.h
index 043e4b2..2136018 100644
--- a/src/manifest_parser.h
+++ b/src/manifest_parser.h
@@ -31,10 +31,23 @@
kDupeEdgeActionError,
};
+enum PhonyCycleAction {
+ kPhonyCycleActionWarn,
+ kPhonyCycleActionError,
+};
+
+struct ManifestParserOptions {
+ ManifestParserOptions()
+ : dupe_edge_action_(kDupeEdgeActionWarn),
+ phony_cycle_action_(kPhonyCycleActionWarn) {}
+ DupeEdgeAction dupe_edge_action_;
+ PhonyCycleAction phony_cycle_action_;
+};
+
/// Parses .ninja files.
struct ManifestParser {
ManifestParser(State* state, FileReader* file_reader,
- DupeEdgeAction dupe_edge_action);
+ ManifestParserOptions options = ManifestParserOptions());
/// Load and parse a file.
bool Load(const string& filename, string* err, Lexer* parent = NULL);
@@ -67,7 +80,7 @@
BindingEnv* env_;
FileReader* file_reader_;
Lexer lexer_;
- DupeEdgeAction dupe_edge_action_;
+ ManifestParserOptions options_;
bool quiet_;
};
diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc
index 60c2054..67d11f9 100644
--- a/src/manifest_parser_perftest.cc
+++ b/src/manifest_parser_perftest.cc
@@ -56,7 +56,7 @@
string err;
RealDiskInterface disk_interface;
State state;
- ManifestParser parser(&state, &disk_interface, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &disk_interface);
if (!parser.Load("build.ninja", &err)) {
fprintf(stderr, "Failed to read test data: %s\n", err.c_str());
exit(1);
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
index 3c82dc5..c91d8d1 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -23,7 +23,7 @@
struct ParserTest : public testing::Test {
void AssertParse(const char* input) {
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_TRUE(parser.ParseTest(input, &err));
ASSERT_EQ("", err);
@@ -358,7 +358,9 @@
"build out1 out2: cat in1\n"
"build out1: cat in2\n"
"build final: cat out1\n";
- ManifestParser parser(&state, &fs_, kDupeEdgeActionError);
+ ManifestParserOptions parser_opts;
+ parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
+ ManifestParser parser(&state, &fs_, parser_opts);
string err;
EXPECT_FALSE(parser.ParseTest(kInput, &err));
EXPECT_EQ("input:5: multiple rules generate out1 [-w dupbuild=err]\n", err);
@@ -373,13 +375,41 @@
"build final: cat out1\n");
const char kInput[] =
"subninja sub.ninja\n";
- ManifestParser parser(&state, &fs_, kDupeEdgeActionError);
+ ManifestParserOptions parser_opts;
+ parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
+ ManifestParser parser(&state, &fs_, parser_opts);
string err;
EXPECT_FALSE(parser.ParseTest(kInput, &err));
EXPECT_EQ("sub.ninja:5: multiple rules generate out1 [-w dupbuild=err]\n",
err);
}
+TEST_F(ParserTest, PhonySelfReferenceIgnored) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+"build a: phony a\n"
+));
+
+ Node* node = state.LookupNode("a");
+ Edge* edge = node->in_edge();
+ ASSERT_TRUE(edge->inputs_.empty());
+}
+
+TEST_F(ParserTest, PhonySelfReferenceKept) {
+ const char kInput[] =
+"build a: phony a\n";
+ ManifestParserOptions parser_opts;
+ parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
+ ManifestParser parser(&state, &fs_, parser_opts);
+ string err;
+ EXPECT_TRUE(parser.ParseTest(kInput, &err));
+ EXPECT_EQ("", err);
+
+ Node* node = state.LookupNode("a");
+ Edge* edge = node->in_edge();
+ ASSERT_EQ(edge->inputs_.size(), 1);
+ ASSERT_EQ(edge->inputs_[0], node);
+}
+
TEST_F(ParserTest, ReservedWords) {
ASSERT_NO_FATAL_FAILURE(AssertParse(
"rule build\n"
@@ -391,7 +421,7 @@
TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest(string("subn", 4), &err));
EXPECT_EQ("input:1: expected '=', got eof\n"
@@ -402,7 +432,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("foobar", &err));
EXPECT_EQ("input:1: expected '=', got eof\n"
@@ -413,7 +443,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x 3", &err));
EXPECT_EQ("input:1: expected '=', got identifier\n"
@@ -424,7 +454,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = 3", &err));
EXPECT_EQ("input:1: unexpected EOF\n"
@@ -435,7 +465,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = 3\ny 2", &err));
EXPECT_EQ("input:2: expected '=', got identifier\n"
@@ -446,7 +476,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = $", &err));
EXPECT_EQ("input:1: bad $-escape (literal $ must be written as $$)\n"
@@ -457,7 +487,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = $\n $[\n", &err));
EXPECT_EQ("input:2: bad $-escape (literal $ must be written as $$)\n"
@@ -468,7 +498,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = a$\n b$\n $\n", &err));
EXPECT_EQ("input:4: unexpected EOF\n"
@@ -477,7 +507,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("build\n", &err));
EXPECT_EQ("input:1: expected path\n"
@@ -488,29 +518,29 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("build x: y z\n", &err));
EXPECT_EQ("input:1: unknown build rule 'y'\n"
"build x: y z\n"
- " ^ near here"
+ " ^ near here"
, err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("build x:: y z\n", &err));
EXPECT_EQ("input:1: expected build command name\n"
"build x:: y z\n"
- " ^ near here"
+ " ^ near here"
, err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n command = cat ok\n"
"build x: cat $\n :\n",
@@ -523,7 +553,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n",
&err));
@@ -532,7 +562,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = echo\n"
@@ -546,7 +576,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = echo\n"
@@ -558,7 +588,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = ${fafsd\n"
@@ -573,7 +603,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = cat\n"
@@ -588,7 +618,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = cat\n"
@@ -602,16 +632,19 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule %foo\n",
&err));
- EXPECT_EQ("input:1: expected rule name\n", err);
+ EXPECT_EQ("input:1: expected rule name\n"
+ "rule %foo\n"
+ " ^ near here",
+ err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n"
" command = foo\n"
@@ -625,7 +658,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n"
"build $.: cc bar.cc\n",
@@ -638,16 +671,19 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n && bar",
&err));
- EXPECT_EQ("input:3: expected variable name\n", err);
+ EXPECT_EQ("input:3: expected variable name\n"
+ " && bar\n"
+ " ^ near here",
+ err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n"
"build $: cc bar.cc\n",
@@ -660,7 +696,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("default\n",
&err));
@@ -672,7 +708,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("default nonexistent\n",
&err));
@@ -684,7 +720,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule r\n command = r\n"
"build b: r\n"
@@ -698,7 +734,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("default $a\n", &err));
EXPECT_EQ("input:1: empty path\n"
@@ -709,7 +745,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule r\n"
" command = r\n"
@@ -721,7 +757,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
// the indented blank line must terminate the rule
// this also verifies that "unexpected (token)" errors are correct
@@ -734,15 +770,17 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool\n", &err));
- EXPECT_EQ("input:1: expected pool name\n", err);
+ EXPECT_EQ("input:1: expected pool name\n"
+ "pool\n"
+ " ^ near here", err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n", &err));
EXPECT_EQ("input:2: expected 'depth =' line\n", err);
@@ -750,7 +788,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n"
" depth = 4\n"
@@ -763,7 +801,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n"
" depth = -1\n", &err));
@@ -775,7 +813,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n"
" bar = 1\n", &err));
@@ -787,7 +825,7 @@
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
// Pool names are dereferenced at edge parsing time.
EXPECT_FALSE(parser.ParseTest("rule run\n"
@@ -800,7 +838,7 @@
TEST_F(ParserTest, MissingInput) {
State local_state;
- ManifestParser parser(&local_state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, &fs_);
string err;
EXPECT_FALSE(parser.Load("build.ninja", &err));
EXPECT_EQ("loading 'build.ninja': No such file or directory", err);
@@ -808,7 +846,7 @@
TEST_F(ParserTest, MultipleOutputs) {
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_TRUE(parser.ParseTest("rule cc\n command = foo\n depfile = bar\n"
"build a.o b.o: cc c.cc\n",
@@ -818,7 +856,7 @@
TEST_F(ParserTest, MultipleOutputsWithDeps) {
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n deps = gcc\n"
"build a.o b.o: cc c.cc\n",
@@ -853,7 +891,7 @@
}
TEST_F(ParserTest, MissingSubNinja) {
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_FALSE(parser.ParseTest("subninja foo.ninja\n", &err));
EXPECT_EQ("input:1: loading 'foo.ninja': No such file or directory\n"
@@ -866,7 +904,7 @@
// Test that rules are scoped to subninjas.
fs_.Create("test.ninja", "rule cat\n"
" command = cat\n");
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_TRUE(parser.ParseTest("rule cat\n"
" command = cat\n"
@@ -879,7 +917,7 @@
" command = cat\n");
fs_.Create("test.ninja", "include rules.ninja\n"
"build x : cat\n");
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_TRUE(parser.ParseTest("include rules.ninja\n"
"subninja test.ninja\n"
@@ -899,7 +937,7 @@
TEST_F(ParserTest, BrokenInclude) {
fs_.Create("include.ninja", "build\n");
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_FALSE(parser.ParseTest("include include.ninja\n", &err));
EXPECT_EQ("include.ninja:1: expected path\n"
@@ -974,7 +1012,7 @@
}
TEST_F(ParserTest, NoExplicitOutput) {
- ManifestParser parser(&state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, NULL);
string err;
EXPECT_TRUE(parser.ParseTest(
"rule cat\n"
@@ -1034,7 +1072,7 @@
TEST_F(ParserTest, CRLF) {
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_TRUE(parser.ParseTest("# comment with crlf\r\n", &err));
diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc
index c611919..ca93638 100644
--- a/src/minidump-win32.cc
+++ b/src/minidump-win32.cc
@@ -32,17 +32,17 @@
/// Creates a windows minidump in temp folder.
void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep) {
char temp_path[MAX_PATH];
- GetTempPath(sizeof(temp_path), temp_path);
+ GetTempPathA(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.
- DeleteFile(temp_file);
+ DeleteFileA(temp_file);
// Load DbgHelp.dll dynamically, as library is not present on all
// Windows versions.
- HMODULE dbghelp = LoadLibrary("dbghelp.dll");
+ HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
if (dbghelp == NULL) {
Error("failed to create minidump: LoadLibrary('dbghelp.dll'): %s",
GetLastErrorString().c_str());
diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc
index e37a26e..de6147a 100644
--- a/src/msvc_helper-win32.cc
+++ b/src/msvc_helper-win32.cc
@@ -43,10 +43,10 @@
security_attributes.bInheritHandle = TRUE;
// Must be inheritable so subprocesses can dup to children.
- HANDLE nul = CreateFile("NUL", GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE |
- FILE_SHARE_DELETE,
- &security_attributes, OPEN_EXISTING, 0, NULL);
+ HANDLE nul =
+ CreateFileA("NUL", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ &security_attributes, OPEN_EXISTING, 0, NULL);
if (nul == INVALID_HANDLE_VALUE)
Fatal("couldn't open nul");
@@ -58,8 +58,8 @@
Win32Fatal("SetHandleInformation");
PROCESS_INFORMATION process_info = {};
- STARTUPINFO startup_info = {};
- startup_info.cb = sizeof(STARTUPINFO);
+ STARTUPINFOA startup_info = {};
+ startup_info.cb = sizeof(STARTUPINFOA);
startup_info.hStdInput = nul;
startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
startup_info.hStdOutput = stdout_write;
diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc
index e419cd7..644b2a2 100644
--- a/src/msvc_helper_main-win32.cc
+++ b/src/msvc_helper_main-win32.cc
@@ -113,7 +113,7 @@
PushPathIntoEnvironment(env);
}
- char* command = GetCommandLine();
+ char* command = GetCommandLineA();
command = strstr(command, " -- ");
if (!command) {
Fatal("expected command line to end with \" -- command args\"");
diff --git a/src/ninja.cc b/src/ninja.cc
index 5ab73e9..1267b03 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -70,6 +70,9 @@
/// Whether duplicate rules for one target should warn or print an error.
bool dupe_edges_should_err;
+
+ /// Whether phony cycles should warn or print an error.
+ bool phony_cycle_should_err;
};
/// The Ninja main() loads up a series of data structures; various tools need
@@ -151,7 +154,7 @@
// Just checking n isn't enough: If an old output is both in the build log
// and in the deps log, it will have a Node object in state_. (It will also
// have an in edge if one of its inputs is another output that's in the deps
- // log, but having a deps edge product an output thats input to another deps
+ // log, but having a deps edge product an output that's input to another deps
// edge is rare, and the first recompaction will delete all old outputs from
// the deps log, and then a second recompaction will clear the build log,
// which seems good enough for this corner case.)
@@ -203,16 +206,16 @@
" -C DIR change to DIR before doing anything else\n"
" -f FILE specify input build file [default=build.ninja]\n"
"\n"
-" -j N run N jobs in parallel [default=%d, derived from CPUs available]\n"
-" -k N keep going until N jobs fail [default=1]\n"
+" -j N run N jobs in parallel (0 means infinity) [default=%d, derived from CPUs available]\n"
+" -k N keep going until N jobs fail (0 means infinity) [default=1]\n"
" -l N do not start new jobs if the load average is greater than N\n"
" -n dry run (don't run commands but act like they succeeded)\n"
-" -v show all command lines while building\n"
+" -v, --verbose show all command lines while building\n"
"\n"
-" -d MODE enable debugging (use -d list to list modes)\n"
-" -t TOOL run a subtool (use -t list to list subtools)\n"
+" -d MODE enable debugging (use '-d list' to list modes)\n"
+" -t TOOL run a subtool (use '-t list' to list subtools)\n"
" terminates toplevel options; further flags are passed to the tool\n"
-" -w FLAG adjust warnings (use -w list to list warnings)\n",
+" -w FLAG adjust warnings (use '-w list' to list warnings)\n",
kNinjaVersion, config.parallelism);
}
@@ -233,7 +236,7 @@
/// 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 +250,24 @@
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;
@@ -375,7 +387,12 @@
// If we get here, the browse failed.
return 1;
}
-#endif // _WIN32
+#else
+int NinjaMain::ToolBrowse(const Options*, int, char**) {
+ Fatal("browse tool not supported on this platform");
+ return 1;
+}
+#endif
#if defined(_MSC_VER)
int NinjaMain::ToolMSVC(const Options* options, int argc, char* argv[]) {
@@ -482,7 +499,7 @@
TimeStamp mtime = disk_interface.Stat((*it)->path(), &err);
if (mtime == -1)
Error("%s", err.c_str()); // Log and ignore Stat() errors;
- printf("%s: #deps %d, deps mtime %d (%s)\n",
+ printf("%s: #deps %d, deps mtime %" PRId64 " (%s)\n",
(*it)->path().c_str(), deps->node_count, deps->mtime,
(!mtime || mtime > deps->mtime ? "STALE":"VALID"));
for (int i = 0; i < deps->node_count; ++i)
@@ -650,7 +667,65 @@
}
}
-int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, char* argv[]) {
+enum EvaluateCommandMode {
+ ECM_NORMAL,
+ ECM_EXPAND_RSPFILE
+};
+string EvaluateCommandWithRspfile(Edge* edge, EvaluateCommandMode mode) {
+ string command = edge->EvaluateCommand();
+ if (mode == ECM_NORMAL)
+ return command;
+
+ string rspfile = edge->GetUnescapedRspfile();
+ if (rspfile.empty())
+ return command;
+
+ size_t index = command.find(rspfile);
+ if (index == 0 || index == string::npos || command[index - 1] != '@')
+ return command;
+
+ string rspfile_content = edge->GetBinding("rspfile_content");
+ size_t newline_index = 0;
+ while ((newline_index = rspfile_content.find('\n', newline_index)) !=
+ string::npos) {
+ rspfile_content.replace(newline_index, 1, 1, ' ');
+ ++newline_index;
+ }
+ command.replace(index - 1, rspfile.length() + 1, rspfile_content);
+ return command;
+}
+
+int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
+ char* argv[]) {
+ // The compdb tool uses getopt, and expects argv[0] to contain the name of
+ // the tool, i.e. "compdb".
+ argc++;
+ argv--;
+
+ EvaluateCommandMode eval_mode = ECM_NORMAL;
+
+ optind = 1;
+ int opt;
+ while ((opt = getopt(argc, argv, const_cast<char*>("hx"))) != -1) {
+ switch(opt) {
+ case 'x':
+ eval_mode = ECM_EXPAND_RSPFILE;
+ break;
+
+ case 'h':
+ default:
+ printf(
+ "usage: ninja -t compdb [options] [rules]\n"
+ "\n"
+ "options:\n"
+ " -x expand @rspfile style response file invocations\n"
+ );
+ return 1;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
bool first = true;
vector<char> cwd;
@@ -676,9 +751,11 @@
printf("\n {\n \"directory\": \"");
EncodeJSONString(&cwd[0]);
printf("\",\n \"command\": \"");
- EncodeJSONString((*e)->EvaluateCommand().c_str());
+ EncodeJSONString(EvaluateCommandWithRspfile(*e, eval_mode).c_str());
printf("\",\n \"file\": \"");
EncodeJSONString((*e)->inputs_[0]->path().c_str());
+ printf("\",\n \"output\": \"");
+ EncodeJSONString((*e)->outputs_[0]->path().c_str());
printf("\"\n }");
first = false;
@@ -731,10 +808,8 @@
/// Returns a Tool, or NULL if Ninja should exit.
const Tool* ChooseTool(const string& tool_name) {
static const Tool kTools[] = {
-#if defined(NINJA_HAVE_BROWSE)
{ "browse", "browse dependency graph in a web browser",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse },
-#endif
#if defined(_MSC_VER)
{ "msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)",
Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolMSVC },
@@ -836,7 +911,8 @@
bool WarningEnable(const string& name, Options* options) {
if (name == "list") {
printf("warning flags:\n"
-" dupbuild={err,warn} multiple build lines for one target\n");
+" dupbuild={err,warn} multiple build lines for one target\n"
+" phonycycle={err,warn} phony build statement references itself\n");
return false;
} else if (name == "dupbuild=err") {
options->dupe_edges_should_err = true;
@@ -844,9 +920,16 @@
} else if (name == "dupbuild=warn") {
options->dupe_edges_should_err = false;
return true;
+ } else if (name == "phonycycle=err") {
+ options->phony_cycle_should_err = true;
+ return true;
+ } else if (name == "phonycycle=warn") {
+ options->phony_cycle_should_err = false;
+ return true;
} else {
const char* suggestion =
- SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn", NULL);
+ SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn",
+ "phonycycle=err", "phonycycle=warn", NULL);
if (suggestion) {
Error("unknown warning flag '%s', did you mean '%s'?",
name.c_str(), suggestion);
@@ -1022,6 +1105,7 @@
const option kLongOptions[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, OPT_VERSION },
+ { "verbose", no_argument, NULL, 'v' },
{ NULL, 0, NULL, 0 }
};
@@ -1040,9 +1124,12 @@
case 'j': {
char* end;
int value = strtol(optarg, &end, 10);
- if (*end != 0 || value <= 0)
+ if (*end != 0 || value < 0)
Fatal("invalid -j parameter");
- config->parallelism = value;
+
+ // We want to run N jobs in parallel. For N = 0, INT_MAX
+ // is close enough to infinite for most sane builds.
+ config->parallelism = value > 0 ? value : INT_MAX;
break;
}
case 'k': {
@@ -1098,17 +1185,20 @@
return -1;
}
-int real_main(int argc, char** argv) {
+NORETURN void real_main(int argc, char** argv) {
+ // Use exit() instead of return in this function to avoid potentially
+ // expensive cleanup when destructing NinjaMain.
BuildConfig config;
Options options = {};
options.input_file = "build.ninja";
+ options.dupe_edges_should_err = true;
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
const char* ninja_command = argv[0];
int exit_code = ReadFlags(&argc, &argv, &options, &config);
if (exit_code >= 0)
- return exit_code;
+ exit(exit_code);
if (options.working_dir) {
// The formatting of this string, complete with funny quotes, is
@@ -1127,7 +1217,7 @@
// None of the RUN_AFTER_FLAGS actually use a NinjaMain, but it's needed
// by other tools.
NinjaMain ninja(ninja_command, config);
- return (ninja.*options.tool->func)(&options, argc, argv);
+ exit((ninja.*options.tool->func)(&options, argc, argv));
}
// Limit number of rebuilds, to prevent infinite loops.
@@ -1135,50 +1225,54 @@
for (int cycle = 1; cycle <= kCycleLimit; ++cycle) {
NinjaMain ninja(ninja_command, config);
- ManifestParser parser(&ninja.state_, &ninja.disk_interface_,
- options.dupe_edges_should_err
- ? kDupeEdgeActionError
- : kDupeEdgeActionWarn);
+ ManifestParserOptions parser_opts;
+ if (options.dupe_edges_should_err) {
+ parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
+ }
+ if (options.phony_cycle_should_err) {
+ parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
+ }
+ ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts);
string err;
if (!parser.Load(options.input_file, &err)) {
Error("%s", err.c_str());
- return 1;
+ exit(1);
}
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD)
- return (ninja.*options.tool->func)(&options, argc, argv);
+ exit((ninja.*options.tool->func)(&options, argc, argv));
if (!ninja.EnsureBuildDirExists())
- return 1;
+ exit(1);
if (!ninja.OpenBuildLog() || !ninja.OpenDepsLog())
- return 1;
+ exit(1);
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS)
- return (ninja.*options.tool->func)(&options, argc, argv);
+ exit((ninja.*options.tool->func)(&options, argc, argv));
// Attempt to rebuild the manifest before building anything else
if (ninja.RebuildManifest(options.input_file, &err)) {
// In dry_run mode the regeneration will succeed without changing the
// manifest forever. Better to return immediately.
if (config.dry_run)
- return 0;
+ exit(0);
// Start the build over with the new manifest.
continue;
} else if (!err.empty()) {
Error("rebuilding '%s': %s", options.input_file, err.c_str());
- return 1;
+ exit(1);
}
int result = ninja.RunBuild(argc, argv);
if (g_metrics)
ninja.DumpMetrics();
- return result;
+ exit(result);
}
Error("manifest '%s' still dirty after %d tries\n",
options.input_file, kCycleLimit);
- return 1;
+ exit(1);
}
} // anonymous namespace
@@ -1191,7 +1285,7 @@
__try {
// Running inside __try ... __except suppresses any Windows error
// dialogs for errors such as bad_alloc.
- return real_main(argc, argv);
+ real_main(argc, argv);
}
__except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
// Common error situations return exitCode=1. 2 was chosen to
@@ -1199,6 +1293,6 @@
return 2;
}
#else
- return real_main(argc, argv);
+ real_main(argc, argv);
#endif
}
diff --git a/src/state.cc b/src/state.cc
index d539e7b..9b3c7e1 100644
--- a/src/state.cc
+++ b/src/state.cc
@@ -100,7 +100,7 @@
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 @@
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;
@@ -184,8 +184,10 @@
void State::Reset() {
for (Paths::iterator i = paths_.begin(); i != paths_.end(); ++i)
i->second->ResetState();
- for (vector<Edge*>::iterator e = edges_.begin(); e != edges_.end(); ++e)
+ for (vector<Edge*>::iterator e = edges_.begin(); e != edges_.end(); ++e) {
(*e)->outputs_ready_ = false;
+ (*e)->mark_ = Edge::VisitNone;
+ }
}
void State::Dump() {
diff --git a/src/state.h b/src/state.h
index b530207..6fe886c 100644
--- a/src/state.h
+++ b/src/state.h
@@ -23,6 +23,7 @@
#include "eval_env.h"
#include "hash_map.h"
+#include "util.h"
struct Edge;
struct Node;
@@ -32,7 +33,7 @@
/// Pools are scoped to a State. Edges within a State will share Pools. A Pool
/// will keep a count of the total 'weight' of the currently scheduled edges. If
/// a Plan attempts to schedule an Edge which would cause the total weight to
-/// exceed the depth of the Pool, the Pool will enque the Edge instead of
+/// exceed the depth of the Pool, the Pool will enqueue the Edge instead of
/// allowing the Plan to schedule it. The Pool will relinquish queued Edges when
/// the total scheduled weight diminishes enough (i.e. when a scheduled edge
/// completes).
@@ -93,12 +94,12 @@
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
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 @@
/// 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 @@
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..fc5543e 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -14,6 +14,7 @@
#include "subprocess.h"
+#include <sys/select.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
@@ -54,58 +55,73 @@
SetCloseOnExec(fd_);
posix_spawn_file_actions_t action;
- if (posix_spawn_file_actions_init(&action) != 0)
- Fatal("posix_spawn_file_actions_init: %s", strerror(errno));
+ int err = posix_spawn_file_actions_init(&action);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_init: %s", strerror(err));
- if (posix_spawn_file_actions_addclose(&action, output_pipe[0]) != 0)
- Fatal("posix_spawn_file_actions_addclose: %s", strerror(errno));
+ err = posix_spawn_file_actions_addclose(&action, output_pipe[0]);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
posix_spawnattr_t attr;
- if (posix_spawnattr_init(&attr) != 0)
- Fatal("posix_spawnattr_init: %s", strerror(errno));
+ err = posix_spawnattr_init(&attr);
+ if (err != 0)
+ Fatal("posix_spawnattr_init: %s", strerror(err));
short flags = 0;
flags |= POSIX_SPAWN_SETSIGMASK;
- if (posix_spawnattr_setsigmask(&attr, &set->old_mask_) != 0)
- Fatal("posix_spawnattr_setsigmask: %s", strerror(errno));
+ err = posix_spawnattr_setsigmask(&attr, &set->old_mask_);
+ if (err != 0)
+ Fatal("posix_spawnattr_setsigmask: %s", strerror(err));
// Signals which are set to be caught in the calling process image are set to
// 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;
// No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
// Open /dev/null over stdin.
- if (posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
- 0) != 0) {
- Fatal("posix_spawn_file_actions_addopen: %s", strerror(errno));
+ err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
+ 0);
+ if (err != 0) {
+ Fatal("posix_spawn_file_actions_addopen: %s", strerror(err));
}
- if (posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1) != 0)
- 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));
+ err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
+ err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
+ err = posix_spawn_file_actions_addclose(&action, output_pipe[1]);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
// 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));
+ err = posix_spawnattr_setflags(&attr, flags);
+ if (err != 0)
+ Fatal("posix_spawnattr_setflags: %s", strerror(err));
const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
- if (posix_spawn(&pid_, "/bin/sh", &action, &attr,
- const_cast<char**>(spawned_args), environ) != 0)
- Fatal("posix_spawn: %s", strerror(errno));
+ err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
+ const_cast<char**>(spawned_args), environ);
+ if (err != 0)
+ Fatal("posix_spawn: %s", strerror(err));
- if (posix_spawnattr_destroy(&attr) != 0)
- Fatal("posix_spawnattr_destroy: %s", strerror(errno));
- if (posix_spawn_file_actions_destroy(&action) != 0)
- Fatal("posix_spawn_file_actions_destroy: %s", strerror(errno));
+ err = posix_spawnattr_destroy(&attr);
+ if (err != 0)
+ Fatal("posix_spawnattr_destroy: %s", strerror(err));
+ err = posix_spawn_file_actions_destroy(&action);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_destroy: %s", strerror(err));
close(output_pipe[1]);
return true;
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index 4bab719..5982b06 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -59,8 +59,8 @@
}
// Get the write end of the pipe as a handle inheritable across processes.
- HANDLE output_write_handle = CreateFile(pipe_name, GENERIC_WRITE, 0,
- NULL, OPEN_EXISTING, 0, NULL);
+ HANDLE output_write_handle =
+ CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
HANDLE output_write_child;
if (!DuplicateHandle(GetCurrentProcess(), output_write_handle,
GetCurrentProcess(), &output_write_child,
@@ -80,9 +80,10 @@
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = TRUE;
// Must be inheritable so subprocesses can dup to children.
- HANDLE nul = CreateFile("NUL", GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- &security_attributes, OPEN_EXISTING, 0, NULL);
+ HANDLE nul =
+ CreateFileA("NUL", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ &security_attributes, OPEN_EXISTING, 0, NULL);
if (nul == INVALID_HANDLE_VALUE)
Fatal("couldn't open nul");
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index 0a8c206..6e487db 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -182,7 +182,7 @@
"cmd /c echo hi",
"cmd /c time /t",
#else
- "whoami",
+ "id -u",
"pwd",
#endif
};
diff --git a/src/test.cc b/src/test.cc
index 51882f0..a9816bc 100644
--- a/src/test.cc
+++ b/src/test.cc
@@ -95,8 +95,9 @@
return state_.GetNode(path, 0);
}
-void AssertParse(State* state, const char* input) {
- ManifestParser parser(state, NULL, kDupeEdgeActionWarn);
+void AssertParse(State* state, const char* input,
+ ManifestParserOptions opts) {
+ ManifestParser parser(state, NULL, opts);
string err;
EXPECT_TRUE(parser.ParseTest(input, &err));
ASSERT_EQ("", err);
diff --git a/src/test.h b/src/test.h
index 02ed929..6af17b3 100644
--- a/src/test.h
+++ b/src/test.h
@@ -16,6 +16,7 @@
#define NINJA_TEST_H_
#include "disk_interface.h"
+#include "manifest_parser.h"
#include "state.h"
#include "util.h"
@@ -103,7 +104,7 @@
} \
}
-// Support utilites for tests.
+// Support utilities for tests.
struct Node;
@@ -122,7 +123,8 @@
State state_;
};
-void AssertParse(State* state, const char* input);
+void AssertParse(State* state, const char* input,
+ ManifestParserOptions = ManifestParserOptions());
void AssertHash(const char* expected, uint64_t actual);
void VerifyGraph(const State& state);
diff --git a/src/timestamp.h b/src/timestamp.h
index cee7ba8..6a7ccd0 100644
--- a/src/timestamp.h
+++ b/src/timestamp.h
@@ -15,10 +15,19 @@
#ifndef NINJA_TIMESTAMP_H_
#define NINJA_TIMESTAMP_H_
+#ifdef _WIN32
+#include "win32port.h"
+#else
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+#include <inttypes.h>
+#endif
+
// When considering file modification times we only care to compare
// them against one another -- we never convert them to an absolute
-// real time. On POSIX we use time_t (seconds since epoch) and on
-// Windows we use a different value. Both fit in an int.
-typedef int TimeStamp;
+// real time. On POSIX we use timespec (seconds&nanoseconds since epoch)
+// and on Windows we use a different value. Both fit in an int64.
+typedef int64_t TimeStamp;
#endif // NINJA_TIMESTAMP_H_
diff --git a/src/util.cc b/src/util.cc
index de280cd..e793a92 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -90,7 +90,7 @@
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;
@@ -102,20 +102,15 @@
return true;
}
+static bool IsPathSeparator(char c) {
#ifdef _WIN32
-static unsigned int ShiftOverBit(int offset, unsigned int 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);
- return (above >> 1) | below;
-}
+ return c == '/' || c == '\\';
+#else
+ return c == '/';
#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 +120,7 @@
return false;
}
- const int kMaxPathComponents = 30;
+ const int kMaxPathComponents = 60;
char* components[kMaxPathComponents];
int component_count = 0;
@@ -134,37 +129,13 @@
const char* src = start;
const char* end = start + *len;
+ if (IsPathSeparator(*src)) {
#ifdef _WIN32
- unsigned int bits = 0;
- unsigned int bits_mask = 1;
- int bits_offset = 0;
- // Convert \ to /, setting a bit in |bits| for each \ encountered.
- for (char* c = path; c < end; ++c) {
- switch (*c) {
- case '\\':
- bits |= bits_mask;
- *c = '/';
- // Intentional fallthrough.
- case '/':
- bits_mask <<= 1;
- bits_offset++;
- }
- }
- if (bits_offset > 32) {
- *err = "too many path components";
- return false;
- }
- bits_offset = 0;
-#endif
- if (*src == '/') {
-#ifdef _WIN32
- bits_offset++;
// network path starts with //
- if (*len > 1 && *(src + 1) == '/') {
+ if (*len > 1 && IsPathSeparator(*(src + 1))) {
src += 2;
dst += 2;
- bits_offset++;
} else {
++src;
++dst;
@@ -177,24 +148,16 @@
while (src < end) {
if (*src == '.') {
- if (src + 1 == end || src[1] == '/') {
+ if (src + 1 == end || IsPathSeparator(src[1])) {
// '.' component; eliminate.
src += 2;
-#ifdef _WIN32
- bits = ShiftOverBit(bits_offset, bits);
-#endif
continue;
- } else if (src[1] == '.' && (src + 2 == end || src[2] == '/')) {
+ } else if (src[1] == '.' && (src + 2 == end || IsPathSeparator(src[2]))) {
// '..' component. Back up if possible.
if (component_count > 0) {
dst = components[component_count - 1];
src += 3;
--component_count;
-#ifdef _WIN32
- bits = ShiftOverBit(bits_offset, bits);
- bits_offset--;
- bits = ShiftOverBit(bits_offset, bits);
-#endif
} else {
*dst++ = *src++;
*dst++ = *src++;
@@ -204,11 +167,8 @@
}
}
- if (*src == '/') {
+ if (IsPathSeparator(*src)) {
src++;
-#ifdef _WIN32
- bits = ShiftOverBit(bits_offset, bits);
-#endif
continue;
}
@@ -217,11 +177,8 @@
components[component_count] = dst;
++component_count;
- while (*src != '/' && src != end)
+ while (src != end && !IsPathSeparator(*src))
*dst++ = *src++;
-#ifdef _WIN32
- bits_offset++;
-#endif
*dst++ = *src++; // Copy '/' or final \0 character as well.
}
@@ -232,6 +189,20 @@
*len = dst - start - 1;
#ifdef _WIN32
+ uint64_t bits = 0;
+ uint64_t bits_mask = 1;
+
+ for (char* c = start; c < start + *len; ++c) {
+ switch (*c) {
+ case '\\':
+ bits |= bits_mask;
+ *c = '/';
+ NINJA_FALLTHROUGH;
+ case '/':
+ bits_mask <<= 1;
+ }
+ }
+
*slash_bits = bits;
#else
*slash_bits = 0;
@@ -347,13 +318,8 @@
// This makes a ninja run on a set of 1500 manifest files about 4% faster
// than using the generic fopen code below.
err->clear();
- HANDLE f = ::CreateFile(path.c_str(),
- GENERIC_READ,
- FILE_SHARE_READ,
- NULL,
- OPEN_EXISTING,
- FILE_FLAG_SEQUENTIAL_SCAN,
- NULL);
+ HANDLE f = ::CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (f == INVALID_HANDLE_VALUE) {
err->assign(GetLastErrorString());
return -ENOENT;
@@ -481,7 +447,7 @@
}
#endif
-static bool islatinalpha(int c) {
+bool islatinalpha(int c) {
// isalpha() is locale-dependent.
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
@@ -595,6 +561,13 @@
// 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 };
@@ -610,7 +583,7 @@
string ElideMiddle(const string& str, size_t width) {
const int kMargin = 3; // Space for "...".
string result = str;
- if (result.size() + kMargin > width) {
+ if (result.size() > width) {
size_t elide_size = (width - kMargin) / 2;
result = result.substr(0, elide_size)
+ "..."
diff --git a/src/util.h b/src/util.h
index cbdc1a6..1b4227c 100644
--- a/src/util.h
+++ b/src/util.h
@@ -34,6 +34,20 @@
/// Log a fatal message and exit.
NORETURN void Fatal(const char* msg, ...);
+// Have a generic fall-through for different versions of C/C++.
+#if defined(__cplusplus) && __cplusplus >= 201703L
+#define NINJA_FALLTHROUGH [[fallthrough]]
+#elif defined(__cplusplus) && __cplusplus >= 201103L && defined(__clang__)
+#define NINJA_FALLTHROUGH [[clang::fallthrough]]
+#elif defined(__cplusplus) && __cplusplus >= 201103L && defined(__GNUC__) && \
+ __GNUC__ >= 7
+#define NINJA_FALLTHROUGH [[gnu::fallthrough]]
+#elif defined(__GNUC__) && __GNUC__ >= 7 // gcc 7
+#define NINJA_FALLTHROUGH __attribute__ ((fallthrough))
+#else // C++11 on gcc 6, and all other cases
+#define NINJA_FALLTHROUGH
+#endif
+
/// Log a warning message.
void Warning(const char* msg, ...);
@@ -43,8 +57,8 @@
/// 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 +84,8 @@
/// 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..d97b48c 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, 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, 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,33 +274,60 @@
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";
- EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err));
- EXPECT_EQ(slash_bits, 0xffff);
+ "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";
- // 33 is not.
+ EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err));
+ EXPECT_EQ(slash_bits, 0xffffffff);
+
+ // 65 is OK if #component is less than 60 after path canonicalization.
err = "";
- path =
- "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/x.h";
- EXPECT_FALSE(CanonicalizePath(&path, &slash_bits, &err));
- EXPECT_EQ(err, "too many path components");
+ 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_TRUE(CanonicalizePath(&path, &slash_bits, &err));
+ EXPECT_EQ(slash_bits, 0x0);
// Backslashes version.
err = "";
path =
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\."
- "\\a\\.\\a\\.\\a\\.\\a\\.\\a\\x.h";
- EXPECT_FALSE(CanonicalizePath(&path, &slash_bits, &err));
- EXPECT_EQ(err, "too many path components");
+ "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_TRUE(CanonicalizePath(&path, &slash_bits, &err));
+ EXPECT_EQ(slash_bits, 0x1ffffffff);
+
+
+ // 59 after canonicalization is OK.
+ err = "";
+ 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/"
+ "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_EQ(58, std::count(path.begin(), path.end(), '/'));
+ EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err));
+ EXPECT_EQ(slash_bits, 0x0);
+
+ // Backslashes version.
+ err = "";
+ 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\\"
+ "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_EQ(58, std::count(path.begin(), path.end(), '\\'));
+ EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err));
+ EXPECT_EQ(slash_bits, 0x3ffffffffffffff);
}
#endif
@@ -326,7 +353,7 @@
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.
@@ -392,10 +419,12 @@
TEST(ElideMiddle, NothingToElide) {
string input = "Nothing to elide in this short string.";
EXPECT_EQ(input, ElideMiddle(input, 80));
+ EXPECT_EQ(input, ElideMiddle(input, 38));
}
TEST(ElideMiddle, ElideInTheMiddle) {
string input = "01234567890123456789";
string elided = ElideMiddle(input, 10);
EXPECT_EQ("012...789", elided);
+ EXPECT_EQ("01234567...23456789", ElideMiddle(input, 19));
}
diff --git a/src/version.cc b/src/version.cc
index e2a83bb..1b6cfac 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -18,7 +18,7 @@
#include "util.h"
-const char* kNinjaVersion = "1.7.2.git";
+const char* kNinjaVersion = "1.8.2.git";
void ParseVersion(const string& version, int* major, int* minor) {
size_t end = version.find('.');
diff --git a/src/win32port.h b/src/win32port.h
index ce3c949..e542536 100644
--- a/src/win32port.h
+++ b/src/win32port.h
@@ -15,6 +15,13 @@
#ifndef NINJA_WIN32PORT_H_
#define NINJA_WIN32PORT_H_
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+#include <inttypes.h>
+#endif
+
typedef signed short int16_t;
typedef unsigned short uint16_t;
/// A 64-bit integer type
@@ -23,6 +30,7 @@
// printf format specifier for uint64_t, from C99.
#ifndef PRIu64
+#define PRId64 "I64d"
#define PRIu64 "I64u"
#define PRIx64 "I64x"
#endif