CanonicalizePath handles \ on Windows
[platform/upstream/ninja.git] / misc / write_fake_manifests.py
1 #!/usr/bin/env python
2
3 """Writes large manifest files, for manifest parser performance testing.
4
5 The generated manifest files are (eerily) similar in appearance and size to the
6 ones used in the Chromium project.
7
8 Usage:
9   python misc/write_fake_manifests.py outdir  # Will run for about 5s.
10
11 The program contains a hardcoded random seed, so it will generate the same
12 output every time it runs.  By changing the seed, it's easy to generate many
13 different sets of manifest files.
14 """
15
16 import argparse
17 import contextlib
18 import os
19 import random
20 import sys
21
22 import ninja_syntax
23
24
25 def paretoint(avg, alpha):
26     """Returns a random integer that's avg on average, following a power law.
27     alpha determines the shape of the power curve. alpha has to be larger
28     than 1. The closer alpha is to 1, the higher the variation of the returned
29     numbers."""
30     return int(random.paretovariate(alpha) * avg / (alpha / (alpha - 1)))
31
32
33 # Based on http://neugierig.org/software/chromium/class-name-generator.html
34 def moar(avg_options, p_suffix):
35     kStart = ['render', 'web', 'browser', 'tab', 'content', 'extension', 'url',
36               'file', 'sync', 'content', 'http', 'profile']
37     kOption = ['view', 'host', 'holder', 'container', 'impl', 'ref',
38                'delegate', 'widget', 'proxy', 'stub', 'context',
39                'manager', 'master', 'watcher', 'service', 'file', 'data',
40                'resource', 'device', 'info', 'provider', 'internals', 'tracker',
41                'api', 'layer']
42     kOS = ['win', 'mac', 'aura', 'linux', 'android', 'unittest', 'browsertest']
43     num_options = min(paretoint(avg_options, alpha=4), 5)
44     # The original allows kOption to repeat as long as no consecutive options
45     # repeat.  This version doesn't allow any option repetition.
46     name = [random.choice(kStart)] + random.sample(kOption, num_options)
47     if random.random() < p_suffix:
48         name.append(random.choice(kOS))
49     return '_'.join(name)
50
51
52 class GenRandom(object):
53     def __init__(self):
54         self.seen_names = set([None])
55         self.seen_defines = set([None])
56
57     def _unique_string(self, seen, avg_options=1.3, p_suffix=0.1):
58         s = None
59         while s in seen:
60             s = moar(avg_options, p_suffix)
61         seen.add(s)
62         return s
63
64     def _n_unique_strings(self, n):
65         seen = set([None])
66         return [self._unique_string(seen, avg_options=3, p_suffix=0.4)
67                 for _ in xrange(n)]
68
69     def target_name(self):
70         return self._unique_string(p_suffix=0, seen=self.seen_names)
71
72     def path(self):
73         return os.path.sep.join([
74             self._unique_string(self.seen_names, avg_options=1, p_suffix=0)
75             for _ in xrange(1 + paretoint(0.6, alpha=4))])
76
77     def src_obj_pairs(self, path, name):
78         num_sources = paretoint(55, alpha=2) + 1
79         return [(os.path.join('..', '..', path, s + '.cc'),
80                  os.path.join('obj', path, '%s.%s.o' % (name, s)))
81                 for s in self._n_unique_strings(num_sources)]
82
83     def defines(self):
84         return [
85             '-DENABLE_' + self._unique_string(self.seen_defines).upper()
86             for _ in xrange(paretoint(20, alpha=3))]
87
88
89 LIB, EXE = 0, 1
90 class Target(object):
91     def __init__(self, gen, kind):
92         self.name = gen.target_name()
93         self.dir_path = gen.path()
94         self.ninja_file_path = os.path.join(
95             'obj', self.dir_path, self.name + '.ninja')
96         self.src_obj_pairs = gen.src_obj_pairs(self.dir_path, self.name)
97         if kind == LIB:
98             self.output = os.path.join('lib' + self.name + '.a')
99         elif kind == EXE:
100             self.output = os.path.join(self.name)
101         self.defines = gen.defines()
102         self.deps = []
103         self.kind = kind
104         self.has_compile_depends = random.random() < 0.4
105
106     @property
107     def includes(self):
108         return ['-I' + dep.dir_path for dep in self.deps]
109
110
111 def write_target_ninja(ninja, target):
112     compile_depends = None
113     if target.has_compile_depends:
114       compile_depends = os.path.join(
115           'obj', target.dir_path, target.name + '.stamp')
116       ninja.build(compile_depends, 'stamp', target.src_obj_pairs[0][0])
117       ninja.newline()
118
119     ninja.variable('defines', target.defines)
120     if target.deps:
121         ninja.variable('includes', target.includes)
122     ninja.variable('cflags', ['-Wall', '-fno-rtti', '-fno-exceptions'])
123     ninja.newline()
124
125     for src, obj in target.src_obj_pairs:
126         ninja.build(obj, 'cxx', src, implicit=compile_depends)
127     ninja.newline()
128
129     deps = [dep.output for dep in target.deps]
130     libs = [dep.output for dep in target.deps if dep.kind == LIB]
131     if target.kind == EXE:
132         ninja.variable('ldflags', '-Wl,pie')
133         ninja.variable('libs', libs)
134     link = { LIB: 'alink', EXE: 'link'}[target.kind]
135     ninja.build(target.output, link, [obj for _, obj in target.src_obj_pairs],
136                 implicit=deps)
137
138
139 def write_master_ninja(master_ninja, targets):
140     """Writes master build.ninja file, referencing all given subninjas."""
141     master_ninja.variable('cxx', 'c++')
142     master_ninja.variable('ld', '$cxx')
143     master_ninja.newline()
144
145     master_ninja.pool('link_pool', depth=4)
146     master_ninja.newline()
147
148     master_ninja.rule('cxx', description='CXX $out',
149       command='$cxx -MMD -MF $out.d $defines $includes $cflags -c $in -o $out',
150       depfile='$out.d', deps='gcc')
151     master_ninja.rule('alink', description='LIBTOOL-STATIC $out',
152       command='rm -f $out && libtool -static -o $out $in')
153     master_ninja.rule('link', description='LINK $out', pool='link_pool',
154       command='$ld $ldflags -o $out $in $libs')
155     master_ninja.rule('stamp', description='STAMP $out', command='touch $out')
156     master_ninja.newline()
157
158     for target in targets:
159         master_ninja.subninja(target.ninja_file_path)
160     master_ninja.newline()
161
162     master_ninja.comment('Short names for targets.')
163     for target in targets:
164         if target.name != target.output:
165             master_ninja.build(target.name, 'phony', target.output)
166     master_ninja.newline()
167
168     master_ninja.build('all', 'phony', [target.output for target in targets])
169     master_ninja.default('all')
170
171
172 @contextlib.contextmanager
173 def FileWriter(path):
174     """Context manager for a ninja_syntax object writing to a file."""
175     try:
176         os.makedirs(os.path.dirname(path))
177     except OSError:
178         pass
179     f = open(path, 'w')
180     yield ninja_syntax.Writer(f)
181     f.close()
182
183
184 def random_targets():
185     num_targets = 1500
186     gen = GenRandom()
187
188     # N-1 static libraries, and 1 executable depending on all of them.
189     targets = [Target(gen, LIB) for i in xrange(num_targets - 1)]
190     for i in range(len(targets)):
191         targets[i].deps = [t for t in targets[0:i] if random.random() < 0.05]
192
193     last_target = Target(gen, EXE)
194     last_target.deps = targets[:]
195     last_target.src_obj_pairs = last_target.src_obj_pairs[0:10]  # Trim.
196     targets.append(last_target)
197     return targets
198
199
200 def main():
201     parser = argparse.ArgumentParser()
202     parser.add_argument('outdir', help='output directory')
203     args = parser.parse_args()
204     root_dir = args.outdir
205
206     random.seed(12345)
207
208     targets = random_targets()
209     for target in targets:
210         with FileWriter(os.path.join(root_dir, target.ninja_file_path)) as n:
211             write_target_ninja(n, target)
212
213     with FileWriter(os.path.join(root_dir, 'build.ninja')) as master_ninja:
214         master_ninja.width = 120
215         write_master_ninja(master_ninja, targets)
216
217
218 if __name__ == '__main__':
219     sys.exit(main())