diff --git a/.travis.yml b/.travis.yml index d7bee6f54f..544db6fe83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,4 @@ language: cpp compiler: - gcc - clang -before_install: - - sudo apt-get update -qq - - sudo apt-get install libgtest-dev -script: ./bootstrap.py && ./configure.py --with-gtest=/usr/src/gtest && ./ninja ninja_test && ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots +script: ./configure.py --bootstrap && ./ninja ninja_test && ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots && ./misc/ninja_syntax_test.py diff --git a/HACKING.md b/HACKING.md index 8e1696ac7c..059a4244a6 100644 --- a/HACKING.md +++ b/HACKING.md @@ -7,10 +7,6 @@ The primary build target of interest is `ninja`, but when hacking on Ninja your changes should be testable so it's more useful to build and run `ninja_test` when developing. -(`./bootstrap.py` creates a bootstrap `ninja` and runs the above -process; it's only necessary to run if you don't have a copy of -`ninja` to build with.) - ### Adjusting build flags Build in "debug" mode while developing (disables optimizations and builds @@ -50,26 +46,6 @@ patch. ## Testing -### Installing gtest - -The `ninja_test` binary, containing all the tests, depends on the -googletest (gtest) library. - -* On older Ubuntus it'll install as libraries into `/usr/lib`: - - apt-get install libgtest - -* On newer Ubuntus it's only distributed as source - - apt-get install libgtest-dev - ./configure.py --with-gtest=/usr/src/gtest - -* Otherwise you need to download it, unpack it, and pass - `--with-gtest` to `configure.py`. Get it from [its downloads - page](http://code.google.com/p/googletest/downloads/list); [this - direct download link might work - too](http://googletest.googlecode.com/files/gtest-1.6.0.zip). - ### Test-driven development Set your build command to @@ -146,14 +122,15 @@ it's locked while in use. * Install Visual Studio (Express is fine), [Python for Windows][], and (if making changes) googletest (see above instructions) -* In a Visual Studio command prompt: `python bootstrap.py` +* In a Visual Studio command prompt: `python configure.py --bootstrap` [Python for Windows]: http://www.python.org/getit/windows/ ### Via mingw on Windows (not well supported) * Install mingw, msys, and python -* In the mingw shell, put Python in your path, and `python bootstrap.py` +* In the mingw shell, put Python in your path, and + `python configure.py --bootstrap` * To reconfigure, run `python configure.py` * Remember to strip the resulting executable if size matters to you @@ -167,6 +144,12 @@ Setup on Ubuntu Precise: * `sudo apt-get install gcc-mingw-w64-i686 g++-mingw-w64-i686 wine` * `export CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ AR=i686-w64-mingw32-ar` +Setup on Arch: +* Uncomment the `[multilib]` section of `/etc/pacman.conf` and `sudo pacman -Sy`. +* `sudo pacman -S mingw-w64-gcc wine` +* `export CC=x86_64-w64-mingw32-cc CXX=x86_64-w64-mingw32-c++ AR=x86_64-w64-mingw32-ar` +* `export CFLAGS=-I/usr/x86_64-w64-mingw32/include` + Then run: * `./configure.py --platform=mingw --host=linux` * Build `ninja.exe` using a Linux ninja binary: `/path/to/linux/ninja` diff --git a/README b/README index 733ccb39b5..41ecddaa2d 100644 --- a/README +++ b/README @@ -5,12 +5,16 @@ See the manual -- http://martine.github.com/ninja/manual.html or doc/manual.asciidoc included in the distribution -- for background and more details. -To build, run ./bootstrap.py. It first blindly compiles all non-test +To build, run ./configure.py --bootstrap. It first compiles all non-test source files together, then re-builds Ninja using itself. You should -end up with a 'ninja' binary in the source root. Run './ninja -h' for -help. +end up with a 'ninja' binary in the source root. -There is no installation step. The only file of interest to a user -is the resulting ninja binary. +Run './configure.py --help' for more configuration options. +Run './ninja -h' for Ninja help. + +Installation is not necessary because the only required file is is the +resulting ninja binary. However, to enable features like Bash +completion and Emacs and Vim editing modes, some files in misc/ must be +copied to appropriate locations. If you're interested in making changes to Ninja, read HACKING.md first. diff --git a/bootstrap.py b/bootstrap.py index 026396b5c3..56eab64d18 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -15,152 +15,9 @@ from __future__ import print_function -from optparse import OptionParser -import sys -import os -import glob -import errno -import shlex -import shutil import subprocess -import platform_helper - -os.chdir(os.path.dirname(os.path.abspath(__file__))) - -parser = OptionParser() - -parser.add_option('--verbose', action='store_true', - help='enable verbose build',) -parser.add_option('--x64', action='store_true', - help='force 64-bit build (Windows)',) -parser.add_option('--platform', - help='target platform (' + - '/'.join(platform_helper.platforms()) + ')', - choices=platform_helper.platforms()) -parser.add_option('--force-pselect', action='store_true', - help='ppoll() is used by default where available, ' - 'but some platforms might need to use pselect instead',) -(options, conf_args) = parser.parse_args() - - -platform = platform_helper.Platform(options.platform) -conf_args.append("--platform=" + platform.platform()) - -def run(*args, **kwargs): - returncode = subprocess.call(*args, **kwargs) - if returncode != 0: - sys.exit(returncode) - -# Compute system-specific CFLAGS/LDFLAGS as used in both in the below -# g++ call as well as in the later configure.py. -cflags = os.environ.get('CFLAGS', '').split() -ldflags = os.environ.get('LDFLAGS', '').split() -if platform.is_freebsd() or platform.is_openbsd() or platform.is_bitrig(): - cflags.append('-I/usr/local/include') - ldflags.append('-L/usr/local/lib') - -print('Building ninja manually...') - -try: - os.mkdir('build') -except OSError: - e = sys.exc_info()[1] - if e.errno != errno.EEXIST: - raise - -sources = [] -for src in glob.glob('src/*.cc'): - if src.endswith('test.cc') or src.endswith('.in.cc'): - continue - if src.endswith('bench.cc'): - continue - - filename = os.path.basename(src) - if filename == 'browse.cc': # Depends on generated header. - continue - - if platform.is_windows(): - if src.endswith('-posix.cc'): - continue - else: - if src.endswith('-win32.cc'): - continue - - sources.append(src) - -if platform.is_windows(): - sources.append('src/getopt.c') - -if platform.is_msvc(): - cl = 'cl' - vcdir = os.environ.get('VCINSTALLDIR') - if vcdir: - if options.x64: - cl = os.path.join(vcdir, 'bin', 'x86_amd64', 'cl.exe') - if not os.path.exists(cl): - cl = os.path.join(vcdir, 'bin', 'amd64', 'cl.exe') - else: - cl = os.path.join(vcdir, 'bin', 'cl.exe') - args = [cl, '/nologo', '/EHsc', '/DNOMINMAX'] -else: - args = shlex.split(os.environ.get('CXX', 'g++')) - cflags.extend(['-Wno-deprecated', - '-DNINJA_PYTHON="' + sys.executable + '"', - '-DNINJA_BOOTSTRAP']) - if platform.is_windows(): - cflags.append('-D_WIN32_WINNT=0x0501') - if options.x64: - cflags.append('-m64') -if (platform.is_linux() or platform.is_openbsd() or platform.is_bitrig()) and \ - not options.force_pselect: - cflags.append('-DUSE_PPOLL') -if options.force_pselect: - conf_args.append("--force-pselect") -args.extend(cflags) -args.extend(ldflags) -binary = 'ninja.bootstrap' -if platform.is_windows(): - binary = 'ninja.bootstrap.exe' -args.extend(sources) -if platform.is_msvc(): - args.extend(['/link', '/out:' + binary]) -else: - args.extend(['-o', binary]) - -if options.verbose: - print(' '.join(args)) - -try: - run(args) -except: - print('Failure running:', args) - raise - -verbose = [] -if options.verbose: - verbose = ['-v'] - -if platform.is_windows(): - print('Building ninja using itself...') - run([sys.executable, 'configure.py'] + conf_args) - run(['./' + binary] + verbose) - - # Copy the new executable over the bootstrap one. - shutil.copyfile('ninja.exe', binary) - - # Clean up. - for obj in glob.glob('*.obj'): - os.unlink(obj) - - print(""" -Done! +import sys -Note: to work around Windows file locking, where you can't rebuild an -in-use binary, to run ninja after making any changes to build ninja -itself you should run ninja.bootstrap instead.""") -else: - print('Building ninja using itself...') - run([sys.executable, 'configure.py'] + conf_args) - run(['./' + binary] + verbose) - os.unlink(binary) - print('Done!') +print('DEPRECATED: this script will be deleted.') +print('use "configure.py --bootstrap" instead.') +subprocess.check_call([sys.executable, 'configure.py', '--bootstrap']) diff --git a/configure.py b/configure.py index 64123a0af7..aac4ad4f0a 100755 --- a/configure.py +++ b/configure.py @@ -23,29 +23,170 @@ from optparse import OptionParser import os +import pipes +import string +import subprocess import sys -import platform_helper -sys.path.insert(0, 'misc') +sys.path.insert(0, 'misc') import ninja_syntax + +class Platform(object): + """Represents a host/target platform and its specific build attributes.""" + def __init__(self, platform): + self._platform = platform + if self._platform is not None: + return + self._platform = sys.platform + if self._platform.startswith('linux'): + self._platform = 'linux' + elif self._platform.startswith('freebsd'): + self._platform = 'freebsd' + elif self._platform.startswith('gnukfreebsd'): + self._platform = 'freebsd' + elif self._platform.startswith('openbsd'): + self._platform = 'openbsd' + elif self._platform.startswith('solaris') or self._platform == 'sunos5': + self._platform = 'solaris' + elif self._platform.startswith('mingw'): + self._platform = 'mingw' + elif self._platform.startswith('win'): + self._platform = 'msvc' + elif self._platform.startswith('bitrig'): + self._platform = 'bitrig' + elif self._platform.startswith('netbsd'): + self._platform = 'netbsd' + + @staticmethod + def known_platforms(): + return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5', + 'mingw', 'msvc', 'gnukfreebsd', 'bitrig', 'netbsd'] + + def platform(self): + return self._platform + + def is_linux(self): + return self._platform == 'linux' + + def is_mingw(self): + return self._platform == 'mingw' + + def is_msvc(self): + return self._platform == 'msvc' + + def msvc_needs_fs(self): + popen = subprocess.Popen(['cl', '/nologo', '/?'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = popen.communicate() + return '/FS ' in str(out) + + def is_windows(self): + return self.is_mingw() or self.is_msvc() + + def is_solaris(self): + return self._platform == 'solaris' + + def uses_usr_local(self): + return self._platform in ('freebsd', 'openbsd', 'bitrig') + + def supports_ppoll(self): + return self._platform in ('linux', 'openbsd', 'bitrig') + + def supports_ninja_browse(self): + return not self.is_windows() and not self.is_solaris() + + +class Bootstrap: + """API shim for ninja_syntax.Writer that instead runs the commands. + + Used to bootstrap Ninja from scratch. In --bootstrap mode this + class is used to execute all the commands to build an executable. + It also proxies all calls to an underlying ninja_syntax.Writer, to + behave like non-bootstrap mode. + """ + def __init__(self, writer): + self.writer = writer + # Map of variable name => expanded variable value. + self.vars = {} + # Map of rule name => dict of rule attributes. + self.rules = { + 'phony': {} + } + + def comment(self, text): + return self.writer.comment(text) + + def newline(self): + return self.writer.newline() + + def variable(self, key, val): + self.vars[key] = self._expand(val) + return self.writer.variable(key, val) + + def rule(self, name, **kwargs): + self.rules[name] = kwargs + return self.writer.rule(name, **kwargs) + + def build(self, outputs, rule, inputs=None, **kwargs): + ruleattr = self.rules[rule] + cmd = ruleattr.get('command') + if cmd is None: # A phony rule, for example. + return + + # Implement just enough of Ninja variable expansion etc. to + # make the bootstrap build work. + local_vars = { + 'in': self._expand_paths(inputs), + 'out': self._expand_paths(outputs) + } + for key, val in kwargs.get('variables', []): + local_vars[key] = ' '.join(ninja_syntax.as_list(val)) + + self._run_command(self._expand(cmd, local_vars)) + + return self.writer.build(outputs, rule, inputs, **kwargs) + + def default(self, paths): + return self.writer.default(paths) + + def _expand_paths(self, paths): + """Expand $vars in an array of paths, e.g. from a 'build' block.""" + paths = ninja_syntax.as_list(paths) + return ' '.join(map(self._expand, paths)) + + def _expand(self, str, local_vars={}): + """Expand $vars in a string.""" + return ninja_syntax.expand(str, self.vars, local_vars) + + def _run_command(self, cmdline): + """Run a subcommand, quietly. Prints the full command on error.""" + try: + subprocess.check_call(cmdline, shell=True) + except subprocess.CalledProcessError, e: + print('when running: ', cmdline) + raise + + parser = OptionParser() profilers = ['gmon', 'pprof'] +parser.add_option('--bootstrap', action='store_true', + help='bootstrap a ninja binary from nothing') parser.add_option('--platform', help='target platform (' + - '/'.join(platform_helper.platforms()) + ')', - choices=platform_helper.platforms()) + '/'.join(Platform.known_platforms()) + ')', + choices=Platform.known_platforms()) parser.add_option('--host', help='host platform (' + - '/'.join(platform_helper.platforms()) + ')', - choices=platform_helper.platforms()) + '/'.join(Platform.known_platforms()) + ')', + choices=Platform.known_platforms()) parser.add_option('--debug', action='store_true', help='enable debugging extras',) parser.add_option('--profile', metavar='TYPE', choices=profilers, help='enable profiling (' + '/'.join(profilers) + ')',) -parser.add_option('--with-gtest', metavar='PATH', - help='use gtest unpacked in directory PATH') +parser.add_option('--with-gtest', metavar='PATH', help='ignored') parser.add_option('--with-python', metavar='EXE', help='use EXE as the Python interpreter', default=os.path.basename(sys.executable)) @@ -57,15 +198,27 @@ print('ERROR: extra unparsed command-line arguments:', args) sys.exit(1) -platform = platform_helper.Platform(options.platform) +platform = Platform(options.platform) if options.host: - host = platform_helper.Platform(options.host) + host = Platform(options.host) else: host = platform BUILD_FILENAME = 'build.ninja' -buildfile = open(BUILD_FILENAME, 'w') -n = ninja_syntax.Writer(buildfile) +ninja_writer = ninja_syntax.Writer(open(BUILD_FILENAME, 'w')) +n = ninja_writer + +if options.bootstrap: + # Make the build directory. + try: + os.mkdir('build') + except OSError: + pass + # Wrap ninja_writer with the Bootstrapper, which also executes the + # commands. + print('bootstrapping ninja...') + n = Bootstrap(n) + n.comment('This file is used to build ninja itself.') n.comment('It is generated by ' + os.path.basename(__file__) + '.') n.newline() @@ -74,11 +227,15 @@ n.newline() n.comment('The arguments passed to configure.py, for rerunning it.') -n.variable('configure_args', ' '.join(sys.argv[1:])) +configure_args = sys.argv[1:] +if '--bootstrap' in configure_args: + configure_args.remove('--bootstrap') +n.variable('configure_args', ' '.join(configure_args)) env_keys = set(['CXX', 'AR', 'CFLAGS', '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 + '=' + configure_env[k] for k in configure_env]) + config_str = ' '.join([k + '=' + pipes.quote(configure_env[k]) + for k in configure_env]) n.variable('configure_env', config_str + '$ ') n.newline() @@ -113,7 +270,8 @@ def binary(name): n.variable('ar', configure_env.get('AR', 'ar')) if platform.is_msvc(): - cflags = ['/nologo', # Don't print startup banner. + cflags = ['/showIncludes', + '/nologo', # Don't print startup banner. '/Zi', # Create pdb with debug info. '/W4', # Highest warning level. '/WX', # Warnings as errors. @@ -128,6 +286,10 @@ def binary(name): '/DNOMINMAX', '/D_CRT_SECURE_NO_WARNINGS', '/D_VARIADIC_MAX=10', '/DNINJA_PYTHON="%s"' % options.with_python] + if options.bootstrap: + # In bootstrap mode, we have no ninja process to catch /showIncludes + # output. + cflags.remove('/showIncludes') if platform.msvc_needs_fs(): cflags.append('/FS') ldflags = ['/DEBUG', '/libpath:$builddir'] @@ -153,12 +315,16 @@ def binary(name): if platform.is_mingw(): cflags += ['-D_WIN32_WINNT=0x0501'] ldflags = ['-L$builddir'] + if platform.uses_usr_local(): + cflags.append('-I/usr/local/include') + ldflags.append('-L/usr/local/lib') + libs = [] if platform.is_mingw(): cflags.remove('-fvisibility=hidden'); ldflags.append('-static') -elif platform.is_sunos5(): +elif platform.is_solaris(): cflags.remove('-fvisibility=hidden') elif platform.is_msvc(): pass @@ -170,9 +336,10 @@ def binary(name): cflags.append('-fno-omit-frame-pointer') libs.extend(['-Wl,--no-as-needed', '-lprofiler']) -if (platform.is_linux() or platform.is_openbsd() or platform.is_bitrig()) and \ - not options.force_pselect: +if platform.supports_ppoll() and not options.force_pselect: cflags.append('-DUSE_PPOLL') +if platform.supports_ninja_browse(): + cflags.append('-DNINJA_HAVE_BROWSE') def shell_escape(str): """Escape str such that it's interpreted as a single argument by @@ -195,9 +362,10 @@ def shell_escape(str): if platform.is_msvc(): n.rule('cxx', - command='$cxx /showIncludes $cflags -c $in /Fo$out', + command='$cxx $cflags -c $in /Fo$out', description='CXX $out', - deps='msvc') + deps='msvc' # /showIncludes is included in $cflags. + ) else: n.rule('cxx', command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out', @@ -232,7 +400,7 @@ def shell_escape(str): objs = [] -if not platform.is_windows() and not platform.is_solaris(): +if platform.supports_ninja_browse(): n.comment('browse_py.h is used to inline browse.py.') n.rule('inline', command='src/inline.sh $varname < $in > $out', @@ -247,7 +415,6 @@ def shell_escape(str): n.comment('the depfile parser and ninja lexers are generated using re2c.') def has_re2c(): - import subprocess try: proc = subprocess.Popen(['re2c', '-V'], stdout=subprocess.PIPE) return int(proc.communicate()[0], 10) >= 1103 @@ -316,37 +483,16 @@ def has_re2c(): n.newline() all_targets += ninja +if options.bootstrap: + # We've built the ninja binary. Don't run any more commands + # through the bootstrap executor, but continue writing the + # build.ninja file. + n = ninja_writer + n.comment('Tests all build into ninja_test executable.') -variables = [] -test_cflags = cflags + ['-DGTEST_HAS_RTTI=0'] -test_ldflags = None -test_libs = libs objs = [] -if options.with_gtest: - path = options.with_gtest - gtest_all_incs = '-I%s -I%s' % (path, os.path.join(path, 'include')) - if platform.is_msvc(): - gtest_cflags = '/nologo /EHsc /Zi /D_VARIADIC_MAX=10 ' - if platform.msvc_needs_fs(): - gtest_cflags += '/FS ' - gtest_cflags += gtest_all_incs - else: - gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs - objs += n.build(built('gtest-all' + objext), 'cxx', - os.path.join(path, 'src', 'gtest-all.cc'), - variables=[('cflags', gtest_cflags)]) - - test_cflags.append('-I%s' % os.path.join(path, 'include')) -else: - # Use gtest from system. - if platform.is_msvc(): - test_libs.extend(['gtest_main.lib', 'gtest.lib']) - else: - test_libs.extend(['-lgtest_main', '-lgtest']) - -n.variable('test_cflags', test_cflags) for name in ['build_log_test', 'build_test', 'clean_test', @@ -362,16 +508,13 @@ def has_re2c(): 'subprocess_test', 'test', 'util_test']: - objs += cxx(name, variables=[('cflags', '$test_cflags')]) + objs += cxx(name) if platform.is_windows(): for name in ['includes_normalize_test', 'msvc_helper_test']: - objs += cxx(name, variables=[('cflags', '$test_cflags')]) + objs += cxx(name) -if not platform.is_windows(): - test_libs.append('-lpthread') ninja_test = n.build(binary('ninja_test'), 'link', objs, implicit=ninja_lib, - variables=[('ldflags', test_ldflags), - ('libs', test_libs)]) + variables=[('libs', libs)]) n.newline() all_targets += ninja_test @@ -456,4 +599,16 @@ def has_re2c(): n.build('all', 'phony', all_targets) +n.close() print('wrote %s.' % BUILD_FILENAME) + +if options.bootstrap: + print('bootstrap complete. rebuilding...') + if platform.is_windows(): + bootstrap_exe = 'ninja.bootstrap.exe' + if os.path.exists(bootstrap_exe): + os.unlink(bootstrap_exe) + os.rename('ninja.exe', bootstrap_exe) + subprocess.check_call('ninja.bootstrap.exe', shell=True) + else: + subprocess.check_call('./ninja', shell=True) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 28fd9d30ae..21f4e42908 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -1,7 +1,7 @@ Ninja ===== Evan Martin -v1.5.1, June 2014 +v1.5.3, Nov 2014 Introduction @@ -697,9 +697,7 @@ Lexical syntax Ninja is mostly encoding agnostic, as long as the bytes Ninja cares about (like slashes in paths) are ASCII. This means e.g. UTF-8 or -ISO-8859-1 input files ought to work. (To simplify some code, tabs -and carriage returns are currently disallowed; this could be fixed if -it really mattered to you.) +ISO-8859-1 input files ought to work. Comments begin with `#` and extend to the end of the line. diff --git a/misc/bash-completion b/misc/bash-completion index 6edf4dfea0..0536760172 100644 --- a/misc/bash-completion +++ b/misc/bash-completion @@ -50,7 +50,7 @@ _ninja_target() { esac done; targets_command="eval ninja -C \"${dir}\" -t targets all" - targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}') + targets=$(${targets_command} 2>/dev/null | awk -F: '{print $1}') COMPREPLY=($(compgen -W "$targets" -- "$cur")) fi return diff --git a/misc/ninja-mode.el b/misc/ninja-mode.el index 36ada6f7a6..71825d547b 100644 --- a/misc/ninja-mode.el +++ b/misc/ninja-mode.el @@ -1,4 +1,6 @@ -;;; ninja-mode.el --- Major mode for editing .ninja files +;;; ninja-mode.el --- Major mode for editing .ninja files -*- lexical-binding: t -*- + +;; Package-Requires: ((emacs "24")) ;; Copyright 2011 Google Inc. All Rights Reserved. ;; @@ -22,26 +24,58 @@ ;;; Code: (defvar ninja-keywords - (list - '("^#.*" . font-lock-comment-face) - (cons (concat "^" (regexp-opt '("rule" "build" "subninja" "include" - "pool" "default") - 'words)) - font-lock-keyword-face) - '("\\([[:alnum:]_]+\\) =" . (1 font-lock-variable-name-face)) - ;; Variable expansion. - '("\\($[[:alnum:]_]+\\)" . (1 font-lock-variable-name-face)) - ;; Rule names - '("rule \\([[:alnum:]_-]+\\)" . (1 font-lock-function-name-face)) - )) - -;;;###autoload -(define-derived-mode ninja-mode fundamental-mode "ninja" - (setq comment-start "#") - ; Pass extra "t" to turn off syntax-based fontification -- we don't want - ; quoted strings highlighted. - (setq font-lock-defaults '(ninja-keywords t)) - ) + `((,(concat "^" (regexp-opt '("rule" "build" "subninja" "include" + "pool" "default") + 'words)) + . font-lock-keyword-face) + ("\\([[:alnum:]_]+\\) =" 1 font-lock-variable-name-face) + ;; Variable expansion. + ("$[[:alnum:]_]+" . font-lock-variable-name-face) + ("${[[:alnum:]._]+}" . font-lock-variable-name-face) + ;; Rule names + ("rule +\\([[:alnum:]_.-]+\\)" 1 font-lock-function-name-face) + ;; Build Statement - highlight the rule used, + ;; allow for escaped $,: in outputs. + ("build +\\(?:[^:$\n]\\|$[:$]\\)+ *: *\\([[:alnum:]_.-]+\\)" + 1 font-lock-function-name-face))) + +(defvar ninja-mode-syntax-table + (let ((table (make-syntax-table))) + (modify-syntax-entry ?\" "." table) + table) + "Syntax table used in `ninja-mode'.") + +(defun ninja-syntax-propertize (start end) + (save-match-data + (save-excursion + (goto-char start) + (while (search-forward "#" end t) + (let ((match-pos (match-beginning 0))) + (when (and + ;; Is it the first non-white character on the line? + (eq match-pos (save-excursion (back-to-indentation) (point))) + (save-excursion + (goto-char (line-end-position 0)) + (or + ;; If we're continuting the previous line, it's not a + ;; comment. + (not (eq ?$ (char-before))) + ;; Except if the previous line is a comment as well, as the + ;; continuation dollar is ignored then. + (nth 4 (syntax-ppss))))) + (put-text-property match-pos (1+ match-pos) 'syntax-table '(11)) + (let ((line-end (line-end-position))) + ;; Avoid putting properties past the end of the buffer. + ;; Otherwise we get an `args-out-of-range' error. + (unless (= line-end (1+ (buffer-size))) + (put-text-property line-end (1+ line-end) 'syntax-table '(12)))))))))) + +;;;###autoload +(define-derived-mode ninja-mode prog-mode "ninja" + (set (make-local-variable 'comment-start) "#") + (set (make-local-variable 'parse-sexp-lookup-properties) t) + (set (make-local-variable 'syntax-propertize-function) #'ninja-syntax-propertize) + (setq font-lock-defaults '(ninja-keywords))) ;; Run ninja-mode for files ending in .ninja. ;;;###autoload diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py index 14b932ff4c..8673518cfb 100644 --- a/misc/ninja_syntax.py +++ b/misc/ninja_syntax.py @@ -7,6 +7,7 @@ use Python. """ +import re import textwrap def escape_path(word): @@ -59,16 +60,16 @@ def rule(self, name, command, description=None, depfile=None, def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, variables=None): - outputs = self._as_list(outputs) + outputs = as_list(outputs) out_outputs = [escape_path(x) for x in outputs] - all_inputs = [escape_path(x) for x in self._as_list(inputs)] + all_inputs = [escape_path(x) for x in as_list(inputs)] if implicit: - implicit = [escape_path(x) for x in self._as_list(implicit)] + implicit = [escape_path(x) for x in as_list(implicit)] all_inputs.append('|') all_inputs.extend(implicit) if order_only: - order_only = [escape_path(x) for x in self._as_list(order_only)] + order_only = [escape_path(x) for x in as_list(order_only)] all_inputs.append('||') all_inputs.extend(order_only) @@ -93,7 +94,7 @@ def subninja(self, path): self._line('subninja %s' % path) def default(self, paths): - self._line('default %s' % ' '.join(self._as_list(paths))) + self._line('default %s' % ' '.join(as_list(paths))) def _count_dollars_before_index(self, s, i): """Returns the number of '$' characters right in front of s[i].""" @@ -140,12 +141,16 @@ def _line(self, text, indent=0): self.output.write(leading_space + text + '\n') - def _as_list(self, input): - if input is None: - return [] - if isinstance(input, list): - return input - return [input] + def close(self): + self.output.close() + + +def as_list(input): + if input is None: + return [] + if isinstance(input, list): + return input + return [input] def escape(string): @@ -154,3 +159,17 @@ def escape(string): assert '\n' not in string, 'Ninja syntax does not allow newlines' # We only have one special metacharacter: '$'. return string.replace('$', '$$') + + +def expand(string, vars, local_vars={}): + """Expand a string containing $vars as Ninja would. + + Note: doesn't handle the full Ninja variable syntax, but it's enough + to make configure.py's use of it work. + """ + def exp(m): + var = m.group(1) + if var == '$': + return '$' + return local_vars.get(var, vars.get(var, '')) + return re.sub(r'\$(\$|\w*)', exp, string) diff --git a/misc/ninja_syntax_test.py b/misc/ninja_syntax_test.py index 2aef7ff830..36b2e7be8b 100755 --- a/misc/ninja_syntax_test.py +++ b/misc/ninja_syntax_test.py @@ -148,5 +148,31 @@ def test_variables_list(self): ''', self.out.getvalue()) +class TestExpand(unittest.TestCase): + def test_basic(self): + vars = {'x': 'X'} + self.assertEqual('foo', ninja_syntax.expand('foo', vars)) + + def test_var(self): + vars = {'xyz': 'XYZ'} + self.assertEqual('fooXYZ', ninja_syntax.expand('foo$xyz', vars)) + + def test_vars(self): + vars = {'x': 'X', 'y': 'YYY'} + self.assertEqual('XYYY', ninja_syntax.expand('$x$y', vars)) + + def test_space(self): + vars = {} + self.assertEqual('x y z', ninja_syntax.expand('x$ y$ z', vars)) + + def test_locals(self): + vars = {'x': 'a'} + local_vars = {'x': 'b'} + self.assertEqual('a', ninja_syntax.expand('$x', vars)) + self.assertEqual('b', ninja_syntax.expand('$x', vars, local_vars)) + + def test_double(self): + self.assertEqual('a b$c', ninja_syntax.expand('a$ b$$c', {})) + if __name__ == '__main__': unittest.main() diff --git a/misc/packaging/ninja.spec b/misc/packaging/ninja.spec index f0c46feab5..fa244a6fec 100644 --- a/misc/packaging/ninja.spec +++ b/misc/packaging/ninja.spec @@ -23,7 +23,7 @@ seconds to start building after changing one file. Ninja is under a second. %build echo Building.. -./bootstrap.py +./configure.py --bootstrap ./ninja manual %install diff --git a/misc/write_fake_manifests.py b/misc/write_fake_manifests.py index 837007e603..ca49535857 100644 --- a/misc/write_fake_manifests.py +++ b/misc/write_fake_manifests.py @@ -182,7 +182,7 @@ def FileWriter(path): def random_targets(): - num_targets = 800 + num_targets = 1500 gen = GenRandom() # N-1 static libraries, and 1 executable depending on all of them. diff --git a/misc/zsh-completion b/misc/zsh-completion index 2fe16fb049..af24f89b26 100644 --- a/misc/zsh-completion +++ b/misc/zsh-completion @@ -17,7 +17,14 @@ # . path/to/ninja/misc/zsh-completion __get_targets() { - ninja -t targets 2>/dev/null | while read -r a b; do echo $a | cut -d ':' -f1; done; + dir="." + if [ -n "${opt_args[-C]}" ]; + then + eval dir="${opt_args[-C]}" + fi + targets_command="ninja -C \"${dir}\" -t targets" + eval ${targets_command} 2>/dev/null | while read -r a b; do echo $a | cut -d ':' -f1; done; + } __get_tools() { diff --git a/platform_helper.py b/platform_helper.py deleted file mode 100644 index bc3a12525f..0000000000 --- a/platform_helper.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# Copyright 2011 Google Inc. -# Copyright 2013 Patrick von Reth -# 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. - -import sys - -def platforms(): - return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5', - 'mingw', 'msvc', 'gnukfreebsd', 'bitrig'] - -class Platform(object): - def __init__(self, platform): - self._platform = platform - if not self._platform is None: - return - self._platform = sys.platform - if self._platform.startswith('linux'): - self._platform = 'linux' - elif self._platform.startswith('freebsd'): - self._platform = 'freebsd' - elif self._platform.startswith('gnukfreebsd'): - self._platform = 'freebsd' - elif self._platform.startswith('openbsd'): - self._platform = 'openbsd' - elif self._platform.startswith('solaris'): - self._platform = 'solaris' - elif self._platform.startswith('mingw'): - self._platform = 'mingw' - elif self._platform.startswith('win'): - self._platform = 'msvc' - elif self._platform.startswith('bitrig'): - self._platform = 'bitrig' - - def platform(self): - return self._platform - - def is_linux(self): - return self._platform == 'linux' - - def is_mingw(self): - return self._platform == 'mingw' - - def is_msvc(self): - return self._platform == 'msvc' - - def msvc_needs_fs(self): - import subprocess - popen = subprocess.Popen(['cl', '/nologo', '/?'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = popen.communicate() - return '/FS ' in str(out) - - def is_windows(self): - return self.is_mingw() or self.is_msvc() - - def is_solaris(self): - return self._platform == 'solaris' - - def is_freebsd(self): - return self._platform == 'freebsd' - - def is_openbsd(self): - return self._platform == 'openbsd' - - def is_sunos5(self): - return self._platform == 'sunos5' - - def is_bitrig(self): - return self._platform == 'bitrig' diff --git a/src/build.cc b/src/build.cc index 64bcea3786..3e74131a72 100644 --- a/src/build.cc +++ b/src/build.cc @@ -488,7 +488,9 @@ void RealCommandRunner::Abort() { } bool RealCommandRunner::CanRunMore() { - return ((int)subprocs_.running_.size()) < config_.parallelism + size_t subproc_number = + subprocs_.running_.size() + subprocs_.finished_.size(); + return (int)subproc_number < config_.parallelism && ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f) || GetLoadAverage() < config_.max_load_average); } @@ -823,7 +825,11 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, result->output = parser.Parse(result->output, deps_prefix); for (set::iterator i = parser.includes_.begin(); i != parser.includes_.end(); ++i) { - deps_nodes->push_back(state_->GetNode(*i)); + // ~0 is assuming that with MSVC-parsed headers, it's ok to always make + // all backslashes (as some of the slashes will certainly be backslashes + // anyway). This could be fixed if necessary with some additional + // complexity in IncludesNormalize::Relativize. + deps_nodes->push_back(state_->GetNode(*i, ~0u)); } } else #endif @@ -848,9 +854,11 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, deps_nodes->reserve(deps.ins_.size()); for (vector::iterator i = deps.ins_.begin(); i != deps.ins_.end(); ++i) { - if (!CanonicalizePath(const_cast(i->str_), &i->len_, err)) + unsigned int slash_bits; + if (!CanonicalizePath(const_cast(i->str_), &i->len_, &slash_bits, + err)) return false; - deps_nodes->push_back(state_->GetNode(*i)); + deps_nodes->push_back(state_->GetNode(*i, slash_bits)); } if (disk_interface_->RemoveFile(depfile) < 0) { diff --git a/src/build_log.cc b/src/build_log.cc index 3f24c16d38..b6f9874578 100644 --- a/src/build_log.cc +++ b/src/build_log.cc @@ -102,7 +102,7 @@ BuildLog::LogEntry::LogEntry(const string& output, uint64_t command_hash, {} BuildLog::BuildLog() - : log_file_(NULL), needs_recompaction_(false) {} + : log_file_(NULL), needs_recompaction_(false), quiet_(false) {} BuildLog::~BuildLog() { Close(); @@ -354,7 +354,8 @@ bool BuildLog::WriteEntry(FILE* f, const LogEntry& entry) { bool BuildLog::Recompact(const string& path, const BuildLogUser& user, string* err) { METRIC_RECORD(".ninja_log recompact"); - printf("Recompacting log...\n"); + if (!quiet_) + printf("Recompacting log...\n"); Close(); string temp_path = path + ".recompact"; diff --git a/src/build_log.h b/src/build_log.h index fe81a851f4..db0cfe0ebd 100644 --- a/src/build_log.h +++ b/src/build_log.h @@ -80,6 +80,7 @@ struct BuildLog { /// Rewrite the known log entries, throwing away old data. bool Recompact(const string& path, const BuildLogUser& user, string* err); + void set_quiet(bool quiet) { quiet_ = quiet; } typedef ExternalStringHashMap::Type Entries; const Entries& entries() const { return entries_; } @@ -88,6 +89,7 @@ struct BuildLog { Entries entries_; FILE* log_file_; bool needs_recompaction_; + bool quiet_; }; #endif // NINJA_BUILD_LOG_H_ diff --git a/src/build_log_test.cc b/src/build_log_test.cc index 6738c7b5dc..7ea2117ba0 100644 --- a/src/build_log_test.cc +++ b/src/build_log_test.cc @@ -17,12 +17,12 @@ #include "util.h" #include "test.h" +#include #ifdef _WIN32 #include #include #else #include -#include #include #endif @@ -290,6 +290,7 @@ TEST_F(BuildLogRecompactTest, Recompact) { ASSERT_TRUE(log2.LookupByOutput("out")); ASSERT_TRUE(log2.LookupByOutput("out2")); // ...and force a recompaction. + log2.set_quiet(true); EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err)); log2.Close(); diff --git a/src/build_test.cc b/src/build_test.cc index dad69dc0ea..bd1cd30cff 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -14,6 +14,8 @@ #include "build.h" +#include + #include "build_log.h" #include "deps_log.h" #include "graph.h" @@ -506,7 +508,7 @@ void BuildTest::RebuildTarget(const string& target, const char* manifest, builder.command_runner_.reset(&command_runner_); if (!builder.AlreadyUpToDate()) { bool build_res = builder.Build(&err); - EXPECT_TRUE(build_res) << "builder.Build(&err)"; + EXPECT_TRUE(build_res); } builder.command_runner_.release(); } @@ -753,23 +755,18 @@ TEST_F(BuildTest, MakeDirs) { #ifdef _WIN32 ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build subdir\\dir2\\file: cat in1\n")); - EXPECT_TRUE(builder_.AddTarget("subdir\\dir2\\file", &err)); #else ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build subdir/dir2/file: cat in1\n")); - EXPECT_TRUE(builder_.AddTarget("subdir/dir2/file", &err)); #endif + EXPECT_TRUE(builder_.AddTarget("subdir/dir2/file", &err)); EXPECT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); ASSERT_EQ(2u, fs_.directories_made_.size()); EXPECT_EQ("subdir", fs_.directories_made_[0]); -#ifdef _WIN32 - EXPECT_EQ("subdir\\dir2", fs_.directories_made_[1]); -#else EXPECT_EQ("subdir/dir2", fs_.directories_made_[1]); -#endif } TEST_F(BuildTest, DepFileMissing) { @@ -940,6 +937,38 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) { ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]); } +#ifdef _WIN32 +TEST_F(BuildTest, DepFileCanonicalize) { + string err; + int orig_edges = state_.edges_.size(); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cc\n command = cc $in\n depfile = $out.d\n" +"build gen/stuff\\things/foo.o: cc x\\y/z\\foo.c\n")); + Edge* edge = state_.edges_.back(); + + fs_.Create("x/y/z/foo.c", ""); + GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing. + // Note, different slashes from manifest. + fs_.Create("gen/stuff\\things/foo.o.d", + "gen\\stuff\\things\\foo.o: blah.h bar.h\n"); + EXPECT_TRUE(builder_.AddTarget("gen/stuff/things/foo.o", &err)); + ASSERT_EQ("", err); + ASSERT_EQ(1u, fs_.files_read_.size()); + // The depfile path does not get Canonicalize as it seems unnecessary. + EXPECT_EQ("gen/stuff\\things/foo.o.d", fs_.files_read_[0]); + + // Expect three new edges: one generating foo.o, and two more from + // loading the depfile. + ASSERT_EQ(orig_edges + 3, (int)state_.edges_.size()); + // Expect our edge to now have three inputs: foo.c and two headers. + ASSERT_EQ(3u, edge->inputs_.size()); + + // Expect the command line we generate to only use the original input, and + // using the slashes from the manifest. + ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand()); +} +#endif + TEST_F(BuildTest, Phony) { string err; ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, @@ -1854,7 +1883,7 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) { Edge* edge = state.edges_.back(); - state.GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing. + state.GetNode("bar.h", 0)->MarkDirty(); // Mark bar.h as missing. EXPECT_TRUE(builder.AddTarget("fo o.o", &err)); ASSERT_EQ("", err); @@ -1872,6 +1901,72 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) { } } +#ifdef _WIN32 +TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) { + string err; + const char* manifest = + "rule cc\n command = cc $in\n depfile = $out.d\n deps = gcc\n" + "build a/b\\c\\d/e/fo$ o.o: cc x\\y/z\\foo.c\n"; + + fs_.Create("x/y/z/foo.c", ""); + + { + State state; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + // Run the build once, everything should be ok. + DepsLog deps_log; + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + + Builder builder(&state, config_, NULL, &deps_log, &fs_); + builder.command_runner_.reset(&command_runner_); + EXPECT_TRUE(builder.AddTarget("a/b/c/d/e/fo o.o", &err)); + ASSERT_EQ("", err); + // Note, different slashes from manifest. + fs_.Create("a/b\\c\\d/e/fo o.o.d", + "a\\b\\c\\d\\e\\fo\\ o.o: blah.h bar.h\n"); + EXPECT_TRUE(builder.Build(&err)); + EXPECT_EQ("", err); + + deps_log.Close(); + builder.command_runner_.release(); + } + + { + State state; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + DepsLog deps_log; + ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err)); + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + + Builder builder(&state, config_, NULL, &deps_log, &fs_); + builder.command_runner_.reset(&command_runner_); + + Edge* edge = state.edges_.back(); + + state.GetNode("bar.h", 0)->MarkDirty(); // Mark bar.h as missing. + EXPECT_TRUE(builder.AddTarget("a/b/c/d/e/fo o.o", &err)); + ASSERT_EQ("", err); + + // Expect three new edges: one generating fo o.o, and two more from + // loading the depfile. + ASSERT_EQ(3u, state.edges_.size()); + // Expect our edge to now have three inputs: foo.c and two headers. + ASSERT_EQ(3u, edge->inputs_.size()); + + // Expect the command line we generate to only use the original input. + // Note, slashes from manifest, not .d. + ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand()); + + deps_log.Close(); + builder.command_runner_.release(); + } +} +#endif + /// Check that a restat rule doesn't clear an edge if the depfile is missing. /// Follows from: https://github.com/martine/ninja/issues/603 TEST_F(BuildTest, RestatMissingDepfile) { diff --git a/src/canon_perftest.cc b/src/canon_perftest.cc index 59bd18f452..389ac24636 100644 --- a/src/canon_perftest.cc +++ b/src/canon_perftest.cc @@ -33,8 +33,9 @@ int main() { for (int j = 0; j < 5; ++j) { const int kNumRepetitions = 2000000; int64_t start = GetTimeMillis(); + unsigned int slash_bits; for (int i = 0; i < kNumRepetitions; ++i) { - CanonicalizePath(buf, &len, &err); + CanonicalizePath(buf, &len, &slash_bits, &err); } int delta = (int)(GetTimeMillis() - start); times.push_back(delta); diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc index a5f3321409..e67ef794ca 100644 --- a/src/depfile_parser_test.cc +++ b/src/depfile_parser_test.cc @@ -14,7 +14,7 @@ #include "depfile_parser.h" -#include +#include "test.h" struct DepfileParserTest : public testing::Test { bool Parse(const char* input, string* err); diff --git a/src/deps_log.cc b/src/deps_log.cc index 61df387e0d..fc46497847 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -240,7 +240,12 @@ bool DepsLog::Load(const string& path, State* state, string* err) { if (buf[path_size - 1] == '\0') --path_size; if (buf[path_size - 1] == '\0') --path_size; StringPiece path(buf, path_size); - Node* node = state->GetNode(path); + // It is not necessary to pass in a correct slash_bits here. It will + // either be a Node that's in the manifest (in which case it will already + // have a correct slash_bits that GetNode will look up), or it is an + // implicit dependency from a .d which does not affect the build command + // (and so need not have its slashes maintained). + Node* node = state->GetNode(path, 0); // Check that the expected index matches the actual index. This can only // happen if two ninja processes write to the same deps log concurrently. @@ -302,7 +307,8 @@ DepsLog::Deps* DepsLog::GetDeps(Node* node) { bool DepsLog::Recompact(const string& path, string* err) { METRIC_RECORD(".ninja_deps recompact"); - printf("Recompacting deps...\n"); + if (!quiet_) + printf("Recompacting deps...\n"); Close(); string temp_path = path + ".recompact"; diff --git a/src/deps_log.h b/src/deps_log.h index cec0257cef..9b81bc1565 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -64,7 +64,7 @@ struct State; /// wins, allowing updates to just be appended to the file. A separate /// repacking step can run occasionally to remove dead records. struct DepsLog { - DepsLog() : needs_recompaction_(false), file_(NULL) {} + DepsLog() : needs_recompaction_(false), quiet_(false), file_(NULL) {} ~DepsLog(); // Writing (build-time) interface. @@ -87,6 +87,7 @@ struct DepsLog { /// Rewrite the known log entries, throwing away old data. bool Recompact(const string& path, string* err); + void set_quiet(bool quiet) { quiet_ = quiet; } /// Returns if the deps entry for a node is still reachable from the manifest. /// @@ -108,6 +109,7 @@ struct DepsLog { bool RecordId(Node* node); bool needs_recompaction_; + bool quiet_; FILE* file_; /// Maps id -> Node. diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index e8e5138fc2..ac2b315f03 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -14,6 +14,11 @@ #include "deps_log.h" +#include +#ifndef _WIN32 +#include +#endif + #include "graph.h" #include "util.h" #include "test.h" @@ -41,16 +46,16 @@ TEST_F(DepsLogTest, WriteRead) { { vector deps; - deps.push_back(state1.GetNode("foo.h")); - deps.push_back(state1.GetNode("bar.h")); - log1.RecordDeps(state1.GetNode("out.o"), 1, deps); + deps.push_back(state1.GetNode("foo.h", 0)); + deps.push_back(state1.GetNode("bar.h", 0)); + log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state1.GetNode("foo.h")); - deps.push_back(state1.GetNode("bar2.h")); - log1.RecordDeps(state1.GetNode("out2.o"), 2, deps); + deps.push_back(state1.GetNode("foo.h", 0)); + deps.push_back(state1.GetNode("bar2.h", 0)); + log1.RecordDeps(state1.GetNode("out2.o", 0), 2, deps); - DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o")); + DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0)); ASSERT_TRUE(log_deps); ASSERT_EQ(1, log_deps->mtime); ASSERT_EQ(2, log_deps->node_count); @@ -74,7 +79,7 @@ TEST_F(DepsLogTest, WriteRead) { } // Spot-check the entries in log2. - DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o")); + DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o", 0)); ASSERT_TRUE(log_deps); ASSERT_EQ(2, log_deps->mtime); ASSERT_EQ(2, log_deps->node_count); @@ -96,11 +101,11 @@ TEST_F(DepsLogTest, LotsOfDeps) { for (int i = 0; i < kNumDeps; ++i) { char buf[32]; sprintf(buf, "file%d.h", i); - deps.push_back(state1.GetNode(buf)); + deps.push_back(state1.GetNode(buf, 0)); } - log1.RecordDeps(state1.GetNode("out.o"), 1, deps); + log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps); - DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o")); + DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0)); ASSERT_EQ(kNumDeps, log_deps->node_count); } @@ -111,7 +116,7 @@ TEST_F(DepsLogTest, LotsOfDeps) { EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err)); ASSERT_EQ("", err); - DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out.o")); + DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out.o", 0)); ASSERT_EQ(kNumDeps, log_deps->node_count); } @@ -127,9 +132,9 @@ TEST_F(DepsLogTest, DoubleEntry) { ASSERT_EQ("", err); vector deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); log.Close(); struct stat st; @@ -149,9 +154,9 @@ TEST_F(DepsLogTest, DoubleEntry) { ASSERT_EQ("", err); vector deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); log.Close(); struct stat st; @@ -181,14 +186,14 @@ TEST_F(DepsLogTest, Recompact) { ASSERT_EQ("", err); vector deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("baz.h")); - log.RecordDeps(state.GetNode("other_out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("baz.h", 0)); + log.RecordDeps(state.GetNode("other_out.o", 0), 1, deps); log.Close(); @@ -211,8 +216,8 @@ TEST_F(DepsLogTest, Recompact) { ASSERT_EQ("", err); vector deps; - deps.push_back(state.GetNode("foo.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); log.Close(); struct stat st; @@ -232,14 +237,14 @@ TEST_F(DepsLogTest, Recompact) { string err; ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); - Node* out = state.GetNode("out.o"); + Node* out = state.GetNode("out.o", 0); DepsLog::Deps* deps = log.GetDeps(out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); ASSERT_EQ(1, deps->node_count); ASSERT_EQ("foo.h", deps->nodes[0]->path()); - Node* other_out = state.GetNode("other_out.o"); + Node* other_out = state.GetNode("other_out.o", 0); deps = log.GetDeps(other_out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); @@ -247,6 +252,7 @@ TEST_F(DepsLogTest, Recompact) { ASSERT_EQ("foo.h", deps->nodes[0]->path()); ASSERT_EQ("baz.h", deps->nodes[1]->path()); + log.set_quiet(true); ASSERT_TRUE(log.Recompact(kTestFilename, &err)); // The in-memory deps graph should still be valid after recompaction. @@ -281,14 +287,14 @@ TEST_F(DepsLogTest, Recompact) { string err; ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); - Node* out = state.GetNode("out.o"); + Node* out = state.GetNode("out.o", 0); DepsLog::Deps* deps = log.GetDeps(out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); ASSERT_EQ(1, deps->node_count); ASSERT_EQ("foo.h", deps->nodes[0]->path()); - Node* other_out = state.GetNode("other_out.o"); + Node* other_out = state.GetNode("other_out.o", 0); deps = log.GetDeps(other_out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); @@ -296,6 +302,7 @@ TEST_F(DepsLogTest, Recompact) { ASSERT_EQ("foo.h", deps->nodes[0]->path()); ASSERT_EQ("baz.h", deps->nodes[1]->path()); + log.set_quiet(true); ASSERT_TRUE(log.Recompact(kTestFilename, &err)); // The previous entries should have been removed. @@ -354,14 +361,14 @@ TEST_F(DepsLogTest, Truncated) { ASSERT_EQ("", err); vector deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar2.h")); - log.RecordDeps(state.GetNode("out2.o"), 2, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar2.h", 0)); + log.RecordDeps(state.GetNode("out2.o", 0), 2, deps); log.Close(); } @@ -413,14 +420,14 @@ TEST_F(DepsLogTest, TruncatedRecovery) { ASSERT_EQ("", err); vector deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar2.h")); - log.RecordDeps(state.GetNode("out2.o"), 2, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar2.h", 0)); + log.RecordDeps(state.GetNode("out2.o", 0), 2, deps); log.Close(); } @@ -441,16 +448,16 @@ TEST_F(DepsLogTest, TruncatedRecovery) { err.clear(); // The truncated entry should've been discarded. - EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o"))); + EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o", 0))); EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err)); ASSERT_EQ("", err); // Add a new entry. vector deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar2.h")); - log.RecordDeps(state.GetNode("out2.o"), 3, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar2.h", 0)); + log.RecordDeps(state.GetNode("out2.o", 0), 3, deps); log.Close(); } @@ -464,7 +471,7 @@ TEST_F(DepsLogTest, TruncatedRecovery) { EXPECT_TRUE(log.Load(kTestFilename, &state, &err)); // The truncated entry should exist. - DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o")); + DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o", 0)); ASSERT_TRUE(deps); } } diff --git a/src/disk_interface.cc b/src/disk_interface.cc index b170f63ea6..9810708b1e 100644 --- a/src/disk_interface.cc +++ b/src/disk_interface.cc @@ -82,16 +82,20 @@ TimeStamp StatSingleFile(const string& path, bool quiet) { 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; + (version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 1); } +#ifdef _MSC_VER #pragma warning(pop) +#endif bool StatAllFilesInDir(const string& dir, map* stamps, bool quiet) { diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc index b2e8cb5fb3..05d509c00d 100644 --- a/src/disk_interface_test.cc +++ b/src/disk_interface_test.cc @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - +#include +#include #ifdef _WIN32 #include #include diff --git a/src/getopt.c b/src/getopt.c index 75ef99cfb4..3350fb9ce5 100644 --- a/src/getopt.c +++ b/src/getopt.c @@ -299,7 +299,7 @@ getopt_internal (int argc, char **argv, char *shortopts, return (optopt = '?'); } has_arg = ((cp[1] == ':') - ? ((cp[2] == ':') ? OPTIONAL_ARG : REQUIRED_ARG) : no_argument); + ? ((cp[2] == ':') ? OPTIONAL_ARG : required_argument) : no_argument); possible_arg = argv[optind] + optwhere + 1; optopt = *cp; } @@ -318,7 +318,7 @@ getopt_internal (int argc, char **argv, char *shortopts, else optarg = NULL; break; - case REQUIRED_ARG: + case required_argument: if (*possible_arg == '=') possible_arg++; if (*possible_arg != '\0') diff --git a/src/getopt.h b/src/getopt.h index ead9878b9a..b4247fb4b7 100644 --- a/src/getopt.h +++ b/src/getopt.h @@ -4,9 +4,9 @@ /* include files needed by this include file */ /* macros defined by this include file */ -#define no_argument 0 -#define REQUIRED_ARG 1 -#define OPTIONAL_ARG 2 +#define no_argument 0 +#define required_argument 1 +#define OPTIONAL_ARG 2 /* types defined by this include file */ diff --git a/src/graph.cc b/src/graph.cc index aa9c0e8c5e..2829669e79 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -131,7 +131,7 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { } bool DependencyScan::RecomputeOutputsDirty(Edge* edge, - Node* most_recent_input) { + Node* most_recent_input) { string command = edge->EvaluateCommand(true); for (vector::iterator i = edge->outputs_.begin(); i != edge->outputs_.end(); ++i) { @@ -256,7 +256,7 @@ string EdgeEnv::MakePathList(vector::iterator begin, for (vector::iterator i = begin; i != end; ++i) { if (!result.empty()) result.push_back(sep); - const string& path = (*i)->path(); + const string& path = (*i)->PathDecanonicalized(); if (escape_in_out_ == kShellEscape) { #if _WIN32 GetWin32EscapedString(path, &result); @@ -328,6 +328,20 @@ bool Edge::use_console() const { return pool() == &State::kConsolePool; } +string Node::PathDecanonicalized() const { + string result = path_; +#ifdef _WIN32 + unsigned int mask = 1; + for (char* c = &result[0]; (c = strchr(c, '/')) != NULL;) { + if (slash_bits_ & mask) + *c = '\\'; + c++; + mask <<= 1; + } +#endif + return result; +} + void Node::Dump(const char* prefix) const { printf("%s <%s 0x%p> mtime: %d%s, (:%s), ", prefix, path().c_str(), this, @@ -379,6 +393,11 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, return false; } + unsigned int unused; + if (!CanonicalizePath(const_cast(depfile.out_.str_), + &depfile.out_.len_, &unused, err)) + return false; + // Check that this depfile matches the edge's output. Node* first_output = edge->outputs_[0]; StringPiece opath = StringPiece(first_output->path()); @@ -395,10 +414,12 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, // Add all its in-edges. for (vector::iterator i = depfile.ins_.begin(); i != depfile.ins_.end(); ++i, ++implicit_dep) { - if (!CanonicalizePath(const_cast(i->str_), &i->len_, err)) + unsigned int slash_bits; + if (!CanonicalizePath(const_cast(i->str_), &i->len_, &slash_bits, + err)) return false; - Node* node = state_->GetNode(*i); + Node* node = state_->GetNode(*i, slash_bits); *implicit_dep = node; node->AddOutEdge(edge); CreatePhonyInEdge(node); diff --git a/src/graph.h b/src/graph.h index 66e31b530c..9aada38562 100644 --- a/src/graph.h +++ b/src/graph.h @@ -33,8 +33,9 @@ struct State; /// Information about a node in the dependency graph: the file, whether /// it's dirty, mtime, etc. struct Node { - explicit Node(const string& path) + Node(const string& path, unsigned int slash_bits) : path_(path), + slash_bits_(slash_bits), mtime_(-1), dirty_(false), in_edge_(NULL), @@ -71,6 +72,9 @@ struct Node { } const string& path() const { return path_; } + /// Get |path()| but use slash_bits to convert back to original slash styles. + string PathDecanonicalized() const; + unsigned int slash_bits() const { return slash_bits_; } TimeStamp mtime() const { return mtime_; } bool dirty() const { return dirty_; } @@ -90,6 +94,11 @@ struct Node { private: string path_; + + /// Set bits starting from lowest for backslashes that were normalized to + /// forward slashes by CanonicalizePath. See |PathDecanonicalized|. + unsigned int slash_bits_; + /// Possible values of mtime_: /// -1: file hasn't been examined /// 0: we looked, and file doesn't exist diff --git a/src/graph_test.cc b/src/graph_test.cc index 14dc6780b5..382d352def 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -251,3 +251,24 @@ TEST_F(GraphTest, NestedPhonyPrintsDone) { EXPECT_EQ(0, plan_.command_edge_count()); ASSERT_FALSE(plan_.more_to_do()); } + +#ifdef _WIN32 +TEST_F(GraphTest, Decanonicalize) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build out\\out1: cat src\\in1\n" +"build out\\out2/out3\\out4: cat mid1\n" +"build out3 out4\\foo: cat mid1\n")); + + string err; + vector root_nodes = state_.RootNodes(&err); + EXPECT_EQ(4u, root_nodes.size()); + EXPECT_EQ(root_nodes[0]->path(), "out/out1"); + EXPECT_EQ(root_nodes[1]->path(), "out/out2/out3/out4"); + EXPECT_EQ(root_nodes[2]->path(), "out3"); + EXPECT_EQ(root_nodes[3]->path(), "out4/foo"); + EXPECT_EQ(root_nodes[0]->PathDecanonicalized(), "out\\out1"); + EXPECT_EQ(root_nodes[1]->PathDecanonicalized(), "out\\out2/out3\\out4"); + EXPECT_EQ(root_nodes[2]->PathDecanonicalized(), "out3"); + EXPECT_EQ(root_nodes[3]->PathDecanonicalized(), "out4\\foo"); +} +#endif diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc index 05ce75d190..1e88a0ab65 100644 --- a/src/includes_normalize-win32.cc +++ b/src/includes_normalize-win32.cc @@ -68,12 +68,15 @@ string IncludesNormalize::ToLower(const string& s) { string IncludesNormalize::AbsPath(StringPiece s) { char result[_MAX_PATH]; GetFullPathName(s.AsString().c_str(), sizeof(result), result, NULL); + for (char* c = result; *c; ++c) + if (*c == '\\') + *c = '/'; return result; } string IncludesNormalize::Relativize(StringPiece path, const string& start) { - vector start_list = Split(AbsPath(start), '\\'); - vector path_list = Split(AbsPath(path), '\\'); + vector start_list = Split(AbsPath(start), '/'); + vector path_list = Split(AbsPath(path), '/'); int i; for (i = 0; i < static_cast(min(start_list.size(), path_list.size())); ++i) { @@ -88,7 +91,7 @@ string IncludesNormalize::Relativize(StringPiece path, const string& start) { rel_list.push_back(path_list[j]); if (rel_list.size() == 0) return "."; - return Join(rel_list, '\\'); + return Join(rel_list, '/'); } string IncludesNormalize::Normalize(const string& input, @@ -96,19 +99,17 @@ string IncludesNormalize::Normalize(const string& input, char copy[_MAX_PATH]; size_t len = input.size(); strncpy(copy, input.c_str(), input.size() + 1); - for (size_t j = 0; j < len; ++j) - if (copy[j] == '/') - copy[j] = '\\'; string err; - if (!CanonicalizePath(copy, &len, &err)) { - Warning("couldn't canonicalize '%s: %s\n", input.c_str(), err.c_str()); - } + unsigned int slash_bits; + if (!CanonicalizePath(copy, &len, &slash_bits, &err)) + Warning("couldn't canonicalize '%s': %s\n", input.c_str(), err.c_str()); + StringPiece partially_fixed(copy, len); + string curdir; if (!relative_to) { curdir = AbsPath("."); relative_to = curdir.c_str(); } - StringPiece partially_fixed(copy, len); if (!SameDrive(partially_fixed, relative_to)) return partially_fixed.AsString(); return Relativize(partially_fixed, relative_to); diff --git a/src/includes_normalize.h b/src/includes_normalize.h index 43527af941..634fef3579 100644 --- a/src/includes_normalize.h +++ b/src/includes_normalize.h @@ -29,7 +29,6 @@ struct IncludesNormalize { static string Relativize(StringPiece path, const string& start); /// Normalize by fixing slashes style, fixing redundant .. and . and makes the - /// path relative to |relative_to|. Case is normalized to lowercase on - /// Windows too. + /// path relative to |relative_to|. static string Normalize(const string& input, const char* relative_to); }; diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc index 419996f4e4..c4c247630e 100644 --- a/src/includes_normalize_test.cc +++ b/src/includes_normalize_test.cc @@ -14,7 +14,7 @@ #include "includes_normalize.h" -#include +#include #include "test.h" #include "util.h" @@ -22,8 +22,8 @@ TEST(IncludesNormalize, Simple) { EXPECT_EQ("b", IncludesNormalize::Normalize("a\\..\\b", NULL)); EXPECT_EQ("b", IncludesNormalize::Normalize("a\\../b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\.\\b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\./b", NULL)); + EXPECT_EQ("a/b", IncludesNormalize::Normalize("a\\.\\b", NULL)); + EXPECT_EQ("a/b", IncludesNormalize::Normalize("a\\./b", NULL)); } namespace { @@ -42,21 +42,21 @@ TEST(IncludesNormalize, WithRelative) { EXPECT_EQ("c", IncludesNormalize::Normalize("a/b/c", "a/b")); EXPECT_EQ("a", IncludesNormalize::Normalize(IncludesNormalize::AbsPath("a"), NULL)); - EXPECT_EQ(string("..\\") + currentdir + string("\\a"), + EXPECT_EQ(string("../") + currentdir + string("/a"), IncludesNormalize::Normalize("a", "../b")); - EXPECT_EQ(string("..\\") + currentdir + string("\\a\\b"), + EXPECT_EQ(string("../") + currentdir + string("/a/b"), IncludesNormalize::Normalize("a/b", "../c")); - EXPECT_EQ("..\\..\\a", IncludesNormalize::Normalize("a", "b/c")); + EXPECT_EQ("../../a", IncludesNormalize::Normalize("a", "b/c")); EXPECT_EQ(".", IncludesNormalize::Normalize("a", "a")); } TEST(IncludesNormalize, Case) { EXPECT_EQ("b", IncludesNormalize::Normalize("Abc\\..\\b", NULL)); EXPECT_EQ("BdEf", IncludesNormalize::Normalize("Abc\\..\\BdEf", NULL)); - EXPECT_EQ("A\\b", IncludesNormalize::Normalize("A\\.\\b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\./b", NULL)); - EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\.\\B", NULL)); - EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\./B", NULL)); + EXPECT_EQ("A/b", IncludesNormalize::Normalize("A\\.\\b", NULL)); + EXPECT_EQ("a/b", IncludesNormalize::Normalize("a\\./b", NULL)); + EXPECT_EQ("A/B", IncludesNormalize::Normalize("A\\.\\B", NULL)); + EXPECT_EQ("A/B", IncludesNormalize::Normalize("A\\./B", NULL)); } TEST(IncludesNormalize, Join) { @@ -92,13 +92,13 @@ TEST(IncludesNormalize, DifferentDrive) { IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "p:\\vs08")); EXPECT_EQ("stuff.h", IncludesNormalize::Normalize("P:\\Vs08\\stuff.h", "p:\\vs08")); - EXPECT_EQ("p:\\vs08\\stuff.h", + EXPECT_EQ("p:/vs08/stuff.h", IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "c:\\vs08")); - EXPECT_EQ("P:\\vs08\\stufF.h", + EXPECT_EQ("P:/vs08/stufF.h", IncludesNormalize::Normalize("P:\\vs08\\stufF.h", "D:\\stuff/things")); - EXPECT_EQ("P:\\vs08\\stuff.h", + EXPECT_EQ("P:/vs08/stuff.h", IncludesNormalize::Normalize("P:/vs08\\stuff.h", "D:\\stuff/things")); - // TODO: this fails; fix it. - //EXPECT_EQ("P:\\wee\\stuff.h", - // IncludesNormalize::Normalize("P:/vs08\\../wee\\stuff.h", "D:\\stuff/things")); + EXPECT_EQ("P:/wee/stuff.h", + IncludesNormalize::Normalize("P:/vs08\\../wee\\stuff.h", + "D:\\stuff/things")); } diff --git a/src/lexer.cc b/src/lexer.cc index 685fe818fc..37b867885c 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -103,8 +103,6 @@ const char* Lexer::TokenErrorHint(Token expected) { string Lexer::DescribeLastError() { if (last_token_) { switch (last_token_[0]) { - case '\r': - return "carriage returns are not allowed, use newlines"; case '\t': return "tabs are not allowed, use spaces"; } @@ -129,7 +127,7 @@ Lexer::Token Lexer::ReadToken() { unsigned int yyaccept = 0; static const unsigned char yybm[] = { 0, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 64, 64, 0, 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, @@ -163,56 +161,66 @@ Lexer::Token Lexer::ReadToken() { }; yych = *p; - if (yych <= '^') { - if (yych <= ',') { - if (yych <= 0x1F) { - if (yych <= 0x00) goto yy22; - if (yych == '\n') goto yy6; - goto yy24; + if (yych <= 'Z') { + if (yych <= '#') { + if (yych <= '\f') { + if (yych <= 0x00) goto yy23; + if (yych == '\n') goto yy7; + goto yy25; } else { - if (yych <= ' ') goto yy2; - if (yych == '#') goto yy4; - goto yy24; + if (yych <= 0x1F) { + if (yych <= '\r') goto yy6; + goto yy25; + } else { + if (yych <= ' ') goto yy2; + if (yych <= '"') goto yy25; + goto yy4; + } } } else { - if (yych <= ':') { - if (yych == '/') goto yy24; - if (yych <= '9') goto yy21; - goto yy15; + if (yych <= '9') { + if (yych <= ',') goto yy25; + if (yych == '/') goto yy25; + goto yy22; } else { - if (yych <= '=') { - if (yych <= '<') goto yy24; - goto yy13; + if (yych <= '<') { + if (yych <= ':') goto yy16; + goto yy25; } else { - if (yych <= '@') goto yy24; - if (yych <= 'Z') goto yy21; - goto yy24; + if (yych <= '=') goto yy14; + if (yych <= '@') goto yy25; + goto yy22; } } } } else { if (yych <= 'i') { - if (yych <= 'b') { - if (yych == '`') goto yy24; - if (yych <= 'a') goto yy21; - goto yy8; + if (yych <= 'a') { + if (yych == '_') goto yy22; + if (yych <= '`') goto yy25; + goto yy22; } else { - if (yych == 'd') goto yy12; - if (yych <= 'h') goto yy21; - goto yy19; + if (yych <= 'c') { + if (yych <= 'b') goto yy9; + goto yy22; + } else { + if (yych <= 'd') goto yy13; + if (yych <= 'h') goto yy22; + goto yy20; + } } } else { if (yych <= 'r') { - if (yych == 'p') goto yy10; - if (yych <= 'q') goto yy21; - goto yy11; + if (yych == 'p') goto yy11; + if (yych <= 'q') goto yy22; + goto yy12; } else { if (yych <= 'z') { - if (yych <= 's') goto yy20; - goto yy21; + if (yych <= 's') goto yy21; + goto yy22; } else { - if (yych == '|') goto yy17; - goto yy24; + if (yych == '|') goto yy18; + goto yy25; } } } @@ -220,192 +228,203 @@ Lexer::Token Lexer::ReadToken() { yy2: yyaccept = 0; yych = *(q = ++p); - goto yy70; + goto yy73; yy3: { token = INDENT; break; } yy4: yyaccept = 1; yych = *(q = ++p); - if (yych <= 0x00) goto yy5; - if (yych != '\r') goto yy65; + if (yych >= 0x01) goto yy68; yy5: { token = ERROR; break; } yy6: - ++p; + yych = *++p; + if (yych == '\n') goto yy65; + goto yy5; yy7: - { token = NEWLINE; break; } -yy8: ++p; - if ((yych = *p) == 'u') goto yy59; - goto yy26; +yy8: + { token = NEWLINE; break; } yy9: - { token = IDENT; break; } + ++p; + if ((yych = *p) == 'u') goto yy60; + goto yy27; yy10: - yych = *++p; - if (yych == 'o') goto yy55; - goto yy26; + { token = IDENT; break; } yy11: yych = *++p; - if (yych == 'u') goto yy51; - goto yy26; + if (yych == 'o') goto yy56; + goto yy27; yy12: yych = *++p; - if (yych == 'e') goto yy44; - goto yy26; + if (yych == 'u') goto yy52; + goto yy27; yy13: + yych = *++p; + if (yych == 'e') goto yy45; + goto yy27; +yy14: ++p; { token = EQUALS; break; } -yy15: +yy16: ++p; { token = COLON; break; } -yy17: +yy18: ++p; - if ((yych = *p) == '|') goto yy42; + if ((yych = *p) == '|') goto yy43; { token = PIPE; break; } -yy19: - yych = *++p; - if (yych == 'n') goto yy35; - goto yy26; yy20: yych = *++p; - if (yych == 'u') goto yy27; - goto yy26; + if (yych == 'n') goto yy36; + goto yy27; yy21: yych = *++p; - goto yy26; + if (yych == 'u') goto yy28; + goto yy27; yy22: + yych = *++p; + goto yy27; +yy23: ++p; { token = TEOF; break; } -yy24: +yy25: yych = *++p; goto yy5; -yy25: +yy26: ++p; yych = *p; -yy26: +yy27: if (yybm[0+yych] & 32) { - goto yy25; + goto yy26; } - goto yy9; -yy27: + goto yy10; +yy28: yych = *++p; - if (yych != 'b') goto yy26; + if (yych != 'b') goto yy27; yych = *++p; - if (yych != 'n') goto yy26; + if (yych != 'n') goto yy27; yych = *++p; - if (yych != 'i') goto yy26; + if (yych != 'i') goto yy27; yych = *++p; - if (yych != 'n') goto yy26; + if (yych != 'n') goto yy27; yych = *++p; - if (yych != 'j') goto yy26; + if (yych != 'j') goto yy27; yych = *++p; - if (yych != 'a') goto yy26; + if (yych != 'a') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = SUBNINJA; break; } -yy35: +yy36: yych = *++p; - if (yych != 'c') goto yy26; + if (yych != 'c') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 'u') goto yy26; + if (yych != 'u') goto yy27; yych = *++p; - if (yych != 'd') goto yy26; + if (yych != 'd') goto yy27; yych = *++p; - if (yych != 'e') goto yy26; + if (yych != 'e') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = INCLUDE; break; } -yy42: +yy43: ++p; { token = PIPE2; break; } -yy44: +yy45: yych = *++p; - if (yych != 'f') goto yy26; + if (yych != 'f') goto yy27; yych = *++p; - if (yych != 'a') goto yy26; + if (yych != 'a') goto yy27; yych = *++p; - if (yych != 'u') goto yy26; + if (yych != 'u') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 't') goto yy26; + if (yych != 't') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = DEFAULT; break; } -yy51: +yy52: yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 'e') goto yy26; + if (yych != 'e') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = RULE; break; } -yy55: +yy56: yych = *++p; - if (yych != 'o') goto yy26; + if (yych != 'o') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = POOL; break; } -yy59: +yy60: yych = *++p; - if (yych != 'i') goto yy26; + if (yych != 'i') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 'd') goto yy26; + if (yych != 'd') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = BUILD; break; } -yy64: +yy65: + ++p; + { token = NEWLINE; break; } +yy67: ++p; yych = *p; -yy65: +yy68: if (yybm[0+yych] & 64) { - goto yy64; + goto yy67; } - if (yych <= 0x00) goto yy66; - if (yych <= '\f') goto yy67; -yy66: + if (yych >= 0x01) goto yy70; +yy69: p = q; if (yyaccept <= 0) { goto yy3; } else { goto yy5; } -yy67: +yy70: ++p; { continue; } -yy69: +yy72: yyaccept = 0; q = ++p; yych = *p; -yy70: +yy73: if (yybm[0+yych] & 128) { - goto yy69; + goto yy72; + } + if (yych <= '\f') { + if (yych != '\n') goto yy3; + } else { + if (yych <= '\r') goto yy75; + if (yych == '#') goto yy67; + goto yy3; } - if (yych == '\n') goto yy71; - if (yych == '#') goto yy64; - goto yy3; -yy71: + yych = *++p; + goto yy8; +yy75: ++p; - yych = *p; - goto yy7; + if ((yych = *p) == '\n') goto yy65; + goto yy69; } } @@ -427,6 +446,7 @@ bool Lexer::PeekToken(Token token) { void Lexer::EatWhitespace() { const char* p = ofs_; + const char* q; for (;;) { ofs_ = p; @@ -468,39 +488,48 @@ void Lexer::EatWhitespace() { }; yych = *p; if (yych <= ' ') { - if (yych <= 0x00) goto yy78; - if (yych <= 0x1F) goto yy80; + if (yych <= 0x00) goto yy82; + if (yych <= 0x1F) goto yy84; } else { - if (yych == '$') goto yy76; - goto yy80; + if (yych == '$') goto yy80; + goto yy84; } ++p; yych = *p; - goto yy84; -yy75: + goto yy92; +yy79: { continue; } -yy76: - ++p; - if ((yych = *p) == '\n') goto yy81; -yy77: +yy80: + yych = *(q = ++p); + if (yych == '\n') goto yy85; + if (yych == '\r') goto yy87; +yy81: { break; } -yy78: +yy82: ++p; { break; } -yy80: +yy84: yych = *++p; - goto yy77; -yy81: + goto yy81; +yy85: ++p; { continue; } -yy83: +yy87: + yych = *++p; + if (yych == '\n') goto yy89; + p = q; + goto yy81; +yy89: + ++p; + { continue; } +yy91: ++p; yych = *p; -yy84: +yy92: if (yybm[0+yych] & 128) { - goto yy83; + goto yy91; } - goto yy75; + goto yy79; } } @@ -550,40 +579,40 @@ bool Lexer::ReadIdent(string* out) { yych = *p; if (yych <= '@') { if (yych <= '.') { - if (yych <= ',') goto yy89; + if (yych <= ',') goto yy97; } else { - if (yych <= '/') goto yy89; - if (yych >= ':') goto yy89; + if (yych <= '/') goto yy97; + if (yych >= ':') goto yy97; } } else { if (yych <= '_') { - if (yych <= 'Z') goto yy87; - if (yych <= '^') goto yy89; + if (yych <= 'Z') goto yy95; + if (yych <= '^') goto yy97; } else { - if (yych <= '`') goto yy89; - if (yych >= '{') goto yy89; + if (yych <= '`') goto yy97; + if (yych >= '{') goto yy97; } } -yy87: +yy95: ++p; yych = *p; - goto yy92; -yy88: + goto yy100; +yy96: { out->assign(start, p - start); break; } -yy89: +yy97: ++p; { return false; } -yy91: +yy99: ++p; yych = *p; -yy92: +yy100: if (yybm[0+yych] & 128) { - goto yy91; + goto yy99; } - goto yy88; + goto yy96; } } @@ -638,29 +667,36 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) { yych = *p; if (yych <= ' ') { if (yych <= '\n') { - if (yych <= 0x00) goto yy101; - if (yych >= '\n') goto yy97; + if (yych <= 0x00) goto yy110; + if (yych >= '\n') goto yy107; } else { - if (yych == '\r') goto yy103; - if (yych >= ' ') goto yy97; + if (yych == '\r') goto yy105; + if (yych >= ' ') goto yy107; } } else { if (yych <= '9') { - if (yych == '$') goto yy99; + if (yych == '$') goto yy109; } else { - if (yych <= ':') goto yy97; - if (yych == '|') goto yy97; + if (yych <= ':') goto yy107; + if (yych == '|') goto yy107; } } ++p; yych = *p; - goto yy126; -yy96: + goto yy140; +yy104: { eval->AddText(StringPiece(start, p - start)); continue; } -yy97: +yy105: + ++p; + if ((yych = *p) == '\n') goto yy137; + { + last_token_ = start; + return Error(DescribeLastError(), err); + } +yy107: ++p; { if (path) { @@ -673,137 +709,152 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) { continue; } } -yy99: - ++p; - if ((yych = *p) <= '/') { - if (yych <= ' ') { - if (yych == '\n') goto yy115; - if (yych <= 0x1F) goto yy104; - goto yy106; +yy109: + 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; + } } else { - if (yych <= '$') { - if (yych <= '#') goto yy104; - goto yy108; + if (yych <= '#') { + if (yych <= ' ') goto yy115; + goto yy112; } else { - if (yych == '-') goto yy110; - goto yy104; + if (yych <= '$') goto yy117; + if (yych <= ',') goto yy112; + goto yy119; } } } else { - if (yych <= '^') { - if (yych <= ':') { - if (yych <= '9') goto yy110; - goto yy112; + if (yych <= 'Z') { + if (yych <= '9') { + if (yych <= '/') goto yy112; + goto yy119; } else { - if (yych <= '@') goto yy104; - if (yych <= 'Z') goto yy110; - goto yy104; + if (yych <= ':') goto yy121; + if (yych <= '@') goto yy112; + goto yy119; } } else { if (yych <= '`') { - if (yych <= '_') goto yy110; - goto yy104; + if (yych == '_') goto yy119; + goto yy112; } else { - if (yych <= 'z') goto yy110; - if (yych <= '{') goto yy114; - goto yy104; + if (yych <= 'z') goto yy119; + if (yych <= '{') goto yy123; + goto yy112; } } } -yy100: - { - last_token_ = start; - return Error(DescribeLastError(), err); - } -yy101: +yy110: ++p; { last_token_ = start; return Error("unexpected EOF", err); } -yy103: - yych = *++p; - goto yy100; -yy104: +yy112: ++p; -yy105: +yy113: { last_token_ = start; return Error("bad $-escape (literal $ must be written as $$)", err); } -yy106: +yy114: + yych = *++p; + if (yych == '\n') goto yy134; + goto yy113; +yy115: ++p; { eval->AddText(StringPiece(" ", 1)); continue; } -yy108: +yy117: ++p; { eval->AddText(StringPiece("$", 1)); continue; } -yy110: +yy119: ++p; yych = *p; - goto yy124; -yy111: + goto yy133; +yy120: { eval->AddSpecial(StringPiece(start + 1, p - start - 1)); continue; } -yy112: +yy121: ++p; { eval->AddText(StringPiece(":", 1)); continue; } -yy114: +yy123: yych = *(q = ++p); if (yybm[0+yych] & 32) { - goto yy118; + goto yy127; } - goto yy105; -yy115: + goto yy113; +yy124: ++p; yych = *p; if (yybm[0+yych] & 16) { - goto yy115; + goto yy124; } { continue; } -yy118: +yy127: ++p; yych = *p; if (yybm[0+yych] & 32) { - goto yy118; + goto yy127; } - if (yych == '}') goto yy121; + if (yych == '}') goto yy130; p = q; - goto yy105; -yy121: + goto yy113; +yy130: ++p; { eval->AddSpecial(StringPiece(start + 2, p - start - 3)); continue; } -yy123: +yy132: ++p; yych = *p; -yy124: +yy133: if (yybm[0+yych] & 64) { - goto yy123; + goto yy132; } - goto yy111; -yy125: + goto yy120; +yy134: ++p; yych = *p; -yy126: + if (yych == ' ') goto yy134; + { + continue; + } +yy137: + ++p; + { + if (path) + p = start; + break; + } +yy139: + ++p; + yych = *p; +yy140: if (yybm[0+yych] & 128) { - goto yy125; + goto yy139; } - goto yy96; + goto yy104; } } diff --git a/src/lexer.in.cc b/src/lexer.in.cc index 93d5540c87..f861239089 100644 --- a/src/lexer.in.cc +++ b/src/lexer.in.cc @@ -102,8 +102,6 @@ const char* Lexer::TokenErrorHint(Token expected) { string Lexer::DescribeLastError() { if (last_token_) { switch (last_token_[0]) { - case '\r': - return "carriage returns are not allowed, use newlines"; case '\t': return "tabs are not allowed, use spaces"; } @@ -132,8 +130,9 @@ Lexer::Token Lexer::ReadToken() { simple_varname = [a-zA-Z0-9_-]+; varname = [a-zA-Z0-9_.-]+; - [ ]*"#"[^\000\r\n]*"\n" { continue; } - [ ]*[\n] { token = NEWLINE; break; } + [ ]*"#"[^\000\n]*"\n" { continue; } + [ ]*"\r\n" { token = NEWLINE; break; } + [ ]*"\n" { token = NEWLINE; break; } [ ]+ { token = INDENT; break; } "build" { token = BUILD; break; } "pool" { token = POOL; break; } @@ -168,13 +167,15 @@ bool Lexer::PeekToken(Token token) { void Lexer::EatWhitespace() { const char* p = ofs_; + const char* q; for (;;) { ofs_ = p; /*!re2c - [ ]+ { continue; } - "$\n" { continue; } - nul { break; } - [^] { break; } + [ ]+ { continue; } + "$\r\n" { continue; } + "$\n" { continue; } + nul { break; } + [^] { break; } */ } } @@ -207,6 +208,11 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) { eval->AddText(StringPiece(start, p - start)); continue; } + "\r\n" { + if (path) + p = start; + break; + } [ :|\n] { if (path) { p = start; @@ -226,6 +232,9 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) { eval->AddText(StringPiece(" ", 1)); continue; } + "$\r\n"[ ]* { + continue; + } "$\n"[ ]* { continue; } diff --git a/src/lexer_test.cc b/src/lexer_test.cc index e8a164254e..331d8e1ea9 100644 --- a/src/lexer_test.cc +++ b/src/lexer_test.cc @@ -14,9 +14,8 @@ #include "lexer.h" -#include - #include "eval_env.h" +#include "test.h" TEST(Lexer, ReadVarValue) { Lexer lexer("plain text $var $VaR ${x}\n"); diff --git a/src/line_printer.cc b/src/line_printer.cc index ef1609c28f..813f63eb5c 100644 --- a/src/line_printer.cc +++ b/src/line_printer.cc @@ -21,6 +21,7 @@ #else #include #include +#include #include #endif diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc index 6fa4f7c8b4..388b5bc679 100644 --- a/src/manifest_parser.cc +++ b/src/manifest_parser.cc @@ -191,7 +191,7 @@ bool ManifestParser::ParseRule(string* err) { bool ManifestParser::ParseLet(string* key, EvalString* value, string* err) { if (!lexer_.ReadIdent(key)) - return false; + return lexer_.Error("expected variable name", err); if (!ExpectToken(Lexer::EQUALS, err)) return false; if (!lexer_.ReadVarValue(value, err)) @@ -209,7 +209,8 @@ bool ManifestParser::ParseDefault(string* err) { do { string path = eval.Evaluate(env_); string path_err; - if (!CanonicalizePath(&path, &path_err)) + unsigned int 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)) return lexer_.Error(path_err, err); @@ -323,16 +324,18 @@ bool ManifestParser::ParseEdge(string* err) { for (vector::iterator i = ins.begin(); i != ins.end(); ++i) { string path = i->Evaluate(env); string path_err; - if (!CanonicalizePath(&path, &path_err)) + unsigned int slash_bits; + if (!CanonicalizePath(&path, &slash_bits, &path_err)) return lexer_.Error(path_err, err); - state_->AddIn(edge, path); + state_->AddIn(edge, path, slash_bits); } for (vector::iterator i = outs.begin(); i != outs.end(); ++i) { string path = i->Evaluate(env); string path_err; - if (!CanonicalizePath(&path, &path_err)) + unsigned int slash_bits; + if (!CanonicalizePath(&path, &slash_bits, &path_err)) return lexer_.Error(path_err, err); - state_->AddOut(edge, path); + state_->AddOut(edge, path, slash_bits); } edge->implicit_deps_ = implicit; edge->order_only_deps_ = order_only; diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index 152b965d78..6909ea9240 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -17,17 +17,16 @@ #include #include -#include - #include "graph.h" #include "state.h" +#include "test.h" struct ParserTest : public testing::Test, public ManifestParser::FileReader { void AssertParse(const char* input) { ManifestParser parser(&state, this); string err; - ASSERT_TRUE(parser.ParseTest(input, &err)) << err; + EXPECT_TRUE(parser.ParseTest(input, &err)); ASSERT_EQ("", err); } @@ -97,7 +96,7 @@ TEST_F(ParserTest, IgnoreIndentedComments) { ASSERT_EQ(2u, state.rules_.size()); const Rule* rule = state.rules_.begin()->second; EXPECT_EQ("cat", rule->name()); - Edge* edge = state.GetNode("result")->in_edge(); + Edge* edge = state.GetNode("result", 0)->in_edge(); EXPECT_TRUE(edge->GetBindingBool("restat")); EXPECT_FALSE(edge->GetBindingBool("generator")); } @@ -270,6 +269,26 @@ TEST_F(ParserTest, CanonicalizeFile) { EXPECT_FALSE(state.LookupNode("in//2")); } +#ifdef _WIN32 +TEST_F(ParserTest, CanonicalizeFileBackslashes) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build out: cat in\\1 in\\\\2\n" +"build in\\1: cat\n" +"build in\\2: cat\n")); + + Node* node = state.LookupNode("in/1");; + EXPECT_TRUE(node); + EXPECT_EQ(1, node->slash_bits()); + node = state.LookupNode("in/2"); + EXPECT_TRUE(node); + EXPECT_EQ(1, node->slash_bits()); + EXPECT_FALSE(state.LookupNode("in//1")); + EXPECT_FALSE(state.LookupNode("in//2")); +} +#endif + TEST_F(ParserTest, PathVariables) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule cat\n" @@ -293,6 +312,34 @@ TEST_F(ParserTest, CanonicalizePaths) { EXPECT_TRUE(state.LookupNode("bar/foo.cc")); } +#ifdef _WIN32 +TEST_F(ParserTest, CanonicalizePathsBackslashes) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build ./out.o: cat ./bar/baz/../foo.cc\n" +"build .\\out2.o: cat .\\bar/baz\\..\\foo.cc\n" +"build .\\out3.o: cat .\\bar\\baz\\..\\foo3.cc\n" +)); + + EXPECT_FALSE(state.LookupNode("./out.o")); + EXPECT_FALSE(state.LookupNode(".\\out2.o")); + EXPECT_FALSE(state.LookupNode(".\\out3.o")); + EXPECT_TRUE(state.LookupNode("out.o")); + EXPECT_TRUE(state.LookupNode("out2.o")); + EXPECT_TRUE(state.LookupNode("out3.o")); + EXPECT_FALSE(state.LookupNode("./bar/baz/../foo.cc")); + EXPECT_FALSE(state.LookupNode(".\\bar/baz\\..\\foo.cc")); + EXPECT_FALSE(state.LookupNode(".\\bar/baz\\..\\foo3.cc")); + Node* node = state.LookupNode("bar/foo.cc"); + EXPECT_TRUE(node); + EXPECT_EQ(0, node->slash_bits()); + node = state.LookupNode("bar/foo3.cc"); + EXPECT_TRUE(node); + EXPECT_EQ(1, node->slash_bits()); +} +#endif + TEST_F(ParserTest, ReservedWords) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule build\n" @@ -549,6 +596,15 @@ TEST_F(ParserTest, Errors) { , err); } + { + State state; + ManifestParser parser(&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); + } + { State state; ManifestParser parser(&state, NULL); @@ -862,22 +918,18 @@ TEST_F(ParserTest, UTF8) { " description = compilaci\xC3\xB3\n")); } -// We might want to eventually allow CRLF to be nice to Windows developers, -// but for now just verify we error out with a nice message. TEST_F(ParserTest, CRLF) { State state; ManifestParser parser(&state, NULL); string err; - EXPECT_FALSE(parser.ParseTest("# comment with crlf\r\n", - &err)); - EXPECT_EQ("input:1: lexing error\n", - err); - - EXPECT_FALSE(parser.ParseTest("foo = foo\nbar = bar\r\n", - &err)); - EXPECT_EQ("input:2: carriage returns are not allowed, use newlines\n" - "bar = bar\r\n" - " ^ near here", - err); + EXPECT_TRUE(parser.ParseTest("# comment with crlf\r\n", &err)); + EXPECT_TRUE(parser.ParseTest("foo = foo\nbar = bar\r\n", &err)); + EXPECT_TRUE(parser.ParseTest( + "pool link_pool\r\n" + " depth = 15\r\n\r\n" + "rule xyz\r\n" + " command = something$expand \r\n" + " description = YAY!\r\n", + &err)); } diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc index c79ec0e9cb..c611919672 100644 --- a/src/minidump-win32.cc +++ b/src/minidump-win32.cc @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NINJA_BOOTSTRAP +#ifdef _MSC_VER #include #include - #include "util.h" typedef BOOL (WINAPI *MiniDumpWriteDumpFunc) ( @@ -85,4 +84,4 @@ void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep) { Warning("minidump created: %s", temp_file); } -#endif // NINJA_BOOTSTRAP +#endif // _MSC_VER diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc index 391c04568c..29db65065d 100644 --- a/src/msvc_helper_test.cc +++ b/src/msvc_helper_test.cc @@ -14,8 +14,6 @@ #include "msvc_helper.h" -#include - #include "test.h" #include "util.h" diff --git a/src/ninja.cc b/src/ninja.cc index a381e833ee..2c890c24cc 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -163,7 +163,8 @@ struct Tool { /// When to run the tool. enum { - /// Run after parsing the command-line flags (as early as possible). + /// Run after parsing the command-line flags and potentially changing + /// the current working directory (as early as possible). RUN_AFTER_FLAGS, /// Run after loading build.ninja. @@ -192,9 +193,6 @@ void Usage(const BuildConfig& config) { "\n" " -j N run N jobs in parallel [default=%d, derived from CPUs available]\n" " -l N do not start new jobs if the load average is greater than N\n" -#ifdef _WIN32 -" (not yet implemented on Windows)\n" -#endif " -k N keep going until N jobs fail [default=1]\n" " -n dry run (don't run commands but act like they succeeded)\n" " -v show all command lines while building\n" @@ -230,7 +228,8 @@ struct RealFileReader : public ManifestParser::FileReader { /// Returns true if the manifest was rebuilt. bool NinjaMain::RebuildManifest(const char* input_file, string* err) { string path = input_file; - if (!CanonicalizePath(&path, err)) + unsigned int slash_bits; // Unused because this path is only used for lookup. + if (!CanonicalizePath(&path, &slash_bits, err)) return false; Node* node = state_.LookupNode(path); if (!node) @@ -252,7 +251,8 @@ bool NinjaMain::RebuildManifest(const char* input_file, string* err) { Node* NinjaMain::CollectTarget(const char* cpath, string* err) { string path = cpath; - if (!CanonicalizePath(&path, err)) + unsigned int slash_bits; // Unused because this path is only used for lookup. + if (!CanonicalizePath(&path, &slash_bits, err)) return NULL; // Special syntax: "foo.cc^" means "the first output of foo.cc". @@ -365,7 +365,7 @@ int NinjaMain::ToolQuery(int argc, char* argv[]) { return 0; } -#if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP) +#if defined(NINJA_HAVE_BROWSE) int NinjaMain::ToolBrowse(int argc, char* argv[]) { if (argc < 1) { Error("expected a target to browse"); @@ -698,7 +698,7 @@ int NinjaMain::ToolUrtle(int argc, char** argv) { /// Returns a Tool, or NULL if Ninja should exit. const Tool* ChooseTool(const string& tool_name) { static const Tool kTools[] = { -#if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP) +#if defined(NINJA_HAVE_BROWSE) { "browse", "browse dependency graph in a web browser", Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse }, #endif @@ -1042,13 +1042,6 @@ int real_main(int argc, char** argv) { if (exit_code >= 0) return exit_code; - if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) { - // 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)(argc, argv); - } - if (options.working_dir) { // The formatting of this string, complete with funny quotes, is // so Emacs can properly identify that the cwd has changed for @@ -1062,6 +1055,13 @@ int real_main(int argc, char** argv) { } } + if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) { + // 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)(argc, argv); + } + // The build can take up to 2 passes: one to rebuild the manifest, then // another to build the desired target. for (int cycle = 0; cycle < 2; ++cycle) { @@ -1111,7 +1111,7 @@ int real_main(int argc, char** argv) { } // anonymous namespace int main(int argc, char** argv) { -#if !defined(NINJA_BOOTSTRAP) && defined(_MSC_VER) +#if defined(_MSC_VER) // Set a handler to catch crashes not caught by the __try..__except // block (e.g. an exception in a stack-unwind-block). set_terminate(TerminateHandler); diff --git a/src/ninja_test.cc b/src/ninja_test.cc index 989ea5c72e..54d87844b6 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -15,9 +15,35 @@ #include #include -#include "gtest/gtest.h" +#ifdef _WIN32 +#include "getopt.h" +#else +#include +#endif + +#include "test.h" #include "line_printer.h" +struct RegisteredTest { + testing::Test* (*factory)(); + const char *name; + bool should_run; +}; +// This can't be a vector because tests call RegisterTest from static +// initializers and the order static initializers run it isn't specified. So +// the vector constructor isn't guaranteed to run before all of the +// RegisterTest() calls. +static RegisteredTest tests[10000]; +testing::Test* g_current_test; +static int ntests; +static LinePrinter printer; + +void RegisterTest(testing::Test* (*factory)(), const char* name) { + tests[ntests].factory = factory; + tests[ntests++].name = name; +} + +namespace { string StringPrintf(const char* format, ...) { const int N = 1024; char buf[N]; @@ -30,59 +56,101 @@ string StringPrintf(const char* format, ...) { return buf; } -/// A test result printer that's less wordy than gtest's default. -struct LaconicPrinter : public testing::EmptyTestEventListener { - LaconicPrinter() : tests_started_(0), test_count_(0), iteration_(0) {} - virtual void OnTestProgramStart(const testing::UnitTest& unit_test) { - test_count_ = unit_test.test_to_run_count(); - } +void Usage() { + fprintf(stderr, +"usage: ninja_tests [options]\n" +"\n" +"options:\n" +" --gtest_filter=POSTIVE_PATTERN[-NEGATIVE_PATTERN]\n" +" Run tests whose names match the positive but not the negative pattern.\n" +" '*' matches any substring. (gtest's ':', '?' are not implemented).\n"); +} - virtual void OnTestIterationStart(const testing::UnitTest& test_info, - int iteration) { - tests_started_ = 0; - iteration_ = iteration; +bool PatternMatchesString(const char* pattern, const char* str) { + switch (*pattern) { + case '\0': + case '-': return *str == '\0'; + case '*': return (*str != '\0' && PatternMatchesString(pattern, str + 1)) || + PatternMatchesString(pattern + 1, str); + default: return *pattern == *str && + PatternMatchesString(pattern + 1, str + 1); } +} - virtual void OnTestStart(const testing::TestInfo& test_info) { - ++tests_started_; - printer_.Print( - StringPrintf("[%d/%d%s] %s.%s", - tests_started_, - test_count_, - iteration_ ? StringPrintf(" iter %d", iteration_).c_str() - : "", - test_info.test_case_name(), - test_info.name()), - LinePrinter::ELIDE); - } +bool TestMatchesFilter(const char* test, const char* filter) { + // Split --gtest_filter at '-' into positive and negative filters. + const char* const dash = strchr(filter, '-'); + const char* pos = dash == filter ? "*" : filter; //Treat '-test1' as '*-test1' + const char* neg = dash ? dash + 1 : ""; + return PatternMatchesString(pos, test) && !PatternMatchesString(neg, test); +} - virtual void OnTestPartResult( - const testing::TestPartResult& test_part_result) { - if (!test_part_result.failed()) - return; - printer_.PrintOnNewLine(StringPrintf( - "*** Failure in %s:%d\n%s\n", test_part_result.file_name(), - test_part_result.line_number(), test_part_result.summary())); - } +bool ReadFlags(int* argc, char*** argv, const char** test_filter) { + enum { OPT_GTEST_FILTER = 1 }; + const option kLongOptions[] = { + { "gtest_filter", required_argument, NULL, OPT_GTEST_FILTER }, + { NULL, 0, NULL, 0 } + }; - virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) { - printer_.PrintOnNewLine(unit_test.Passed() ? "passed\n" : "failed\n"); + int opt; + while ((opt = getopt_long(*argc, *argv, "h", kLongOptions, NULL)) != -1) { + switch (opt) { + case OPT_GTEST_FILTER: + if (strchr(optarg, '?') == NULL && strchr(optarg, ':') == NULL) { + *test_filter = optarg; + break; + } // else fall through. + default: + Usage(); + return false; + } } + *argv += optind; + *argc -= optind; + return true; +} - private: - LinePrinter printer_; - int tests_started_; - int test_count_; - int iteration_; -}; +} // namespace + +bool testing::Test::Check(bool condition, const char* file, int line, + const char* error) { + if (!condition) { + printer.PrintOnNewLine( + StringPrintf("*** Failure in %s:%d\n%s\n", file, line, error)); + failed_ = true; + } + return condition; +} int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); + int tests_started = 0; - testing::TestEventListeners& listeners = - testing::UnitTest::GetInstance()->listeners(); - delete listeners.Release(listeners.default_result_printer()); - listeners.Append(new LaconicPrinter); + const char* test_filter = "*"; + if (!ReadFlags(&argc, &argv, &test_filter)) + return 1; + + int nactivetests = 0; + for (int i = 0; i < ntests; i++) + if ((tests[i].should_run = TestMatchesFilter(tests[i].name, test_filter))) + ++nactivetests; + + bool passed = true; + for (int i = 0; i < ntests; i++) { + if (!tests[i].should_run) continue; + + ++tests_started; + testing::Test* test = tests[i].factory(); + printer.Print( + StringPrintf("[%d/%d] %s", tests_started, nactivetests, tests[i].name), + LinePrinter::ELIDE); + test->SetUp(); + test->Run(); + test->TearDown(); + if (test->Failed()) + passed = false; + delete test; + } - return RUN_ALL_TESTS(); + printer.PrintOnNewLine(passed ? "passed\n" : "failed\n"); + return passed ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/state.cc b/src/state.cc index 7258272056..6e3e10d65a 100644 --- a/src/state.cc +++ b/src/state.cc @@ -111,11 +111,11 @@ Edge* State::AddEdge(const Rule* rule) { return edge; } -Node* State::GetNode(StringPiece path) { +Node* State::GetNode(StringPiece path, unsigned int slash_bits) { Node* node = LookupNode(path); if (node) return node; - node = new Node(path.AsString()); + node = new Node(path.AsString(), slash_bits); paths_[node->path()] = node; return node; } @@ -145,14 +145,14 @@ Node* State::SpellcheckNode(const string& path) { return result; } -void State::AddIn(Edge* edge, StringPiece path) { - Node* node = GetNode(path); +void State::AddIn(Edge* edge, StringPiece path, unsigned int slash_bits) { + Node* node = GetNode(path, slash_bits); edge->inputs_.push_back(node); node->AddOutEdge(edge); } -void State::AddOut(Edge* edge, StringPiece path) { - Node* node = GetNode(path); +void State::AddOut(Edge* edge, StringPiece path, unsigned int slash_bits) { + Node* node = GetNode(path, slash_bits); edge->outputs_.push_back(node); if (node->in_edge()) { Warning("multiple rules generate %s. " diff --git a/src/state.h b/src/state.h index c382dc014c..880453284c 100644 --- a/src/state.h +++ b/src/state.h @@ -95,12 +95,12 @@ struct State { Edge* AddEdge(const Rule* rule); - Node* GetNode(StringPiece path); + Node* GetNode(StringPiece path, unsigned int slash_bits); Node* LookupNode(StringPiece path) const; Node* SpellcheckNode(const string& path); - void AddIn(Edge* edge, StringPiece path); - void AddOut(Edge* edge, StringPiece path); + void AddIn(Edge* edge, StringPiece path, unsigned int slash_bits); + void AddOut(Edge* edge, StringPiece path, unsigned int slash_bits); bool AddDefault(StringPiece path, string* error); /// Reset state. Keeps all nodes and edges, but restores them to the diff --git a/src/state_test.cc b/src/state_test.cc index af2bff19b3..bd133b60b1 100644 --- a/src/state_test.cc +++ b/src/state_test.cc @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include "graph.h" #include "state.h" +#include "test.h" namespace { @@ -33,15 +32,15 @@ TEST(State, Basic) { state.AddRule(rule); Edge* edge = state.AddEdge(rule); - state.AddIn(edge, "in1"); - state.AddIn(edge, "in2"); - state.AddOut(edge, "out"); + state.AddIn(edge, "in1", 0); + state.AddIn(edge, "in2", 0); + state.AddOut(edge, "out", 0); EXPECT_EQ("cat in1 in2 > out", edge->EvaluateCommand()); - EXPECT_FALSE(state.GetNode("in1")->dirty()); - EXPECT_FALSE(state.GetNode("in2")->dirty()); - EXPECT_FALSE(state.GetNode("out")->dirty()); + EXPECT_FALSE(state.GetNode("in1", 0)->dirty()); + EXPECT_FALSE(state.GetNode("in2", 0)->dirty()); + EXPECT_FALSE(state.GetNode("out", 0)->dirty()); } } // namespace diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index 775a13a6c9..8a0787c987 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -18,6 +18,7 @@ #ifndef _WIN32 // SetWithLots need setrlimit. +#include #include #include #include @@ -92,7 +93,7 @@ TEST_F(SubprocessTest, InterruptParent) { return; } - ADD_FAILURE() << "We should have been interrupted"; + ASSERT_FALSE("We should have been interrupted"); } TEST_F(SubprocessTest, Console) { @@ -171,14 +172,15 @@ TEST_F(SubprocessTest, SetWithMulti) { TEST_F(SubprocessTest, SetWithLots) { // Arbitrary big number; needs to be over 1024 to confirm we're no longer // hostage to pselect. - const size_t kNumProcs = 1025; + const unsigned kNumProcs = 1025; // Make sure [ulimit -n] isn't going to stop us from working. rlimit rlim; ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlim)); - ASSERT_GT(rlim.rlim_cur, kNumProcs) - << "Raise [ulimit -n] well above " << kNumProcs - << " to make this test go"; + if (!EXPECT_GT(rlim.rlim_cur, kNumProcs)) { + printf("Raise [ulimit -n] well above %u to make this test go\n", kNumProcs); + return; + } vector procs; for (size_t i = 0; i < kNumProcs; ++i) { diff --git a/src/test.cc b/src/test.cc index 21015ed34e..f667fef0f3 100644 --- a/src/test.cc +++ b/src/test.cc @@ -12,20 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifdef _WIN32 +#include // Has to be before util.h is included. +#endif + #include "test.h" #include #include +#ifdef _WIN32 +#include +#else +#include +#endif #include "build_log.h" #include "manifest_parser.h" #include "util.h" -#ifdef _WIN32 -#include -#endif - namespace { #ifdef _WIN32 @@ -84,13 +89,14 @@ void StateTestWithBuiltinRules::AddCatRule(State* state) { } Node* StateTestWithBuiltinRules::GetNode(const string& path) { - return state_.GetNode(path); + EXPECT_FALSE(strpbrk(path.c_str(), "/\\")); + return state_.GetNode(path, 0); } void AssertParse(State* state, const char* input) { ManifestParser parser(state, NULL); string err; - ASSERT_TRUE(parser.ParseTest(input, &err)) << err; + EXPECT_TRUE(parser.ParseTest(input, &err)); ASSERT_EQ("", err); } diff --git a/src/test.h b/src/test.h index f34b877911..4c152039d7 100644 --- a/src/test.h +++ b/src/test.h @@ -15,12 +15,94 @@ #ifndef NINJA_TEST_H_ #define NINJA_TEST_H_ -#include - #include "disk_interface.h" #include "state.h" #include "util.h" +// A tiny testing framework inspired by googletest, but much simpler and +// faster to compile. It supports most things commonly used from googltest. The +// most noticeable things missing: EXPECT_* and ASSERT_* don't support +// streaming notes to them with operator<<, and for failing tests the lhs and +// rhs are not printed. That's so that this header does not have to include +// sstream, which slows down building ninja_test almost 20%. +namespace testing { +class Test { + bool failed_; + int assertion_failures_; + public: + Test() : failed_(false), assertion_failures_(0) {} + virtual ~Test() {} + virtual void SetUp() {} + virtual void TearDown() {} + virtual void Run() = 0; + + bool Failed() const { return failed_; } + int AssertionFailures() const { return assertion_failures_; } + void AddAssertionFailure() { assertion_failures_++; } + bool Check(bool condition, const char* file, int line, const char* error); +}; +} + +void RegisterTest(testing::Test* (*)(), const char*); + +extern testing::Test* g_current_test; +#define TEST_F_(x, y, name) \ + struct y : public x { \ + static testing::Test* Create() { return g_current_test = new y; } \ + virtual void Run(); \ + }; \ + struct Register##y { \ + Register##y() { RegisterTest(y::Create, name); } \ + }; \ + Register##y g_register_##y; \ + void y::Run() + +#define TEST_F(x, y) TEST_F_(x, x##y, #x "." #y) +#define TEST(x, y) TEST_F_(testing::Test, x##y, #x "." #y) + +#define EXPECT_EQ(a, b) \ + g_current_test->Check(a == b, __FILE__, __LINE__, #a " == " #b) +#define EXPECT_NE(a, b) \ + g_current_test->Check(a != b, __FILE__, __LINE__, #a " != " #b) +#define EXPECT_GT(a, b) \ + g_current_test->Check(a > b, __FILE__, __LINE__, #a " > " #b) +#define EXPECT_LT(a, b) \ + g_current_test->Check(a < b, __FILE__, __LINE__, #a " < " #b) +#define EXPECT_GE(a, b) \ + g_current_test->Check(a >= b, __FILE__, __LINE__, #a " >= " #b) +#define EXPECT_LE(a, b) \ + g_current_test->Check(a <= b, __FILE__, __LINE__, #a " <= " #b) +#define EXPECT_TRUE(a) \ + g_current_test->Check(static_cast(a), __FILE__, __LINE__, #a) +#define EXPECT_FALSE(a) \ + g_current_test->Check(!static_cast(a), __FILE__, __LINE__, #a) + +#define ASSERT_EQ(a, b) \ + if (!EXPECT_EQ(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_NE(a, b) \ + if (!EXPECT_NE(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_GT(a, b) \ + if (!EXPECT_GT(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_LT(a, b) \ + if (!EXPECT_LT(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_GE(a, b) \ + if (!EXPECT_GE(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_LE(a, b) \ + if (!EXPECT_LE(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_TRUE(a) \ + if (!EXPECT_TRUE(a)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_FALSE(a) \ + if (!EXPECT_FALSE(a)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_NO_FATAL_FAILURE(a) \ + { \ + int f = g_current_test->AssertionFailures(); \ + a; \ + if (f != g_current_test->AssertionFailures()) { \ + g_current_test->AddAssertionFailure(); \ + return; \ + } \ + } + // Support utilites for tests. struct Node; diff --git a/src/util.cc b/src/util.cc index 484b0c158a..746d7ed1aa 100644 --- a/src/util.cc +++ b/src/util.cc @@ -85,19 +85,33 @@ void Error(const char* msg, ...) { fprintf(stderr, "\n"); } -bool CanonicalizePath(string* path, string* err) { +bool CanonicalizePath(string* path, unsigned int* slash_bits, string* err) { METRIC_RECORD("canonicalize str"); size_t len = path->size(); char* str = 0; if (len > 0) str = &(*path)[0]; - if (!CanonicalizePath(str, &len, err)) + if (!CanonicalizePath(str, &len, slash_bits, err)) return false; path->resize(len); return true; } -bool CanonicalizePath(char* path, size_t* len, string* err) { +#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; +} +#endif + +bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits, + string* err) { // WARNING: this function is performance-critical; please benchmark // any changes you make to it. METRIC_RECORD("canonicalize path"); @@ -115,12 +129,37 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { const char* src = start; const char* end = start + *len; +#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) == '/') { src += 2; dst += 2; + bits_offset++; } else { ++src; ++dst; @@ -136,6 +175,9 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { if (src + 1 == end || src[1] == '/') { // '.' component; eliminate. src += 2; +#ifdef _WIN32 + bits = ShiftOverBit(bits_offset, bits); +#endif continue; } else if (src[1] == '.' && (src + 2 == end || src[2] == '/')) { // '..' component. Back up if possible. @@ -143,6 +185,11 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { 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++; @@ -154,6 +201,9 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { if (*src == '/') { src++; +#ifdef _WIN32 + bits = ShiftOverBit(bits_offset, bits); +#endif continue; } @@ -164,6 +214,9 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { while (*src != '/' && src != end) *dst++ = *src++; +#ifdef _WIN32 + bits_offset++; +#endif *dst++ = *src++; // Copy '/' or final \0 character as well. } @@ -173,6 +226,11 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { } *len = dst - start - 1; +#ifdef _WIN32 + *slash_bits = bits; +#else + *slash_bits = 0; +#endif return true; } @@ -280,7 +338,38 @@ void GetWin32EscapedString(const string& input, string* result) { } int ReadFile(const string& path, string* contents, string* err) { - FILE* f = fopen(path.c_str(), "r"); +#ifdef _WIN32 + // 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); + if (f == INVALID_HANDLE_VALUE) { + err->assign(GetLastErrorString()); + return -ENOENT; + } + + for (;;) { + DWORD len; + char buf[64 << 10]; + if (!::ReadFile(f, buf, sizeof(buf), &len, NULL)) { + err->assign(GetLastErrorString()); + contents->clear(); + return -1; + } + if (len == 0) + break; + contents->append(buf, len); + } + ::CloseHandle(f); + return 0; +#else + FILE* f = fopen(path.c_str(), "rb"); if (!f) { err->assign(strerror(errno)); return -errno; @@ -299,6 +388,7 @@ int ReadFile(const string& path, string* contents, string* err) { } fclose(f); return 0; +#endif } void SetCloseOnExec(int fd) { @@ -415,10 +505,70 @@ int GetProcessorCount() { } #if defined(_WIN32) || defined(__CYGWIN__) +static double CalculateProcessorLoad(uint64_t idle_ticks, uint64_t total_ticks) +{ + static uint64_t previous_idle_ticks = 0; + static uint64_t previous_total_ticks = 0; + static double previous_load = -0.0; + + uint64_t idle_ticks_since_last_time = idle_ticks - previous_idle_ticks; + uint64_t total_ticks_since_last_time = total_ticks - previous_total_ticks; + + bool first_call = (previous_total_ticks == 0); + bool ticks_not_updated_since_last_call = (total_ticks_since_last_time == 0); + + double load; + if (first_call || ticks_not_updated_since_last_call) { + load = previous_load; + } else { + // Calculate load. + double idle_to_total_ratio = + ((double)idle_ticks_since_last_time) / total_ticks_since_last_time; + double load_since_last_call = 1.0 - idle_to_total_ratio; + + // Filter/smooth result when possible. + if(previous_load > 0) { + load = 0.9 * previous_load + 0.1 * load_since_last_call; + } else { + load = load_since_last_call; + } + } + + previous_load = load; + previous_total_ticks = total_ticks; + previous_idle_ticks = idle_ticks; + + return load; +} + +static uint64_t FileTimeToTickCount(const FILETIME & ft) +{ + uint64_t high = (((uint64_t)(ft.dwHighDateTime)) << 32); + uint64_t low = ft.dwLowDateTime; + return (high | low); +} + double GetLoadAverage() { - // TODO(nicolas.despres@gmail.com): Find a way to implement it on Windows. - // Remember to also update Usage() when this is fixed. - return -0.0f; + FILETIME idle_time, kernel_time, user_time; + BOOL get_system_time_succeeded = + GetSystemTimes(&idle_time, &kernel_time, &user_time); + + double posix_compatible_load; + if (get_system_time_succeeded) { + uint64_t idle_ticks = FileTimeToTickCount(idle_time); + + // kernel_time from GetSystemTimes already includes idle_time. + uint64_t total_ticks = + FileTimeToTickCount(kernel_time) + FileTimeToTickCount(user_time); + + double processor_load = CalculateProcessorLoad(idle_ticks, total_ticks); + posix_compatible_load = processor_load * GetProcessorCount(); + + } else { + posix_compatible_load = -0.0; + } + + return posix_compatible_load; } #else double GetLoadAverage() { diff --git a/src/util.h b/src/util.h index 71017703ff..cbdc1a61f5 100644 --- a/src/util.h +++ b/src/util.h @@ -41,9 +41,11 @@ void Warning(const char* msg, ...); void Error(const char* msg, ...); /// Canonicalize a path like "foo/../bar.h" into just "bar.h". -bool CanonicalizePath(string* path, string* err); - -bool CanonicalizePath(char* path, size_t* len, string* err); +/// |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, + string* err); /// Appends |input| to |*result|, escaping according to the whims of either /// Bash, or Win32's CommandLineToArgvW(). diff --git a/src/util_test.cc b/src/util_test.cc index b58d15e8ce..8ca7f56209 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -16,6 +16,15 @@ #include "test.h" +namespace { + +bool CanonicalizePath(string* path, string* err) { + unsigned int unused; + return ::CanonicalizePath(path, &unused, err); +} + +} // namespace + TEST(CanonicalizePath, PathSamples) { string path; string err; @@ -84,6 +93,201 @@ TEST(CanonicalizePath, PathSamples) { EXPECT_EQ("", path); } +#ifdef _WIN32 +TEST(CanonicalizePath, PathSamplesWindows) { + string path; + string err; + + EXPECT_FALSE(CanonicalizePath(&path, &err)); + EXPECT_EQ("empty path", err); + + path = "foo.h"; err = ""; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo.h", path); + + path = ".\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo.h", path); + + path = ".\\foo\\.\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo/bar.h", path); + + path = ".\\x\\foo\\..\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("x/bar.h", path); + + path = ".\\x\\foo\\..\\..\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("bar.h", path); + + path = "foo\\\\bar"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo/bar", path); + + path = "foo\\\\.\\\\..\\\\\\bar"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("bar", path); + + path = ".\\x\\..\\foo\\..\\..\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("../bar.h", path); + + path = "foo\\.\\."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo", path); + + path = "foo\\bar\\.."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo", path); + + path = "foo\\.hidden_bar"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo/.hidden_bar", path); + + path = "\\foo"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("/foo", path); + + path = "\\\\foo"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("//foo", path); + + path = "\\"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("", path); +} + +TEST(CanonicalizePath, SlashTracking) { + string path; + string err; + unsigned int slash_bits; + + path = "foo.h"; err = ""; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("foo.h", path); + EXPECT_EQ(0, slash_bits); + + path = "a\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a/bcd/efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(4, slash_bits); + + path = "a\\bcd/efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(5, slash_bits); + + path = "a\\bcd\\efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(7, slash_bits); + + path = "a/bcd/efh/foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(0, slash_bits); + + path = "a\\./efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/efh/foo.h", path); + EXPECT_EQ(3, slash_bits); + + path = "a\\../efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("efh/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\b\\c\\d\\e\\f\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/b/c/d/e/f/g/foo.h", path); + EXPECT_EQ(127, slash_bits); + + path = "a\\b\\c\\..\\..\\..\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("g/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\b/c\\../../..\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("g/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\b/c\\./../..\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/g/foo.h", path); + EXPECT_EQ(3, slash_bits); + + path = "a\\b/c\\./../..\\g/foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/g/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\\\\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a/\\\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(0, slash_bits); + + path = "a\\//foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(1, slash_bits); +} + +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; + string err; + size_t size = 13; + EXPECT_TRUE(::CanonicalizePath(buf, &size, &slash_bits, &err)); + EXPECT_EQ(0, strncmp("foo/bar/baz.h", buf, size)); + EXPECT_EQ(2, slash_bits); // Not including the trailing one. +} + +TEST(CanonicalizePath, TooManyComponents) { + string path; + string err; + unsigned int slash_bits; + + // 32 is OK. + 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)); + + // 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); + + // 33 is not. + 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"); + + // 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"); +} +#endif + TEST(CanonicalizePath, EmptyResult) { string path; string err; @@ -122,26 +326,27 @@ TEST(CanonicalizePath, NotNullTerminated) { string path; string err; size_t len; + unsigned int unused; path = "foo/. bar/."; len = strlen("foo/."); // Canonicalize only the part before the space. - EXPECT_TRUE(CanonicalizePath(&path[0], &len, &err)); + EXPECT_TRUE(CanonicalizePath(&path[0], &len, &unused, &err)); EXPECT_EQ(strlen("foo"), len); EXPECT_EQ("foo/. bar/.", string(path)); path = "foo/../file bar/."; len = strlen("foo/../file"); - EXPECT_TRUE(CanonicalizePath(&path[0], &len, &err)); + EXPECT_TRUE(CanonicalizePath(&path[0], &len, &unused, &err)); EXPECT_EQ(strlen("file"), len); EXPECT_EQ("file ./file bar/.", string(path)); } TEST(PathEscaping, TortureTest) { string result; - + GetWin32EscapedString("foo bar\\\"'$@d!st!c'\\path'\\", &result); EXPECT_EQ("\"foo bar\\\\\\\"'$@d!st!c'\\path'\\\\\"", result); - result.clear(); + result.clear(); GetShellEscapedString("foo bar\"/'$@d!st!c'/path'", &result); EXPECT_EQ("'foo bar\"/'\\''$@d!st!c'\\''/path'\\'''", result); diff --git a/src/version.cc b/src/version.cc index 3fb729fe44..2d2d9c010b 100644 --- a/src/version.cc +++ b/src/version.cc @@ -18,7 +18,7 @@ #include "util.h" -const char* kNinjaVersion = "1.5.1"; +const char* kNinjaVersion = "1.5.3"; void ParseVersion(const string& version, int* major, int* minor) { size_t end = version.find('.');