OILS / mycpp / NINJA_subgraph.py View on Github | oilshell.org

416 lines, 261 significant
1"""
2mycpp/NINJA_subgraph.py
3"""
4
5from __future__ import print_function
6
7import os
8import sys
9
10from build.ninja_lib import log, COMPILERS_VARIANTS
11
12_ = log
13
14
15def DefineTargets(ru):
16
17 ru.py_binary('mycpp/mycpp_main.py',
18 deps_base_dir='prebuilt/ninja',
19 template='mycpp')
20
21 ru.cc_library(
22 '//mycpp/runtime',
23 # Could separate into //mycpp/runtime_{marksweep,bumpleak}
24 srcs=[
25 'mycpp/bump_leak_heap.cc',
26 'mycpp/gc_builtins.cc',
27 'mycpp/gc_mops.cc',
28 'mycpp/gc_mylib.cc',
29 'mycpp/gc_str.cc',
30 'mycpp/hash.cc',
31 'mycpp/mark_sweep_heap.cc',
32 ])
33
34 # Special test with -D
35 ru.cc_binary('mycpp/bump_leak_heap_test.cc',
36 deps=['//mycpp/runtime'],
37 matrix=[
38 ('cxx', 'asan+bumpleak'),
39 ('cxx', 'ubsan+bumpleak'),
40 ('clang', 'ubsan+bumpleak'),
41 ('clang', 'coverage+bumpleak'),
42 ],
43 phony_prefix='mycpp-unit')
44
45 for test_main in [
46 'mycpp/mark_sweep_heap_test.cc',
47 'mycpp/gc_heap_test.cc',
48 'mycpp/gc_stress_test.cc',
49 'mycpp/gc_builtins_test.cc',
50 'mycpp/gc_mops_test.cc',
51 'mycpp/gc_mylib_test.cc',
52 'mycpp/gc_dict_test.cc',
53 'mycpp/gc_list_test.cc',
54 'mycpp/gc_str_test.cc',
55 'mycpp/gc_tuple_test.cc',
56 'mycpp/small_str_test.cc',
57 'mycpp/yaks_runtime_test.cc',
58 ]:
59 ru.cc_binary(test_main,
60 deps=['//mycpp/runtime'],
61 matrix=COMPILERS_VARIANTS,
62 phony_prefix='mycpp-unit')
63
64 ru.cc_binary(
65 'mycpp/float_test.cc',
66 deps=['//mycpp/runtime'],
67 # Just test two compilers, in fast mode
68 matrix=[('cxx', 'opt'), ('clang', 'opt')],
69 phony_prefix='mycpp-unit')
70
71 for test_main in [
72 'mycpp/demo/gc_header.cc',
73 'mycpp/demo/hash_table.cc',
74 'mycpp/demo/target_lang.cc',
75 ]:
76 ru.cc_binary(test_main,
77 deps=['//mycpp/runtime'],
78 matrix=COMPILERS_VARIANTS,
79 phony_prefix='mycpp-unit')
80
81 # ASDL schema that examples/parse.py depends on
82 ru.asdl_library('mycpp/examples/expr.asdl')
83
84
85#
86# mycpp/examples build config
87#
88
89# TODO:
90# - Fold this dependency into a proper shwrap wrapper
91# - Make a n.build() wrapper that takes it into account automatically
92RULES_PY = 'build/ninja-rules-py.sh'
93
94# special ones in examples.sh:
95# - parse
96# - lexer_main -- these use Oil code
97# - pgen2_demo -- uses pgen2
98
99
100def ShouldSkipBuild(name):
101 if name.startswith('invalid_'):
102 return True
103
104 if name in [
105 # these use Oil code, and don't type check or compile. Maybe give up on
106 # them? pgen2_demo might be useful later.
107 'lexer_main',
108 'pgen2_demo',
109 ]:
110 return True
111
112 return False
113
114
115def ExamplesToBuild():
116 filenames = os.listdir('mycpp/examples')
117 py = [
118 name[:-3] for name in filenames
119 if name.endswith('.py') and name != '__init__.py'
120 ]
121
122 to_test = [name for name in py if not ShouldSkipBuild(name)]
123
124 return to_test
125
126
127def ShouldSkipTest(name):
128 return False
129
130
131def ShouldSkipBenchmark(name):
132 return name.startswith('test_')
133
134
135TRANSLATE_FILES = {
136 # TODO: We could also use app_deps.py here
137 # BUG: modules.py must be listed last. Order matters with inheritance
138 # across modules!
139 'modules': [
140 'mycpp/testpkg/module1.py',
141 'mycpp/testpkg/module2.py',
142 'mycpp/examples/modules.py',
143 ],
144 'parse': [], # added dynamically from mycpp/examples/parse.translate.txt
145}
146
147# Unused. Could use mycpp/examples/parse.typecheck.txt
148EXAMPLES_PY = {
149 'parse': [],
150}
151
152
153def TranslatorSubgraph(ru, translator, ex):
154 n = ru.n
155
156 raw = '_gen/mycpp/examples/%s_raw.%s.cc' % (ex, translator)
157
158 # Translate to C++
159 if ex in TRANSLATE_FILES:
160 to_translate = TRANSLATE_FILES[ex]
161 else:
162 to_translate = ['mycpp/examples/%s.py' % ex]
163
164 # Implicit dependency: if the translator changes, regenerate source code.
165 # But don't pass it on the command line.
166 translator_wrapper = '_bin/shwrap/%s_main' % translator
167
168 n.build(
169 raw,
170 'translate-%s' % translator,
171 to_translate,
172 implicit=[translator_wrapper],
173 # examples/parse uses pyext/fastfunc.pyi
174 variables=[('mypypath',
175 '$NINJA_REPO_ROOT/mycpp:$NINJA_REPO_ROOT/pyext')])
176
177 p = 'mycpp/examples/%s_preamble.h' % ex
178 # Ninja empty string!
179 preamble_path = p if os.path.exists(p) else "''"
180
181 main_cc_src = '_gen/mycpp/examples/%s.%s.cc' % (ex, translator)
182
183 # Make a translation unit
184 n.build(main_cc_src,
185 'wrap-cc',
186 raw,
187 implicit=[RULES_PY],
188 variables=[('name', ex), ('preamble_path', preamble_path),
189 ('translator', translator)])
190
191 n.newline()
192
193 if translator == 'pea':
194 ru.phony['pea-translate'].append(main_cc_src)
195
196 if translator == 'mycpp':
197 example_matrix = COMPILERS_VARIANTS
198 else:
199 # pea just has one variant for now
200 example_matrix = [('cxx', 'asan+gcalways')]
201
202 if translator == 'mycpp':
203 phony_prefix = 'mycpp-examples'
204 else:
205 phony_prefix = ''
206
207 deps = ['//mycpp/runtime']
208 if ex == 'parse':
209 deps = deps + ['//mycpp/examples/expr.asdl', '//cpp/data_lang']
210
211 ru.cc_binary(
212 main_cc_src,
213 deps=deps,
214 matrix=example_matrix,
215 phony_prefix=phony_prefix,
216 )
217
218 # TODO:
219 # - restore lost 'pea-compile' tag?
220
221
222def NinjaGraph(ru):
223 n = ru.n
224
225 ru.comment('Generated by %s' % __name__)
226
227 # Running build/ninja_main.py
228 this_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
229
230 n.variable('NINJA_REPO_ROOT', os.path.dirname(this_dir))
231 n.newline()
232
233 # mycpp and pea have the same interface
234 n.rule('translate-mycpp',
235 command='_bin/shwrap/mycpp_main $mypypath $out $in',
236 description='mycpp $mypypath $out $in')
237 n.newline()
238
239 n.rule('translate-pea',
240 command='_bin/shwrap/pea_main $mypypath $out $in',
241 description='pea $mypypath $out $in')
242 n.newline()
243
244 n.rule(
245 'wrap-cc',
246 command=
247 'build/ninja-rules-py.sh wrap-cc $out $translator $name $in $preamble_path',
248 description='wrap-cc $out $translator $name $in $preamble_path $out')
249 n.newline()
250 n.rule(
251 'example-task',
252 # note: $out can be MULTIPLE FILES, shell-quoted
253 command='build/ninja-rules-py.sh example-task $name $impl $bin $out',
254 description='example-task $name $impl $bin $out')
255 n.newline()
256 n.rule(
257 'typecheck',
258 command='build/ninja-rules-py.sh typecheck $main_py $out $skip_imports',
259 description='typecheck $main_py $out $skip_imports')
260 n.newline()
261 n.rule('logs-equal',
262 command='build/ninja-rules-py.sh logs-equal $out $in',
263 description='logs-equal $out $in')
264 n.newline()
265 n.rule('benchmark-table',
266 command='build/ninja-rules-py.sh benchmark-table $out $in',
267 description='benchmark-table $out $in')
268 n.newline()
269
270 # For simplicity, this is committed to the repo. We could also have
271 # build/dev.sh minimal generate it?
272 with open('mycpp/examples/parse.translate.txt') as f:
273 for line in f:
274 path = line.strip()
275 TRANSLATE_FILES['parse'].append(path)
276
277 examples = ExamplesToBuild()
278 #examples = ['cgi', 'containers', 'fib_iter']
279
280 # Groups of targets. Not all of these are run by default.
281 ph = {
282 'mycpp-typecheck':
283 [], # optional: for debugging only. translation does it.
284 'mycpp-strip':
285 [], # optional: strip binaries. To see how big they are.
286
287 # Compare logs for tests AND benchmarks.
288 # It's a separate task because we have multiple variants to compare, and
289 # the timing of test/benchmark tasks should NOT include comparison.
290 'mycpp-logs-equal': [],
291
292 # NOTE: _test/benchmark-table.tsv isn't included in any phony target
293
294 # Targets dynamically added:
295 #
296 # mycpp-unit-$compiler-$variant
297 # mycpp-examples-$compiler-$variant
298 'pea-translate': [],
299 'pea-compile': [],
300 # TODO: eventually we will have pea-logs-equal, and pea-benchmark-table
301 }
302 ru.AddPhony(ph)
303
304 DefineTargets(ru)
305
306 #
307 # Build and run examples/
308 #
309
310 to_compare = []
311 benchmark_tasks = []
312
313 for ex in examples:
314 ru.comment('- mycpp/examples/%s' % ex)
315
316 # TODO: make a phony target for these, since they're not strictly necessary.
317 # Translation does everything that type checking does. Type checking only
318 # is useful for debugging.
319 t = '_test/tasks/typecheck/%s.log.txt' % ex
320 main_py = 'mycpp/examples/%s.py' % ex
321
322 # expr.asdl needs to import pylib.collections_, which doesn't type check
323 skip_imports = 'T' if (ex == 'parse') else "''"
324
325 n.build(
326 [t],
327 'typecheck',
328 # TODO: Use mycpp/examples/parse.typecheck.txt
329 EXAMPLES_PY.get(ex, []) + [main_py],
330 variables=[('main_py', main_py), ('skip_imports', skip_imports)])
331 n.newline()
332 ru.phony['mycpp-typecheck'].append(t)
333
334 # Run Python.
335 for mode in ['test', 'benchmark']:
336 prefix = '_test/tasks/%s/%s.py' % (mode, ex)
337 task_out = '%s.task.txt' % prefix
338
339 if mode == 'benchmark':
340 if ShouldSkipBenchmark(ex):
341 #log('Skipping benchmark of %s', ex)
342 continue
343 benchmark_tasks.append(task_out)
344
345 elif mode == 'test':
346 if ShouldSkipTest(ex):
347 #log('Skipping test of %s', ex)
348 continue
349
350 # TODO: This should be a Python stub!
351 log_out = '%s.log' % prefix
352 n.build([task_out, log_out],
353 'example-task',
354 EXAMPLES_PY.get(ex, []) + ['mycpp/examples/%s.py' % ex],
355 variables=[('bin', main_py), ('name', ex),
356 ('impl', 'Python')])
357
358 n.newline()
359
360 for translator in ['mycpp', 'pea']:
361 TranslatorSubgraph(ru, translator, ex)
362
363 # Don't run it for now; just compile
364 if translator == 'pea':
365 continue
366
367 # minimal
368 MATRIX = [
369 ('test', 'asan'), # TODO: asan+gcalways is better!
370 ('benchmark', 'opt'),
371 ]
372
373 # Run the binary in two ways
374 for mode, variant in MATRIX:
375 task_out = '_test/tasks/%s/%s.%s.%s.task.txt' % (
376 mode, ex, translator, variant)
377
378 if mode == 'benchmark':
379 if ShouldSkipBenchmark(ex):
380 #log('Skipping benchmark of %s', ex)
381 continue
382 benchmark_tasks.append(task_out)
383
384 elif mode == 'test':
385 if ShouldSkipTest(ex):
386 #log('Skipping test of %s', ex)
387 continue
388
389 cc_log_out = '_test/tasks/%s/%s.%s.%s.log' % (
390 mode, ex, translator, variant)
391 py_log_out = '_test/tasks/%s/%s.py.log' % (mode, ex)
392
393 to_compare.append(cc_log_out)
394 to_compare.append(py_log_out)
395
396 # Only test cxx- variant
397 b_example = '_bin/cxx-%s/mycpp/examples/%s.%s' % (variant, ex,
398 translator)
399 n.build([task_out, cc_log_out],
400 'example-task', [b_example],
401 variables=[('bin', b_example), ('name', ex),
402 ('impl', 'C++')])
403 n.newline()
404
405 # Compare the log of all examples
406 out = '_test/mycpp-compare-passing.txt'
407 n.build([out], 'logs-equal', to_compare)
408 n.newline()
409
410 # NOTE: Don't really need this
411 ru.phony['mycpp-logs-equal'].append(out)
412
413 # Timing of benchmarks
414 out = '_test/benchmark-table.tsv'
415 n.build([out], 'benchmark-table', benchmark_tasks)
416 n.newline()