OILS / mycpp / NINJA_subgraph.py View on Github | oils.pub

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