summaryrefslogtreecommitdiff
path: root/ninja/misc
diff options
context:
space:
mode:
Diffstat (limited to 'ninja/misc')
-rw-r--r--ninja/misc/bash-completion54
-rw-r--r--ninja/misc/ninja_syntax.py44
-rw-r--r--ninja/misc/write_fake_manifests.py219
-rw-r--r--ninja/misc/zsh-completion47
4 files changed, 320 insertions, 44 deletions
diff --git a/ninja/misc/bash-completion b/ninja/misc/bash-completion
index 2d6975b9450..6edf4dfea02 100644
--- a/ninja/misc/bash-completion
+++ b/ninja/misc/bash-completion
@@ -16,25 +16,43 @@
# . path/to/ninja/misc/bash-completion
_ninja_target() {
- local cur targets dir line targets_command OPTIND
- cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur prev targets dir line targets_command OPTIND
- if [[ "$cur" == "--"* ]]; then
- # there is currently only one argument that takes --
- COMPREPLY=($(compgen -P '--' -W 'version' -- "${cur:2}"))
- else
- dir="."
- line=$(echo ${COMP_LINE} | cut -d" " -f 2-)
- # filter out all non relevant arguments but keep C for dirs
- while getopts C:f:j:l:k:nvd:t: opt "${line[@]}"; do
- case $opt in
- C) dir="$OPTARG" ;;
- esac
- done;
- targets_command="ninja -C ${dir} -t targets all"
- targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}')
- COMPREPLY=($(compgen -W "$targets" -- "$cur"))
- fi
+ # When available, use bash_completion to:
+ # 1) Complete words when the cursor is in the middle of the word
+ # 2) Complete paths with files or directories, as appropriate
+ if _get_comp_words_by_ref cur prev &>/dev/null ; then
+ case $prev in
+ -f)
+ _filedir
+ return 0
+ ;;
+ -C)
+ _filedir -d
+ return 0
+ ;;
+ esac
+ else
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ fi
+
+ if [[ "$cur" == "--"* ]]; then
+ # there is currently only one argument that takes --
+ COMPREPLY=($(compgen -P '--' -W 'version' -- "${cur:2}"))
+ else
+ dir="."
+ line=$(echo ${COMP_LINE} | cut -d" " -f 2-)
+ # filter out all non relevant arguments but keep C for dirs
+ while getopts :C:f:j:l:k:nvd:t: opt $line; do
+ case $opt in
+ # eval for tilde expansion
+ C) eval dir="$OPTARG" ;;
+ esac
+ done;
+ targets_command="eval ninja -C \"${dir}\" -t targets all"
+ targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}')
+ COMPREPLY=($(compgen -W "$targets" -- "$cur"))
+ fi
return
}
complete -F _ninja_target ninja
diff --git a/ninja/misc/ninja_syntax.py b/ninja/misc/ninja_syntax.py
index d69e3e49e2d..14b932ff4c8 100644
--- a/ninja/misc/ninja_syntax.py
+++ b/ninja/misc/ninja_syntax.py
@@ -8,10 +8,9 @@ use Python.
"""
import textwrap
-import re
def escape_path(word):
- return word.replace('$ ','$$ ').replace(' ','$ ').replace(':', '$:')
+ return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
class Writer(object):
def __init__(self, output, width=78):
@@ -61,21 +60,20 @@ class Writer(object):
def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
variables=None):
outputs = self._as_list(outputs)
- all_inputs = self._as_list(inputs)[:]
- out_outputs = list(map(escape_path, outputs))
- all_inputs = list(map(escape_path, all_inputs))
+ out_outputs = [escape_path(x) for x in outputs]
+ all_inputs = [escape_path(x) for x in self._as_list(inputs)]
if implicit:
- implicit = map(escape_path, self._as_list(implicit))
+ implicit = [escape_path(x) for x in self._as_list(implicit)]
all_inputs.append('|')
all_inputs.extend(implicit)
if order_only:
- order_only = map(escape_path, self._as_list(order_only))
+ order_only = [escape_path(x) for x in self._as_list(order_only)]
all_inputs.append('||')
all_inputs.extend(order_only)
self._line('build %s: %s' % (' '.join(out_outputs),
- ' '.join([rule] + all_inputs)))
+ ' '.join([rule] + all_inputs)))
if variables:
if isinstance(variables, dict):
@@ -98,13 +96,13 @@ class Writer(object):
self._line('default %s' % ' '.join(self._as_list(paths)))
def _count_dollars_before_index(self, s, i):
- """Returns the number of '$' characters right in front of s[i]."""
- dollar_count = 0
- dollar_index = i - 1
- while dollar_index > 0 and s[dollar_index] == '$':
- dollar_count += 1
- dollar_index -= 1
- return dollar_count
+ """Returns the number of '$' characters right in front of s[i]."""
+ dollar_count = 0
+ dollar_index = i - 1
+ while dollar_index > 0 and s[dollar_index] == '$':
+ dollar_count += 1
+ dollar_index -= 1
+ return dollar_count
def _line(self, text, indent=0):
"""Write 'text' word-wrapped at self.width characters."""
@@ -117,19 +115,19 @@ class Writer(object):
available_space = self.width - len(leading_space) - len(' $')
space = available_space
while True:
- space = text.rfind(' ', 0, space)
- if space < 0 or \
- self._count_dollars_before_index(text, space) % 2 == 0:
- break
+ space = text.rfind(' ', 0, space)
+ if (space < 0 or
+ self._count_dollars_before_index(text, space) % 2 == 0):
+ break
if space < 0:
# No such space; just use the first unescaped space we can find.
space = available_space - 1
while True:
- space = text.find(' ', space + 1)
- if space < 0 or \
- self._count_dollars_before_index(text, space) % 2 == 0:
- break
+ space = text.find(' ', space + 1)
+ if (space < 0 or
+ self._count_dollars_before_index(text, space) % 2 == 0):
+ break
if space < 0:
# Give up on breaking.
break
diff --git a/ninja/misc/write_fake_manifests.py b/ninja/misc/write_fake_manifests.py
new file mode 100644
index 00000000000..837007e603b
--- /dev/null
+++ b/ninja/misc/write_fake_manifests.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python
+
+"""Writes large manifest files, for manifest parser performance testing.
+
+The generated manifest files are (eerily) similar in appearance and size to the
+ones used in the Chromium project.
+
+Usage:
+ python misc/write_fake_manifests.py outdir # Will run for about 5s.
+
+The program contains a hardcoded random seed, so it will generate the same
+output every time it runs. By changing the seed, it's easy to generate many
+different sets of manifest files.
+"""
+
+import argparse
+import contextlib
+import os
+import random
+import sys
+
+import ninja_syntax
+
+
+def paretoint(avg, alpha):
+ """Returns a random integer that's avg on average, following a power law.
+ alpha determines the shape of the power curve. alpha has to be larger
+ than 1. The closer alpha is to 1, the higher the variation of the returned
+ numbers."""
+ return int(random.paretovariate(alpha) * avg / (alpha / (alpha - 1)))
+
+
+# Based on http://neugierig.org/software/chromium/class-name-generator.html
+def moar(avg_options, p_suffix):
+ kStart = ['render', 'web', 'browser', 'tab', 'content', 'extension', 'url',
+ 'file', 'sync', 'content', 'http', 'profile']
+ kOption = ['view', 'host', 'holder', 'container', 'impl', 'ref',
+ 'delegate', 'widget', 'proxy', 'stub', 'context',
+ 'manager', 'master', 'watcher', 'service', 'file', 'data',
+ 'resource', 'device', 'info', 'provider', 'internals', 'tracker',
+ 'api', 'layer']
+ kOS = ['win', 'mac', 'aura', 'linux', 'android', 'unittest', 'browsertest']
+ num_options = min(paretoint(avg_options, alpha=4), 5)
+ # The original allows kOption to repeat as long as no consecutive options
+ # repeat. This version doesn't allow any option repetition.
+ name = [random.choice(kStart)] + random.sample(kOption, num_options)
+ if random.random() < p_suffix:
+ name.append(random.choice(kOS))
+ return '_'.join(name)
+
+
+class GenRandom(object):
+ def __init__(self):
+ self.seen_names = set([None])
+ self.seen_defines = set([None])
+
+ def _unique_string(self, seen, avg_options=1.3, p_suffix=0.1):
+ s = None
+ while s in seen:
+ s = moar(avg_options, p_suffix)
+ seen.add(s)
+ return s
+
+ def _n_unique_strings(self, n):
+ seen = set([None])
+ return [self._unique_string(seen, avg_options=3, p_suffix=0.4)
+ for _ in xrange(n)]
+
+ def target_name(self):
+ return self._unique_string(p_suffix=0, seen=self.seen_names)
+
+ def path(self):
+ return os.path.sep.join([
+ self._unique_string(self.seen_names, avg_options=1, p_suffix=0)
+ for _ in xrange(1 + paretoint(0.6, alpha=4))])
+
+ def src_obj_pairs(self, path, name):
+ num_sources = paretoint(55, alpha=2) + 1
+ return [(os.path.join('..', '..', path, s + '.cc'),
+ os.path.join('obj', path, '%s.%s.o' % (name, s)))
+ for s in self._n_unique_strings(num_sources)]
+
+ def defines(self):
+ return [
+ '-DENABLE_' + self._unique_string(self.seen_defines).upper()
+ for _ in xrange(paretoint(20, alpha=3))]
+
+
+LIB, EXE = 0, 1
+class Target(object):
+ def __init__(self, gen, kind):
+ self.name = gen.target_name()
+ self.dir_path = gen.path()
+ self.ninja_file_path = os.path.join(
+ 'obj', self.dir_path, self.name + '.ninja')
+ self.src_obj_pairs = gen.src_obj_pairs(self.dir_path, self.name)
+ if kind == LIB:
+ self.output = os.path.join('lib' + self.name + '.a')
+ elif kind == EXE:
+ self.output = os.path.join(self.name)
+ self.defines = gen.defines()
+ self.deps = []
+ self.kind = kind
+ self.has_compile_depends = random.random() < 0.4
+
+ @property
+ def includes(self):
+ return ['-I' + dep.dir_path for dep in self.deps]
+
+
+def write_target_ninja(ninja, target):
+ compile_depends = None
+ if target.has_compile_depends:
+ compile_depends = os.path.join(
+ 'obj', target.dir_path, target.name + '.stamp')
+ ninja.build(compile_depends, 'stamp', target.src_obj_pairs[0][0])
+ ninja.newline()
+
+ ninja.variable('defines', target.defines)
+ if target.deps:
+ ninja.variable('includes', target.includes)
+ ninja.variable('cflags', ['-Wall', '-fno-rtti', '-fno-exceptions'])
+ ninja.newline()
+
+ for src, obj in target.src_obj_pairs:
+ ninja.build(obj, 'cxx', src, implicit=compile_depends)
+ ninja.newline()
+
+ deps = [dep.output for dep in target.deps]
+ libs = [dep.output for dep in target.deps if dep.kind == LIB]
+ if target.kind == EXE:
+ ninja.variable('ldflags', '-Wl,pie')
+ ninja.variable('libs', libs)
+ link = { LIB: 'alink', EXE: 'link'}[target.kind]
+ ninja.build(target.output, link, [obj for _, obj in target.src_obj_pairs],
+ implicit=deps)
+
+
+def write_master_ninja(master_ninja, targets):
+ """Writes master build.ninja file, referencing all given subninjas."""
+ master_ninja.variable('cxx', 'c++')
+ master_ninja.variable('ld', '$cxx')
+ master_ninja.newline()
+
+ master_ninja.pool('link_pool', depth=4)
+ master_ninja.newline()
+
+ master_ninja.rule('cxx', description='CXX $out',
+ command='$cxx -MMD -MF $out.d $defines $includes $cflags -c $in -o $out',
+ depfile='$out.d', deps='gcc')
+ master_ninja.rule('alink', description='LIBTOOL-STATIC $out',
+ command='rm -f $out && libtool -static -o $out $in')
+ master_ninja.rule('link', description='LINK $out', pool='link_pool',
+ command='$ld $ldflags -o $out $in $libs')
+ master_ninja.rule('stamp', description='STAMP $out', command='touch $out')
+ master_ninja.newline()
+
+ for target in targets:
+ master_ninja.subninja(target.ninja_file_path)
+ master_ninja.newline()
+
+ master_ninja.comment('Short names for targets.')
+ for target in targets:
+ if target.name != target.output:
+ master_ninja.build(target.name, 'phony', target.output)
+ master_ninja.newline()
+
+ master_ninja.build('all', 'phony', [target.output for target in targets])
+ master_ninja.default('all')
+
+
+@contextlib.contextmanager
+def FileWriter(path):
+ """Context manager for a ninja_syntax object writing to a file."""
+ try:
+ os.makedirs(os.path.dirname(path))
+ except OSError:
+ pass
+ f = open(path, 'w')
+ yield ninja_syntax.Writer(f)
+ f.close()
+
+
+def random_targets():
+ num_targets = 800
+ gen = GenRandom()
+
+ # N-1 static libraries, and 1 executable depending on all of them.
+ targets = [Target(gen, LIB) for i in xrange(num_targets - 1)]
+ for i in range(len(targets)):
+ targets[i].deps = [t for t in targets[0:i] if random.random() < 0.05]
+
+ last_target = Target(gen, EXE)
+ last_target.deps = targets[:]
+ last_target.src_obj_pairs = last_target.src_obj_pairs[0:10] # Trim.
+ targets.append(last_target)
+ return targets
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('outdir', help='output directory')
+ args = parser.parse_args()
+ root_dir = args.outdir
+
+ random.seed(12345)
+
+ targets = random_targets()
+ for target in targets:
+ with FileWriter(os.path.join(root_dir, target.ninja_file_path)) as n:
+ write_target_ninja(n, target)
+
+ with FileWriter(os.path.join(root_dir, 'build.ninja')) as master_ninja:
+ master_ninja.width = 120
+ write_master_ninja(master_ninja, targets)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/ninja/misc/zsh-completion b/ninja/misc/zsh-completion
index cd0edfbd97a..2fe16fb0499 100644
--- a/ninja/misc/zsh-completion
+++ b/ninja/misc/zsh-completion
@@ -1,3 +1,4 @@
+#compdef ninja
# Copyright 2011 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +16,47 @@
# Add the following to your .zshrc to tab-complete ninja targets
# . path/to/ninja/misc/zsh-completion
-_ninja() {
- reply=(`(ninja -t targets all 2&>/dev/null) | awk -F: '{print $1}'`)
+__get_targets() {
+ ninja -t targets 2>/dev/null | while read -r a b; do echo $a | cut -d ':' -f1; done;
}
-compctl -K _ninja ninja
+
+__get_tools() {
+ ninja -t list 2>/dev/null | while read -r a b; do echo $a; done | tail -n +2
+}
+
+__get_modes() {
+ ninja -d list 2>/dev/null | while read -r a b; do echo $a; done | tail -n +2 | head -n -1
+}
+
+__modes() {
+ local -a modes
+ modes=(${(fo)"$(__get_modes)"})
+ _describe 'modes' modes
+}
+
+__tools() {
+ local -a tools
+ tools=(${(fo)"$(__get_tools)"})
+ _describe 'tools' tools
+}
+
+__targets() {
+ local -a targets
+ targets=(${(fo)"$(__get_targets)"})
+ _describe 'targets' targets
+}
+
+_arguments \
+ {-h,--help}'[Show help]' \
+ '--version[Print ninja version]' \
+ '-C+[Change to directory before doing anything else]:directories:_directories' \
+ '-f+[Specify input build file (default=build.ninja)]:files:_files' \
+ '-j+[Run N jobs in parallel (default=number of CPUs available)]:number of jobs' \
+ '-l+[Do not start new jobs if the load average is greater than N]:number of jobs' \
+ '-k+[Keep going until N jobs fail (default=1)]:number of jobs' \
+ '-n[Dry run (do not run commands but act like they succeeded)]' \
+ '-v[Show all command lines while building]' \
+ '-d+[Enable debugging (use -d list to list modes)]:modes:__modes' \
+ '-t+[Run a subtool (use -t list to list subtools)]:tools:__tools' \
+ '*::targets:__targets'
+