summaryrefslogtreecommitdiffstats
path: root/misc
diff options
context:
space:
mode:
Diffstat (limited to 'misc')
-rwxr-xr-xmisc/measure.py4
-rw-r--r--misc/ninja-mode.el37
-rw-r--r--misc/ninja_syntax.py74
-rwxr-xr-xmisc/ninja_syntax_test.py2
-rwxr-xr-xmisc/output_test.py52
-rwxr-xr-x[-rw-r--r--]misc/write_fake_manifests.py2
-rw-r--r--misc/zsh-completion37
7 files changed, 150 insertions, 58 deletions
diff --git a/misc/measure.py b/misc/measure.py
index 8ce95e6..f3825ef 100755
--- a/misc/measure.py
+++ b/misc/measure.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright 2011 Google Inc. All Rights Reserved.
#
@@ -17,8 +17,6 @@
"""measure the runtime of a command by repeatedly running it.
"""
-from __future__ import print_function
-
import time
import subprocess
import sys
diff --git a/misc/ninja-mode.el b/misc/ninja-mode.el
index 8b975d5..d4f06e6 100644
--- a/misc/ninja-mode.el
+++ b/misc/ninja-mode.el
@@ -19,16 +19,22 @@
;;; Commentary:
;; Simple emacs mode for editing .ninja files.
-;; Just some syntax highlighting for now.
;;; Code:
+(defcustom ninja-indent-offset 2
+ "*Amount of offset per level of indentation."
+ :type 'integer
+ :safe 'natnump
+ :group 'ninja)
+
+(defconst ninja-keywords-re
+ (concat "^" (regexp-opt '("rule" "build" "subninja" "include" "pool" "default")
+ 'words)))
+
(defvar ninja-keywords
- `((,(concat "^" (regexp-opt '("rule" "build" "subninja" "include"
- "pool" "default")
- 'words))
- . font-lock-keyword-face)
- ("\\([[:alnum:]_]+\\) =" 1 font-lock-variable-name-face)
+ `((,ninja-keywords-re . font-lock-keyword-face)
+ ("^[[:space:]]*\\([[:alnum:]_]+\\)[[:space:]]*=" 1 font-lock-variable-name-face)
;; Variable expansion.
("$[[:alnum:]_]+" . font-lock-variable-name-face)
("${[[:alnum:]._]+}" . font-lock-variable-name-face)
@@ -69,11 +75,30 @@
(unless (= line-end (1+ (buffer-size)))
(put-text-property line-end (1+ line-end) 'syntax-table '(12)))))))))
+(defun ninja-compute-indentation ()
+ "Calculate indentation for the current line."
+ (save-excursion
+ (beginning-of-line)
+ (if (or (looking-at ninja-keywords-re)
+ (= (line-number-at-pos) 1))
+ 0
+ (forward-line -1)
+ (if (looking-at ninja-keywords-re)
+ ninja-indent-offset
+ (current-indentation)))))
+
+(defun ninja-indent-line ()
+ "Indent the current line. Uses previous indentation level if
+ available or `ninja-indent-offset'"
+ (interactive "*")
+ (indent-line-to (ninja-compute-indentation)))
+
;;;###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)
+ (set (make-local-variable 'indent-line-function) 'ninja-indent-line)
(setq font-lock-defaults '(ninja-keywords)))
;; Run ninja-mode for files ending in .ninja.
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index ca73b5b..2aa8456 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -23,37 +23,54 @@ use Python.
import re
import textwrap
+from io import TextIOWrapper
+from typing import Dict, List, Match, Optional, Tuple, Union
-def escape_path(word):
+def escape_path(word: str) -> str:
return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
class Writer(object):
- def __init__(self, output, width=78):
+ def __init__(self, output: TextIOWrapper, width: int = 78) -> None:
self.output = output
self.width = width
- def newline(self):
+ def newline(self) -> None:
self.output.write('\n')
- def comment(self, text):
+ def comment(self, text: str) -> None:
for line in textwrap.wrap(text, self.width - 2, break_long_words=False,
break_on_hyphens=False):
self.output.write('# ' + line + '\n')
- def variable(self, key, value, indent=0):
+ def variable(
+ self,
+ key: str,
+ value: Optional[Union[bool, int, float, str, List[str]]],
+ indent: int = 0,
+ ) -> None:
if value is None:
return
if isinstance(value, list):
value = ' '.join(filter(None, value)) # Filter out empty strings.
self._line('%s = %s' % (key, value), indent)
- def pool(self, name, depth):
+ def pool(self, name: str, depth: int) -> None:
self._line('pool %s' % name)
self.variable('depth', depth, indent=1)
- def rule(self, name, command, description=None, depfile=None,
- generator=False, pool=None, restat=False, rspfile=None,
- rspfile_content=None, deps=None):
+ def rule(
+ self,
+ name: str,
+ command: str,
+ description: Optional[str] = None,
+ depfile: Optional[str] = None,
+ generator: bool = False,
+ pool: Optional[str] = None,
+ restat: bool = False,
+ rspfile: Optional[str] = None,
+ rspfile_content: Optional[str] = None,
+ deps: Optional[Union[str, List[str]]] = None,
+ ) -> None:
self._line('rule %s' % name)
self.variable('command', command, indent=1)
if description:
@@ -73,8 +90,23 @@ class Writer(object):
if deps:
self.variable('deps', deps, indent=1)
- def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
- variables=None, implicit_outputs=None, pool=None, dyndep=None):
+ def build(
+ self,
+ outputs: Union[str, List[str]],
+ rule: str,
+ inputs: Optional[Union[str, List[str]]] = None,
+ implicit: Optional[Union[str, List[str]]] = None,
+ order_only: Optional[Union[str, List[str]]] = None,
+ variables: Optional[
+ Union[
+ List[Tuple[str, Optional[Union[str, List[str]]]]],
+ Dict[str, Optional[Union[str, List[str]]]],
+ ]
+ ] = None,
+ implicit_outputs: Optional[Union[str, List[str]]] = None,
+ pool: Optional[str] = None,
+ dyndep: Optional[str] = None,
+ ) -> List[str]:
outputs = as_list(outputs)
out_outputs = [escape_path(x) for x in outputs]
all_inputs = [escape_path(x) for x in as_list(inputs)]
@@ -111,16 +143,16 @@ class Writer(object):
return outputs
- def include(self, path):
+ def include(self, path: str) -> None:
self._line('include %s' % path)
- def subninja(self, path):
+ def subninja(self, path: str) -> None:
self._line('subninja %s' % path)
- def default(self, paths):
+ def default(self, paths: Union[str, List[str]]) -> None:
self._line('default %s' % ' '.join(as_list(paths)))
- def _count_dollars_before_index(self, s, i):
+ def _count_dollars_before_index(self, s: str, i: int) -> int:
"""Returns the number of '$' characters right in front of s[i]."""
dollar_count = 0
dollar_index = i - 1
@@ -129,7 +161,7 @@ class Writer(object):
dollar_index -= 1
return dollar_count
- def _line(self, text, indent=0):
+ def _line(self, text: str, indent: int = 0) -> None:
"""Write 'text' word-wrapped at self.width characters."""
leading_space = ' ' * indent
while len(leading_space) + len(text) > self.width:
@@ -165,11 +197,11 @@ class Writer(object):
self.output.write(leading_space + text + '\n')
- def close(self):
+ def close(self) -> None:
self.output.close()
-def as_list(input):
+def as_list(input: Optional[Union[str, List[str]]]) -> List[str]:
if input is None:
return []
if isinstance(input, list):
@@ -177,7 +209,7 @@ def as_list(input):
return [input]
-def escape(string):
+def escape(string: str) -> str:
"""Escape a string such that it can be embedded into a Ninja file without
further interpretation."""
assert '\n' not in string, 'Ninja syntax does not allow newlines'
@@ -185,13 +217,13 @@ def escape(string):
return string.replace('$', '$$')
-def expand(string, vars, local_vars={}):
+def expand(string: str, vars: Dict[str, str], local_vars: Dict[str, str] = {}) -> str:
"""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):
+ def exp(m: Match[str]) -> str:
var = m.group(1)
if var == '$':
return '$'
diff --git a/misc/ninja_syntax_test.py b/misc/ninja_syntax_test.py
index 90ff9c6..61fb177 100755
--- a/misc/ninja_syntax_test.py
+++ b/misc/ninja_syntax_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright 2011 Google Inc. All Rights Reserved.
#
diff --git a/misc/output_test.py b/misc/output_test.py
index 141716c..13b0926 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -13,29 +13,26 @@ import tempfile
import unittest
default_env = dict(os.environ)
-if 'NINJA_STATUS' in default_env:
- del default_env['NINJA_STATUS']
-if 'CLICOLOR_FORCE' in default_env:
- del default_env['CLICOLOR_FORCE']
+default_env.pop('NINJA_STATUS', None)
+default_env.pop('CLICOLOR_FORCE', None)
default_env['TERM'] = ''
NINJA_PATH = os.path.abspath('./ninja')
def run(build_ninja, flags='', pipe=False, env=default_env):
with tempfile.TemporaryDirectory() as d:
- os.chdir(d)
- with open('build.ninja', 'w') as f:
+ with open(os.path.join(d, 'build.ninja'), 'w') as f:
f.write(build_ninja)
f.flush()
ninja_cmd = '{} {}'.format(NINJA_PATH, flags)
try:
if pipe:
- output = subprocess.check_output([ninja_cmd], shell=True, env=env)
+ output = subprocess.check_output([ninja_cmd], shell=True, cwd=d, env=env)
elif platform.system() == 'Darwin':
output = subprocess.check_output(['script', '-q', '/dev/null', 'bash', '-c', ninja_cmd],
- env=env)
+ cwd=d, env=env)
else:
output = subprocess.check_output(['script', '-qfec', ninja_cmd, '/dev/null'],
- env=env)
+ cwd=d, env=env)
except subprocess.CalledProcessError as err:
sys.stdout.buffer.write(err.output)
raise err
@@ -112,14 +109,51 @@ red
\x1b[31mred\x1b[0m
''')
+ def test_issue_1966(self):
+ self.assertEqual(run(
+'''rule cat
+ command = cat $rspfile $rspfile > $out
+ rspfile = cat.rsp
+ rspfile_content = a b c
+
+build a: cat
+''', '-j3'),
+'''[1/1] cat cat.rsp cat.rsp > a\x1b[K
+''')
+
+
def test_pr_1685(self):
# Running those tools without .ninja_deps and .ninja_log shouldn't fail.
self.assertEqual(run('', flags='-t recompact'), '')
self.assertEqual(run('', flags='-t restat'), '')
+ def test_issue_2048(self):
+ with tempfile.TemporaryDirectory() as d:
+ with open(os.path.join(d, 'build.ninja'), 'w'):
+ pass
+
+ with open(os.path.join(d, '.ninja_log'), 'w') as f:
+ f.write('# ninja log v4\n')
+
+ try:
+ output = subprocess.check_output([NINJA_PATH, '-t', 'recompact'],
+ cwd=d,
+ env=default_env,
+ stderr=subprocess.STDOUT,
+ text=True
+ )
+
+ self.assertEqual(
+ output.strip(),
+ "ninja: warning: build log version is too old; starting over"
+ )
+ except subprocess.CalledProcessError as err:
+ self.fail("non-zero exit code with: " + err.output)
+
def test_status(self):
self.assertEqual(run(''), 'ninja: no work to do.\n')
self.assertEqual(run('', pipe=True), 'ninja: no work to do.\n')
+ self.assertEqual(run('', flags='--quiet'), '')
def test_ninja_status_default(self):
'Do we show the default status by default?'
diff --git a/misc/write_fake_manifests.py b/misc/write_fake_manifests.py
index abcb677..bf9cf7d 100644..100755
--- a/misc/write_fake_manifests.py
+++ b/misc/write_fake_manifests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""Writes large manifest files, for manifest parser performance testing.
diff --git a/misc/zsh-completion b/misc/zsh-completion
index 4cee3b8..d439df3 100644
--- a/misc/zsh-completion
+++ b/misc/zsh-completion
@@ -16,7 +16,7 @@
# Add the following to your .zshrc to tab-complete ninja targets
# fpath=(path/to/ninja/misc/zsh-completion $fpath)
-__get_targets() {
+(( $+functions[_ninja-get-targets] )) || _ninja-get-targets() {
dir="."
if [ -n "${opt_args[-C]}" ];
then
@@ -31,42 +31,45 @@ __get_targets() {
eval ${targets_command} 2>/dev/null | cut -d: -f1
}
-__get_tools() {
- ninja -t list 2>/dev/null | while read -r a b; do echo $a; done | tail -n +2
+(( $+functions[_ninja-get-tools] )) || _ninja-get-tools() {
+ # remove the first line; remove the leading spaces; replace spaces with colon
+ ninja -t list 2> /dev/null | sed -e '1d;s/^ *//;s/ \+/:/'
}
-__get_modes() {
- ninja -d list 2>/dev/null | while read -r a b; do echo $a; done | tail -n +2 | sed '$d'
+(( $+functions[_ninja-get-modes] )) || _ninja-get-modes() {
+ # remove the first line; remove the last line; remove the leading spaces; replace spaces with colon
+ ninja -d list 2> /dev/null | sed -e '1d;$d;s/^ *//;s/ \+/:/'
}
-__modes() {
+(( $+functions[_ninja-modes] )) || _ninja-modes() {
local -a modes
- modes=(${(fo)"$(__get_modes)"})
+ modes=(${(fo)"$(_ninja-get-modes)"})
_describe 'modes' modes
}
-__tools() {
+(( $+functions[_ninja-tools] )) || _ninja-tools() {
local -a tools
- tools=(${(fo)"$(__get_tools)"})
+ tools=(${(fo)"$(_ninja-get-tools)"})
_describe 'tools' tools
}
-__targets() {
+(( $+functions[_ninja-targets] )) || _ninja-targets() {
local -a targets
- targets=(${(fo)"$(__get_targets)"})
+ targets=(${(fo)"$(_ninja-get-targets)"})
_describe 'targets' targets
}
_arguments \
- {-h,--help}'[Show help]' \
- '--version[Print ninja version]' \
+ '(- *)'{-h,--help}'[Show help]' \
+ '(- *)--version[Print ninja version]' \
'-C+[Change to directory before doing anything else]:directories:_directories' \
'-f+[Specify input build file (default=build.ninja)]:files:_files' \
'-j+[Run N jobs in parallel (default=number of CPUs available)]:number of jobs' \
'-l+[Do not start new jobs if the load average is greater than N]:number of jobs' \
'-k+[Keep going until N jobs fail (default=1)]:number of jobs' \
'-n[Dry run (do not run commands but act like they succeeded)]' \
- '-v[Show all command lines while building]' \
- '-d+[Enable debugging (use -d list to list modes)]:modes:__modes' \
- '-t+[Run a subtool (use -t list to list subtools)]:tools:__tools' \
- '*::targets:__targets'
+ '(-v --verbose --quiet)'{-v,--verbose}'[Show all command lines while building]' \
+ "(-v --verbose --quiet)--quiet[Don't show progress status, just command output]" \
+ '-d+[Enable debugging (use -d list to list modes)]:modes:_ninja-modes' \
+ '-t+[Run a subtool (use -t list to list subtools)]:tools:_ninja-tools' \
+ '*::targets:_ninja-targets'