summaryrefslogtreecommitdiff
path: root/ninja/misc
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /ninja/misc
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'ninja/misc')
-rw-r--r--ninja/misc/bash-completion40
-rw-r--r--ninja/misc/inherited-fds.ninja23
-rw-r--r--ninja/misc/long-slow-build.ninja38
-rwxr-xr-xninja/misc/measure.py54
-rw-r--r--ninja/misc/ninja-mode.el42
-rw-r--r--ninja/misc/ninja.vim81
-rw-r--r--ninja/misc/ninja_syntax.py158
-rwxr-xr-xninja/misc/ninja_syntax_test.py152
-rw-r--r--ninja/misc/packaging/ninja.spec42
-rwxr-xr-xninja/misc/packaging/rpmbuild.sh29
-rw-r--r--ninja/misc/zsh-completion21
11 files changed, 680 insertions, 0 deletions
diff --git a/ninja/misc/bash-completion b/ninja/misc/bash-completion
new file mode 100644
index 00000000000..2d6975b9450
--- /dev/null
+++ b/ninja/misc/bash-completion
@@ -0,0 +1,40 @@
+# Copyright 2011 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Add the following to your .bashrc to tab-complete ninja targets
+# . path/to/ninja/misc/bash-completion
+
+_ninja_target() {
+ local cur targets dir line targets_command OPTIND
+ cur="${COMP_WORDS[COMP_CWORD]}"
+
+ if [[ "$cur" == "--"* ]]; then
+ # there is currently only one argument that takes --
+ COMPREPLY=($(compgen -P '--' -W 'version' -- "${cur:2}"))
+ else
+ dir="."
+ line=$(echo ${COMP_LINE} | cut -d" " -f 2-)
+ # filter out all non relevant arguments but keep C for dirs
+ while getopts C:f:j:l:k:nvd:t: opt "${line[@]}"; do
+ case $opt in
+ C) dir="$OPTARG" ;;
+ esac
+ done;
+ targets_command="ninja -C ${dir} -t targets all"
+ targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}')
+ COMPREPLY=($(compgen -W "$targets" -- "$cur"))
+ fi
+ return
+}
+complete -F _ninja_target ninja
diff --git a/ninja/misc/inherited-fds.ninja b/ninja/misc/inherited-fds.ninja
new file mode 100644
index 00000000000..671155eb0b3
--- /dev/null
+++ b/ninja/misc/inherited-fds.ninja
@@ -0,0 +1,23 @@
+# This build file prints out a list of open file descriptors in
+# Ninja subprocesses, to help verify we don't accidentally leak
+# any.
+
+# Because one fd leak was in the code managing multiple subprocesses,
+# this test brings up multiple subprocesses and then dumps the fd
+# table of the last one.
+
+# Use like: ./ninja -f misc/inherited-fds.ninja
+
+rule sleep
+ command = sleep 10000
+
+rule dump
+ command = sleep 1; ls -l /proc/self/fd; exit 1
+
+build all: phony a b c d e
+
+build a: sleep
+build b: sleep
+build c: sleep
+build d: sleep
+build e: dump
diff --git a/ninja/misc/long-slow-build.ninja b/ninja/misc/long-slow-build.ninja
new file mode 100644
index 00000000000..46af6bafbe7
--- /dev/null
+++ b/ninja/misc/long-slow-build.ninja
@@ -0,0 +1,38 @@
+# An input file for running a "slow" build.
+# Use like: ninja -f misc/long-slow-build.ninja all
+
+rule sleep
+ command = sleep 1
+ description = SLEEP $out
+
+build 0: sleep README
+build 1: sleep README
+build 2: sleep README
+build 3: sleep README
+build 4: sleep README
+build 5: sleep README
+build 6: sleep README
+build 7: sleep README
+build 8: sleep README
+build 9: sleep README
+build 10: sleep 0
+build 11: sleep 1
+build 12: sleep 2
+build 13: sleep 3
+build 14: sleep 4
+build 15: sleep 5
+build 16: sleep 6
+build 17: sleep 7
+build 18: sleep 8
+build 19: sleep 9
+build 20: sleep 10
+build 21: sleep 11
+build 22: sleep 12
+build 23: sleep 13
+build 24: sleep 14
+build 25: sleep 15
+build 26: sleep 16
+build 27: sleep 17
+build 28: sleep 18
+build 29: sleep 19
+build all: phony 20 21 22 23 24 25 26 27 28 29
diff --git a/ninja/misc/measure.py b/ninja/misc/measure.py
new file mode 100755
index 00000000000..1323fc66d12
--- /dev/null
+++ b/ninja/misc/measure.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+# Copyright 2011 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""measure the runtime of a command by repeatedly running it.
+"""
+
+import time
+import subprocess
+import sys
+
+devnull = open('/dev/null', 'w')
+
+def run(cmd, repeat=10):
+ print 'sampling:',
+ sys.stdout.flush()
+
+ samples = []
+ for _ in range(repeat):
+ start = time.time()
+ subprocess.call(cmd, stdout=devnull, stderr=devnull)
+ end = time.time()
+ dt = (end - start) * 1000
+ print '%dms' % int(dt),
+ sys.stdout.flush()
+ samples.append(dt)
+ print
+
+ # We're interested in the 'pure' runtime of the code, which is
+ # conceptually the smallest time we'd see if we ran it enough times
+ # such that it got the perfect time slices / disk cache hits.
+ best = min(samples)
+ # Also print how varied the outputs were in an attempt to make it
+ # more obvious if something has gone terribly wrong.
+ err = sum(s - best for s in samples) / float(len(samples))
+ print 'estimate: %dms (mean err %.1fms)' % (best, err)
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print 'usage: measure.py command args...'
+ sys.exit(1)
+ run(cmd=sys.argv[1:])
diff --git a/ninja/misc/ninja-mode.el b/ninja/misc/ninja-mode.el
new file mode 100644
index 00000000000..d939206de1f
--- /dev/null
+++ b/ninja/misc/ninja-mode.el
@@ -0,0 +1,42 @@
+;; Copyright 2011 Google Inc. All Rights Reserved.
+;;
+;; Licensed under the Apache License, Version 2.0 (the "License");
+;; you may not use this file except in compliance with the License.
+;; You may obtain a copy of the License at
+;;
+;; http://www.apache.org/licenses/LICENSE-2.0
+;;
+;; Unless required by applicable law or agreed to in writing, software
+;; distributed under the License is distributed on an "AS IS" BASIS,
+;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+;; See the License for the specific language governing permissions and
+;; limitations under the License.
+
+;; Simple emacs mode for editing .ninja files.
+;; Just some syntax highlighting for now.
+
+(setq 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))
+ ))
+(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))
+ )
+
+(provide 'ninja-mode)
+
+;; Run ninja-mode for files ending in .ninja.
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.ninja$" . ninja-mode))
diff --git a/ninja/misc/ninja.vim b/ninja/misc/ninja.vim
new file mode 100644
index 00000000000..d8132678108
--- /dev/null
+++ b/ninja/misc/ninja.vim
@@ -0,0 +1,81 @@
+" ninja build file syntax.
+" Language: ninja build file as described at
+" http://martine.github.com/ninja/manual.html
+" Version: 1.3
+" Last Change: 2013/04/16
+" Maintainer: Nicolas Weber <nicolasweber@gmx.de>
+" Version 1.3 of this script is in the upstream vim repository and will be
+" included in the next vim release. If you change this, please send your change
+" upstream.
+
+" ninja lexer and parser are at
+" https://github.com/martine/ninja/blob/master/src/lexer.in.cc
+" https://github.com/martine/ninja/blob/master/src/manifest_parser.cc
+
+if exists("b:current_syntax")
+ finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+syn case match
+
+syn match ninjaComment /#.*/ contains=@Spell
+
+" Toplevel statements are the ones listed here and
+" toplevel variable assignments (ident '=' value).
+" lexer.in.cc, ReadToken() and manifest_parser.cc, Parse()
+syn match ninjaKeyword "^build\>"
+syn match ninjaKeyword "^rule\>"
+syn match ninjaKeyword "^pool\>"
+syn match ninjaKeyword "^default\>"
+syn match ninjaKeyword "^include\>"
+syn match ninjaKeyword "^subninja\>"
+
+" Both 'build' and 'rule' begin a variable scope that ends
+" on the first line without indent. 'rule' allows only a
+" limited set of magic variables, 'build' allows general
+" let assignments.
+" manifest_parser.cc, ParseRule()
+syn region ninjaRule start="^rule" end="^\ze\S" contains=ALL transparent
+syn keyword ninjaRuleCommand contained command deps depfile description generator
+ \ pool restat rspfile rspfile_content
+
+syn region ninjaPool start="^pool" end="^\ze\S" contains=ALL transparent
+syn keyword ninjaPoolCommand contained depth
+
+" Strings are parsed as follows:
+" lexer.in.cc, ReadEvalString()
+" simple_varname = [a-zA-Z0-9_-]+;
+" varname = [a-zA-Z0-9_.-]+;
+" $$ -> $
+" $\n -> line continuation
+" '$ ' -> escaped space
+" $simple_varname -> variable
+" ${varname} -> variable
+
+syn match ninjaWrapLineOperator "\$$"
+syn match ninjaSimpleVar "\$[a-zA-Z0-9_-]\+"
+syn match ninjaVar "\${[a-zA-Z0-9_.-]\+}"
+
+" operators are:
+" variable assignment =
+" rule definition :
+" implicit dependency |
+" order-only dependency ||
+syn match ninjaOperator "\(=\|:\||\|||\)\ze\s"
+
+hi def link ninjaComment Comment
+hi def link ninjaKeyword Keyword
+hi def link ninjaRuleCommand Statement
+hi def link ninjaPoolCommand Statement
+hi def link ninjaWrapLineOperator ninjaOperator
+hi def link ninjaOperator Operator
+hi def link ninjaSimpleVar ninjaVar
+hi def link ninjaVar Identifier
+
+let b:current_syntax = "ninja"
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/ninja/misc/ninja_syntax.py b/ninja/misc/ninja_syntax.py
new file mode 100644
index 00000000000..d69e3e49e2d
--- /dev/null
+++ b/ninja/misc/ninja_syntax.py
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+
+"""Python module for generating .ninja files.
+
+Note that this is emphatically not a required piece of Ninja; it's
+just a helpful utility for build-file-generation systems that already
+use Python.
+"""
+
+import textwrap
+import re
+
+def escape_path(word):
+ return word.replace('$ ','$$ ').replace(' ','$ ').replace(':', '$:')
+
+class Writer(object):
+ def __init__(self, output, width=78):
+ self.output = output
+ self.width = width
+
+ def newline(self):
+ self.output.write('\n')
+
+ def comment(self, text):
+ for line in textwrap.wrap(text, self.width - 2):
+ self.output.write('# ' + line + '\n')
+
+ def variable(self, key, value, indent=0):
+ 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):
+ 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):
+ self._line('rule %s' % name)
+ self.variable('command', command, indent=1)
+ if description:
+ self.variable('description', description, indent=1)
+ if depfile:
+ self.variable('depfile', depfile, indent=1)
+ if generator:
+ self.variable('generator', '1', indent=1)
+ if pool:
+ self.variable('pool', pool, indent=1)
+ if restat:
+ self.variable('restat', '1', indent=1)
+ if rspfile:
+ self.variable('rspfile', rspfile, indent=1)
+ if rspfile_content:
+ self.variable('rspfile_content', rspfile_content, indent=1)
+ if deps:
+ self.variable('deps', deps, indent=1)
+
+ def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
+ variables=None):
+ outputs = self._as_list(outputs)
+ all_inputs = self._as_list(inputs)[:]
+ out_outputs = list(map(escape_path, outputs))
+ all_inputs = list(map(escape_path, all_inputs))
+
+ if implicit:
+ implicit = map(escape_path, self._as_list(implicit))
+ all_inputs.append('|')
+ all_inputs.extend(implicit)
+ if order_only:
+ order_only = map(escape_path, self._as_list(order_only))
+ all_inputs.append('||')
+ all_inputs.extend(order_only)
+
+ self._line('build %s: %s' % (' '.join(out_outputs),
+ ' '.join([rule] + all_inputs)))
+
+ if variables:
+ if isinstance(variables, dict):
+ iterator = iter(variables.items())
+ else:
+ iterator = iter(variables)
+
+ for key, val in iterator:
+ self.variable(key, val, indent=1)
+
+ return outputs
+
+ def include(self, path):
+ self._line('include %s' % path)
+
+ def subninja(self, path):
+ self._line('subninja %s' % path)
+
+ def default(self, paths):
+ self._line('default %s' % ' '.join(self._as_list(paths)))
+
+ def _count_dollars_before_index(self, s, i):
+ """Returns the number of '$' characters right in front of s[i]."""
+ dollar_count = 0
+ dollar_index = i - 1
+ while dollar_index > 0 and s[dollar_index] == '$':
+ dollar_count += 1
+ dollar_index -= 1
+ return dollar_count
+
+ def _line(self, text, indent=0):
+ """Write 'text' word-wrapped at self.width characters."""
+ leading_space = ' ' * indent
+ while len(leading_space) + len(text) > self.width:
+ # The text is too wide; wrap if possible.
+
+ # Find the rightmost space that would obey our width constraint and
+ # that's not an escaped space.
+ available_space = self.width - len(leading_space) - len(' $')
+ space = available_space
+ while True:
+ space = text.rfind(' ', 0, space)
+ if space < 0 or \
+ self._count_dollars_before_index(text, space) % 2 == 0:
+ break
+
+ if space < 0:
+ # No such space; just use the first unescaped space we can find.
+ space = available_space - 1
+ while True:
+ space = text.find(' ', space + 1)
+ if space < 0 or \
+ self._count_dollars_before_index(text, space) % 2 == 0:
+ break
+ if space < 0:
+ # Give up on breaking.
+ break
+
+ self.output.write(leading_space + text[0:space] + ' $\n')
+ text = text[space+1:]
+
+ # Subsequent lines are continuations, so indent them.
+ leading_space = ' ' * (indent+2)
+
+ 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 escape(string):
+ """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'
+ # We only have one special metacharacter: '$'.
+ return string.replace('$', '$$')
diff --git a/ninja/misc/ninja_syntax_test.py b/ninja/misc/ninja_syntax_test.py
new file mode 100755
index 00000000000..2aef7ff8307
--- /dev/null
+++ b/ninja/misc/ninja_syntax_test.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+
+# Copyright 2011 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+import ninja_syntax
+
+LONGWORD = 'a' * 10
+LONGWORDWITHSPACES = 'a'*5 + '$ ' + 'a'*5
+INDENT = ' '
+
+class TestLineWordWrap(unittest.TestCase):
+ def setUp(self):
+ self.out = StringIO()
+ self.n = ninja_syntax.Writer(self.out, width=8)
+
+ def test_single_long_word(self):
+ # We shouldn't wrap a single long word.
+ self.n._line(LONGWORD)
+ self.assertEqual(LONGWORD + '\n', self.out.getvalue())
+
+ def test_few_long_words(self):
+ # We should wrap a line where the second word is overlong.
+ self.n._line(' '.join(['x', LONGWORD, 'y']))
+ self.assertEqual(' $\n'.join(['x',
+ INDENT + LONGWORD,
+ INDENT + 'y']) + '\n',
+ self.out.getvalue())
+
+ def test_short_words_indented(self):
+ # Test that indent is taking into acount when breaking subsequent lines.
+ # The second line should not be ' to tree', as that's longer than the
+ # test layout width of 8.
+ self.n._line('line_one to tree')
+ self.assertEqual('''\
+line_one $
+ to $
+ tree
+''',
+ self.out.getvalue())
+
+ def test_few_long_words_indented(self):
+ # Check wrapping in the presence of indenting.
+ self.n._line(' '.join(['x', LONGWORD, 'y']), indent=1)
+ self.assertEqual(' $\n'.join([' ' + 'x',
+ ' ' + INDENT + LONGWORD,
+ ' ' + INDENT + 'y']) + '\n',
+ self.out.getvalue())
+
+ def test_escaped_spaces(self):
+ self.n._line(' '.join(['x', LONGWORDWITHSPACES, 'y']))
+ self.assertEqual(' $\n'.join(['x',
+ INDENT + LONGWORDWITHSPACES,
+ INDENT + 'y']) + '\n',
+ self.out.getvalue())
+
+ def test_fit_many_words(self):
+ self.n = ninja_syntax.Writer(self.out, width=78)
+ self.n._line('command = cd ../../chrome; python ../tools/grit/grit/format/repack.py ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak ../out/Debug/gen/chrome/theme_resources_large.pak', 1)
+ self.assertEqual('''\
+ command = cd ../../chrome; python ../tools/grit/grit/format/repack.py $
+ ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak $
+ ../out/Debug/gen/chrome/theme_resources_large.pak
+''',
+ self.out.getvalue())
+
+ def test_leading_space(self):
+ self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping
+ self.n.variable('foo', ['', '-bar', '-somethinglong'], 0)
+ self.assertEqual('''\
+foo = -bar $
+ -somethinglong
+''',
+ self.out.getvalue())
+
+ def test_embedded_dollar_dollar(self):
+ self.n = ninja_syntax.Writer(self.out, width=15) # force wrapping
+ self.n.variable('foo', ['a$$b', '-somethinglong'], 0)
+ self.assertEqual('''\
+foo = a$$b $
+ -somethinglong
+''',
+ self.out.getvalue())
+
+ def test_two_embedded_dollar_dollars(self):
+ self.n = ninja_syntax.Writer(self.out, width=17) # force wrapping
+ self.n.variable('foo', ['a$$b', '-somethinglong'], 0)
+ self.assertEqual('''\
+foo = a$$b $
+ -somethinglong
+''',
+ self.out.getvalue())
+
+ def test_leading_dollar_dollar(self):
+ self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping
+ self.n.variable('foo', ['$$b', '-somethinglong'], 0)
+ self.assertEqual('''\
+foo = $$b $
+ -somethinglong
+''',
+ self.out.getvalue())
+
+ def test_trailing_dollar_dollar(self):
+ self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping
+ self.n.variable('foo', ['a$$', '-somethinglong'], 0)
+ self.assertEqual('''\
+foo = a$$ $
+ -somethinglong
+''',
+ self.out.getvalue())
+
+class TestBuild(unittest.TestCase):
+ def setUp(self):
+ self.out = StringIO()
+ self.n = ninja_syntax.Writer(self.out)
+
+ def test_variables_dict(self):
+ self.n.build('out', 'cc', 'in', variables={'name': 'value'})
+ self.assertEqual('''\
+build out: cc in
+ name = value
+''',
+ self.out.getvalue())
+
+ def test_variables_list(self):
+ self.n.build('out', 'cc', 'in', variables=[('name', 'value')])
+ self.assertEqual('''\
+build out: cc in
+ name = value
+''',
+ self.out.getvalue())
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/ninja/misc/packaging/ninja.spec b/ninja/misc/packaging/ninja.spec
new file mode 100644
index 00000000000..f0c46feab5a
--- /dev/null
+++ b/ninja/misc/packaging/ninja.spec
@@ -0,0 +1,42 @@
+Summary: Ninja is a small build system with a focus on speed.
+Name: ninja
+Version: %{ver}
+Release: %{rel}%{?dist}
+Group: Development/Tools
+License: Apache 2.0
+URL: https://github.com/martine/ninja
+Source0: %{name}-%{version}-%{rel}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{rel}
+
+BuildRequires: asciidoc
+
+%description
+Ninja is yet another build system. It takes as input the interdependencies of files (typically source code and output executables) and
+orchestrates building them, quickly.
+
+Ninja joins a sea of other build systems. Its distinguishing goal is to be fast. It is born from my work on the Chromium browser project,
+which has over 30,000 source files and whose other build systems (including one built from custom non-recursive Makefiles) can take ten
+seconds to start building after changing one file. Ninja is under a second.
+
+%prep
+%setup -q -n %{name}-%{version}-%{rel}
+
+%build
+echo Building..
+./bootstrap.py
+./ninja manual
+
+%install
+mkdir -p %{buildroot}%{_bindir} %{buildroot}%{_docdir}
+cp -p ninja %{buildroot}%{_bindir}/
+
+%files
+%defattr(-, root, root)
+%doc COPYING README doc/manual.html
+%{_bindir}/*
+
+%clean
+rm -rf %{buildroot}
+
+#The changelog is built automatically from Git history
+%changelog
diff --git a/ninja/misc/packaging/rpmbuild.sh b/ninja/misc/packaging/rpmbuild.sh
new file mode 100755
index 00000000000..9b74c6588c9
--- /dev/null
+++ b/ninja/misc/packaging/rpmbuild.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+echo Building ninja RPMs..
+GITROOT=$(git rev-parse --show-toplevel)
+cd $GITROOT
+
+VER=1.0
+REL=$(git rev-parse --short HEAD)git
+RPMTOPDIR=$GITROOT/rpm-build
+echo "Ver: $VER, Release: $REL"
+
+# Create tarball
+mkdir -p $RPMTOPDIR/{SOURCES,SPECS}
+git archive --format=tar --prefix=ninja-${VER}-${REL}/ HEAD | gzip -c > $RPMTOPDIR/SOURCES/ninja-${VER}-${REL}.tar.gz
+
+# Convert git log to RPM's ChangeLog format (shown with rpm -qp --changelog <rpm file>)
+sed -e "s/%{ver}/$VER/" -e "s/%{rel}/$REL/" misc/packaging/ninja.spec > $RPMTOPDIR/SPECS/ninja.spec
+git log --format="* %cd %aN%n- (%h) %s%d%n" --date=local | sed -r 's/[0-9]+:[0-9]+:[0-9]+ //' >> $RPMTOPDIR/SPECS/ninja.spec
+
+# Build SRC and binary RPMs
+rpmbuild --quiet \
+ --define "_topdir $RPMTOPDIR" \
+ --define "_rpmdir $PWD" \
+ --define "_srcrpmdir $PWD" \
+ --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \
+ -ba $RPMTOPDIR/SPECS/ninja.spec &&
+
+rm -rf $RPMTOPDIR &&
+echo Done
diff --git a/ninja/misc/zsh-completion b/ninja/misc/zsh-completion
new file mode 100644
index 00000000000..cd0edfbd97a
--- /dev/null
+++ b/ninja/misc/zsh-completion
@@ -0,0 +1,21 @@
+# Copyright 2011 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Add the following to your .zshrc to tab-complete ninja targets
+# . path/to/ninja/misc/zsh-completion
+
+_ninja() {
+ reply=(`(ninja -t targets all 2&>/dev/null) | awk -F: '{print $1}'`)
+}
+compctl -K _ninja ninja