OILS / build / ninja_lib.py View on Github | oils.pub

823 lines, 470 significant
1#!/usr/bin/env python2
2"""
3ninja_lib.py
4
5Runtime options:
6
7 CXXFLAGS Additional flags to pass to the C++ compiler
8
9Notes on ninja_syntax.py:
10
11- escape_path() seems wrong?
12 - It should really take $ to $$.
13 - It doesn't escape newlines
14
15 return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
16
17 Ninja shouldn't have used $ and ALSO used shell commands (sh -c)! Better
18 solutions:
19
20 - Spawn a process with environment variables.
21 - use % for substitution instead
22
23- Another problem: Ninja doesn't escape the # comment character like $#, so
24 how can you write a string with a # as the first char on a line?
25"""
26from __future__ import print_function
27
28import collections
29import os
30import sys
31
32
33def log(msg, *args):
34 if args:
35 msg = msg % args
36 print(msg, file=sys.stderr)
37
38
39# Matrix of configurations
40
41COMPILERS_VARIANTS = [
42 ('cxx', 'dbg'),
43 ('cxx', 'opt'),
44 ('cxx', 'asan'),
45 ('cxx', 'asan+gcalways'),
46 ('cxx', 'asan32+gcalways'),
47 ('cxx', 'ubsan'),
48
49 #('clang', 'asan'),
50 ('clang', 'dbg'), # compile-quickly
51 ('clang', 'opt'), # for comparisons
52 ('clang', 'ubsan'), # finds different bugs
53 ('clang', 'coverage'),
54]
55
56GC_PERF_VARIANTS = [
57 ('cxx', 'opt+bumpleak'),
58 ('cxx', 'opt+bumproot'),
59 ('cxx', 'opt+bumpsmall'),
60 #('cxx', 'asan+bumpsmall'),
61 ('cxx', 'opt+nopool'),
62
63 # TODO: should be binary with different files
64 #('cxx', 'opt+tcmalloc'),
65
66 # For tracing allocations, or debugging
67 ('cxx', 'uftrace'),
68
69 # Test performance of 32-bit build. (It uses less memory usage, but can be
70 # slower.)
71 ('cxx', 'opt32'),
72]
73
74OTHER_VARIANTS = [
75 # Affects mycpp/gc_mops.cc - we can do overflow checking
76 ('cxx', 'opt+bigint'),
77 ('cxx', 'asan+bigint'),
78]
79
80SMALL_TEST_MATRIX = [
81 ('cxx', 'asan'),
82 ('cxx', 'ubsan'),
83 ('clang', 'coverage'),
84]
85
86
87def ConfigDir(config):
88 compiler, variant, more_cxx_flags = config
89 if more_cxx_flags is None:
90 return '%s-%s' % (compiler, variant)
91 else:
92 # -D CPP_UNIT_TEST -> D_CPP_UNIT_TEST
93 flags_str = more_cxx_flags.replace('-', '').replace(' ', '_')
94 return '%s-%s-%s' % (compiler, variant, flags_str)
95
96
97def ObjPath(src_path, config):
98 rel_path, _ = os.path.splitext(src_path)
99 return '_build/obj/%s/%s.o' % (ConfigDir(config), rel_path)
100
101
102# Used namedtuple since it doesn't have any state
103CcBinary = collections.namedtuple(
104 'CcBinary',
105 'main_cc symlinks implicit deps matrix phony_prefix preprocessed bin_path')
106
107
108class Rules(object):
109 """High-level wrapper for NinjaWriter
110
111 What should it handle?
112
113 - The (compiler, variant) matrix loop
114 - Implicit deps for generated code
115 - Phony convenience targets
116
117 Maybe: exporting data to test runner
118
119 Terminology:
120
121 Ninja has
122 - rules, which are like Bazel "actions"
123 - build targets
124
125 Our library has:
126 - Build config: (compiler, variant), and more later
127
128 - Labels: identifiers starting with //, which are higher level than Ninja
129 "targets"
130 cc_library:
131 //mycpp/runtime
132
133 //mycpp/examples/expr.asdl
134 //frontend/syntax.asdl
135
136 - Deps are lists of labels, and have a transitive closure
137
138 - H Rules / High level rules? B rules / Boil?
139 cc_binary, cc_library, asdl, etc.
140 """
141
142 def __init__(self, n):
143 self.n = n # direct ninja writer
144
145 self.cc_bins = [] # list of CcBinary() objects to write
146 self.cc_libs = {} # label -> CcLibrary object
147
148 self.phony = {} # list of phony targets
149
150 def AddPhony(self, phony_to_add):
151 self.phony.update(phony_to_add)
152
153 def WritePhony(self):
154 for name in sorted(self.phony):
155 targets = self.phony[name]
156 if targets:
157 self.n.build([name], 'phony', targets)
158 self.n.newline()
159
160 def compile(self,
161 out_obj,
162 in_cc,
163 deps,
164 config,
165 implicit=None,
166 maybe_preprocess=False):
167 """ .cc -> compiler -> .o """
168
169 implicit = implicit or []
170
171 compiler, variant, more_cxx_flags = config
172 if more_cxx_flags is None:
173 flags_str = "''"
174 else:
175 assert "'" not in more_cxx_flags, more_cxx_flags # can't handle single quotes
176 flags_str = "'%s'" % more_cxx_flags
177
178 v = [('compiler', compiler), ('variant', variant),
179 ('more_cxx_flags', flags_str)]
180 if maybe_preprocess:
181 # Limit it to certain configs
182 if more_cxx_flags is None and variant in ('dbg', 'opt'):
183 pre = '_build/preprocessed/%s-%s/%s' % (compiler, variant,
184 in_cc)
185 self.n.build(pre,
186 'preprocess', [in_cc],
187 implicit=implicit,
188 variables=v)
189 else:
190 self.n.build([out_obj],
191 'compile_one', [in_cc],
192 implicit=implicit,
193 variables=v)
194
195 self.n.newline()
196
197 def link(self, out_bin, main_obj, deps, config):
198 """ list of .o -> linker -> executable, along with stripped version """
199 compiler, variant, _ = config
200
201 assert isinstance(out_bin, str), out_bin
202 assert isinstance(main_obj, str), main_obj
203
204 objects = [main_obj]
205 for label in deps:
206 try:
207 cc_lib = self.cc_libs[label]
208 except KeyError:
209 raise RuntimeError("Couldn't resolve label %r" % label)
210
211 o = cc_lib.obj_lookup[config]
212 objects.extend(o)
213
214 v = [('compiler', compiler), ('variant', variant),
215 ('more_link_flags', "''")]
216 self.n.build([out_bin], 'link', objects, variables=v)
217 self.n.newline()
218
219 # Strip any .opt binaries
220 if variant.startswith('opt') or variant.startswith('opt32'):
221 stripped = out_bin + '.stripped'
222 symbols = out_bin + '.symbols'
223 self.n.build([stripped, symbols], 'strip', [out_bin])
224 self.n.newline()
225
226 def comment(self, s):
227 self.n.comment(s)
228 self.n.newline()
229
230 def cc_library(
231 self,
232 label,
233 srcs=None,
234 implicit=None,
235 deps=None,
236 # note: headers is only used for tarball manifest, not compiler command line
237 headers=None,
238 generated_headers=None):
239
240 # srcs = [] is allowed for _gen/asdl/hnode.asdl.h
241 if srcs is None:
242 raise RuntimeError('cc_library %r requires srcs' % label)
243
244 implicit = implicit or []
245 deps = deps or []
246 headers = headers or []
247 generated_headers = generated_headers or []
248
249 if label in self.cc_libs:
250 raise RuntimeError('%s was already defined' % label)
251
252 self.cc_libs[label] = CcLibrary(label, srcs, implicit, deps, headers,
253 generated_headers)
254
255 def cc_binary(
256 self,
257 main_cc, # e.g. cpp/core_test.cc
258 symlinks=None, # make these symlinks - separate rules?
259 implicit=None, # for COMPILE action, not link action
260 deps=None, # libraries to depend on, transitive closure
261 matrix=None, # $compiler $variant +bumpleak
262 phony_prefix=None, # group
263 preprocessed=False, # generate report
264 bin_path=None, # default is _bin/$compiler-$variant/rel/path
265 ):
266 """
267 A cc_binary() depends on a list of cc_library() rules specified by
268 //package/label
269
270 It accepts a config matrix of (compiler, variant, +other)
271
272 The transitive closure is computed.
273
274 Then we write Ninja rules corresponding to each dependent library, with
275 respect to the config.
276 """
277 symlinks = symlinks or []
278 implicit = implicit or []
279 deps = deps or []
280 if not matrix:
281 raise RuntimeError("Config matrix required")
282
283 cc_bin = CcBinary(main_cc, symlinks, implicit, deps, matrix,
284 phony_prefix, preprocessed, bin_path)
285
286 self.cc_bins.append(cc_bin)
287
288 def asdl_library(self,
289 asdl_path,
290 deps=None,
291 pretty_print_methods=True,
292 abbrev_module=None):
293
294 deps = deps or []
295
296 # SYSTEM header, _gen/asdl/hnode.asdl.h
297 deps.append('//asdl/hnode.asdl')
298 deps.append('//display/pretty.asdl')
299
300 # to create _gen/mycpp/examples/expr.asdl.h
301 prefix = '_gen/%s' % asdl_path
302
303 out_cc = prefix + '.cc'
304 out_header = prefix + '.h'
305
306 asdl_flags = []
307
308 if pretty_print_methods:
309 outputs = [out_cc, out_header]
310 else:
311 outputs = [out_header]
312 asdl_flags.append('--no-pretty-print-methods')
313
314 if abbrev_module:
315 asdl_flags.append('--abbrev-module=%s' % abbrev_module)
316
317 debug_mod = prefix + '_debug.py'
318 outputs.append(debug_mod)
319
320 # Generating syntax_asdl.h does NOT depend on hnode_asdl.h existing ...
321 self.n.build(outputs,
322 'asdl-cpp', [asdl_path],
323 implicit=['_bin/shwrap/asdl_main'],
324 variables=[
325 ('action', 'cpp'),
326 ('out_prefix', prefix),
327 ('asdl_flags', ' '.join(asdl_flags)),
328 ('debug_mod', debug_mod),
329 ])
330 self.n.newline()
331
332 # ... But COMPILING anything that #includes it does.
333 # Note: assumes there's a build rule for this "system" ASDL schema
334
335 srcs = [out_cc] if pretty_print_methods else []
336 # Define lazy CC library
337 self.cc_library(
338 '//' + asdl_path,
339 srcs=srcs,
340 deps=deps,
341 # For compile_one steps of files that #include this ASDL file
342 generated_headers=[out_header],
343 )
344
345 def py_binary(self, main_py, deps_base_dir='_build/NINJA', template='py'):
346 """Wrapper for Python script with dynamically discovered deps
347
348 Args:
349 template: py, mycpp, or pea
350
351 Example:
352 _bin/shwrap/mycpp_main wraps mycpp/mycpp_main.py
353 - using dependencies from prebuilt/ninja/mycpp.mycpp_main/deps.txt
354 - with the 'shwrap-mycpp' template defined in build/ninja-lib.sh
355 """
356 rel_path, _ = os.path.splitext(main_py)
357 # asdl/asdl_main.py -> asdl.asdl_main
358 py_module = rel_path.replace('/', '.')
359
360 deps_path = os.path.join(deps_base_dir, py_module, 'deps.txt')
361 with open(deps_path) as f:
362 deps = [line.strip() for line in f]
363
364 deps.remove(main_py) # raises ValueError if it's not there
365
366 shwrap_name = os.path.basename(rel_path)
367 self.n.build('_bin/shwrap/%s' % shwrap_name,
368 'write-shwrap', [main_py] + deps,
369 variables=[('template', template)])
370 self.n.newline()
371
372 def souffle_binary(self, souffle_cpp):
373 """
374 Compile souffle C++ into a native executable.
375 """
376 rel_path, _ = os.path.splitext(souffle_cpp)
377 basename = os.path.basename(rel_path)
378
379 souffle_obj = '_build/obj/datalog/%s.o' % basename
380 self.n.build([souffle_obj],
381 'compile_one',
382 souffle_cpp,
383 variables=[('compiler', 'cxx'), ('variant', 'opt'),
384 ('more_cxx_flags', "'-Ivendor -std=c++17'")])
385
386 souffle_bin = '_bin/datalog/%s' % basename
387 self.n.build([souffle_bin],
388 'link',
389 souffle_obj,
390 variables=[('compiler', 'cxx'), ('variant', 'opt'),
391 ('more_link_flags', "'-lstdc++fs'")])
392
393 self.n.newline()
394
395
396def _TransitiveClosure(cc_libs, name, deps, unique_out):
397 """
398 Args:
399 name: for error messages
400 """
401 for label in deps:
402 if label in unique_out:
403 continue
404 unique_out.add(label)
405
406 try:
407 cc_lib = cc_libs[label]
408 except KeyError:
409 raise RuntimeError('Undefined label %s in %r' % (label, name))
410
411 _TransitiveClosure(cc_libs, cc_lib.label, cc_lib.deps, unique_out)
412
413
414def _CalculateDeps(cc_libs, cc_rule, debug_name=''):
415 """ Compile actions for cc_library() also need implicit deps on generated headers"""
416 out_deps = set()
417 _TransitiveClosure(cc_libs, debug_name, cc_rule.deps, out_deps)
418 unique_deps = sorted(out_deps)
419
420 implicit = list(cc_rule.implicit) # copy
421 for label in unique_deps:
422 cc_lib = cc_libs[label]
423 implicit.extend(cc_lib.generated_headers)
424 return unique_deps, implicit
425
426
427class CcLibrary(object):
428 """
429 Life cycle:
430
431 1. A cc_library is first created
432 2. A cc_binary can depend on it
433 - maybe writing rules, and ensuring uniques per configuration
434 3. The link step needs the list of objects
435 4. The tarball needs the list of sources for binary
436 """
437
438 def __init__(self, label, srcs, implicit, deps, headers,
439 generated_headers):
440 self.label = label
441 self.srcs = srcs # queried by SourcesForBinary
442 self.implicit = implicit
443 self.deps = deps
444 self.headers = headers
445 # TODO: asdl() rule should add to this.
446 # Generated headers are different than regular headers. The former need an
447 # implicit dep in Ninja, while the latter can rely on the .d mechanism.
448 self.generated_headers = generated_headers
449
450 self.obj_lookup = {} # config -> list of objects
451 self.preprocessed_lookup = {} # config -> boolean
452
453 def MaybeWrite(self, ru, config, preprocessed):
454 """
455 Args:
456 preprocessed: Did the cc_binary() request preprocessing?
457 """
458 if config not in self.obj_lookup: # already written by some other cc_binary()
459 _, implicit = _CalculateDeps(ru.cc_libs,
460 self,
461 debug_name=self.label)
462
463 objects = []
464 for src in self.srcs:
465 obj = ObjPath(src, config)
466 ru.compile(obj, src, self.deps, config, implicit=implicit)
467 objects.append(obj)
468
469 self.obj_lookup[config] = objects
470
471 if preprocessed and (config not in self.preprocessed_lookup):
472 _, implicit = _CalculateDeps(ru.cc_libs,
473 self,
474 debug_name=self.label)
475
476 for src in self.srcs:
477 # no output needed
478 ru.compile('',
479 src,
480 self.deps,
481 config,
482 implicit=implicit,
483 maybe_preprocess=True)
484 self.preprocessed_lookup[config] = True
485
486
487class Deps(object):
488
489 def __init__(self, ru):
490 self.ru = ru
491 # main_cc -> list of LABELS, for tarball manifest
492 self.cc_binary_deps = {}
493
494 def SourcesForBinary(self, main_cc):
495 """
496 Used for preprocessed metrics, release tarball, _build/oils.sh, etc.
497 """
498 deps = self.cc_binary_deps[main_cc]
499 sources = [main_cc]
500 for label in deps:
501 sources.extend(self.ru.cc_libs[label].srcs)
502 return sources
503
504 def HeadersForBinary(self, main_cc):
505 deps = self.cc_binary_deps[main_cc]
506 headers = []
507 for label in deps:
508 headers.extend(self.ru.cc_libs[label].headers)
509 headers.extend(self.ru.cc_libs[label].generated_headers)
510 return headers
511
512 def WriteRules(self):
513 for cc_bin in self.ru.cc_bins:
514 self.WriteCcBinary(cc_bin)
515
516 def WriteCcBinary(self, cc_bin):
517 ru = self.ru
518 c = cc_bin
519
520 unique_deps, compile_imp = _CalculateDeps(ru.cc_libs,
521 cc_bin,
522 debug_name=cc_bin.main_cc)
523 # compile actions of binaries that have ASDL label deps need the
524 # generated header as implicit dep
525
526 # to compute tarball manifest, with SourcesForBinary()
527 self.cc_binary_deps[c.main_cc] = unique_deps
528
529 for config in c.matrix:
530 if len(config) == 2:
531 config = (config[0], config[1], None)
532
533 # Write cc_library() rules LAZILY
534 for label in unique_deps:
535 cc_lib = ru.cc_libs[label] # should exit
536 cc_lib.MaybeWrite(ru, config, c.preprocessed)
537
538 # Compile main object, maybe with IMPLICIT headers deps
539 main_obj = ObjPath(c.main_cc, config)
540 ru.compile(main_obj,
541 c.main_cc,
542 c.deps,
543 config,
544 implicit=compile_imp)
545 if c.preprocessed:
546 ru.compile('',
547 c.main_cc,
548 c.deps,
549 config,
550 implicit=compile_imp,
551 maybe_preprocess=True)
552
553 config_dir = ConfigDir(config)
554 bin_dir = '_bin/%s' % config_dir # e.g. _bin/cxx-asan
555
556 # Allow user to override bin_path
557 if c.bin_path:
558 # e.g. _bin/cxx-dbg/oils_for_unix
559 bin_to_link = '%s/%s' % (bin_dir, c.bin_path)
560 else:
561 # e.g. _gen/mycpp/examples/classes.mycpp
562 rel_path, _ = os.path.splitext(c.main_cc)
563
564 # Special rule for
565 # sources = hello.mycpp.cc and hello.mycpp-main.cc
566 # binary = _bin/hello.mycpp
567 if rel_path.endswith('-main'):
568 rel_path = rel_path[:-len('-main')]
569
570 # Put binary in _bin/cxx-dbg/mycpp/examples, not _bin/cxx-dbg/_gen/mycpp/examples
571 if rel_path.startswith('_gen/'):
572 rel_path = rel_path[len('_gen/'):]
573
574 bin_to_link = '%s/%s' % (bin_dir, rel_path)
575
576 # Link with OBJECT deps
577 ru.link(bin_to_link, main_obj, unique_deps, config)
578
579 # Make symlinks
580 symlink_dir = os.path.dirname(bin_to_link)
581 bin_name = os.path.basename(bin_to_link)
582 for symlink in c.symlinks:
583 ru.n.build(['%s/%s' % (symlink_dir, symlink)],
584 'symlink', [bin_to_link],
585 variables=[('dir', symlink_dir),
586 ('target', bin_name), ('new', symlink)])
587 ru.n.newline()
588
589 # Maybe add this cc_binary to a group
590 if c.phony_prefix:
591 key = '%s-%s' % (c.phony_prefix, config_dir)
592 if key not in ru.phony:
593 ru.phony[key] = []
594 ru.phony[key].append(bin_to_link)
595
596
597SHWRAP = {
598 'mycpp': '_bin/shwrap/mycpp_main',
599 'mycpp-souffle': '_bin/shwrap/mycpp_main_souffle',
600 'pea': '_bin/shwrap/pea_main',
601}
602
603# TODO: should have dependencies with sh_binary
604RULES_PY = 'build/ninja-rules-py.sh'
605
606# Copied from build/ninja-rules-py.sh mycpp-gen
607DEFAULT_MYPY_PATH = '$NINJA_REPO_ROOT:$NINJA_REPO_ROOT/pyext'
608
609
610def TryDynamicDeps(py_main):
611 """
612 Read dynamic deps files built in ./NINJA-config.sh
613 """
614 # bin/oils_for_unix
615 py_rel_path, _ = os.path.splitext(py_main)
616 # bin.oils_for_unix
617 py_module = py_rel_path.replace('/', '.')
618
619 deps_file = '_build/NINJA/%s/translate.txt' % py_module
620 if os.path.exists(deps_file):
621 with open(deps_file) as f:
622 return [line.strip() for line in f]
623
624 return None
625
626
627# TODO:
628#
629# //bin/oils_for_unix.mycpp
630# //bin/oils_for_unix.mycpp-souffle
631#
632# mycpp_library('bin/oils_for_unix.py',
633# mypy_path=''
634# preamble=''
635# souffle=True,
636# pea=True, # another option
637# )
638#
639# //bin/oils_for_unix.main
640#
641# gen_cpp_main(namespace='oils_for_unix',
642# style='example')
643#
644# mycpp_binary(
645# '//bin/oils_for_unix.main', # cc_library() for main
646# bin_path='bin/oils_for_unix.mycpp-souffle',
647# deps=['//bin/oils_for_unix.mycpp-souffle'],
648# )
649#
650# mycpp_binary(
651# '//bin/oils_for_unix.main',
652# deps=['//bin/oils_for_unix.mycpp'],
653# bin_path='bin/oils_for_unix.mycpp',
654# symlinks=[],
655# preprocessed=True,
656# phony_prefix=''
657# )
658
659
660def mycpp_library(ru,
661 py_main,
662 mypy_path=DEFAULT_MYPY_PATH,
663 preamble=None,
664 translator='mycpp',
665 py_inputs=None,
666 deps=None):
667 """
668 Generate a .cc file with mycpp, and a cc_library() for it
669 """
670
671 # e.g. bin/oils_for_unix
672 py_rel_path, _ = os.path.splitext(py_main)
673 # e.g. bin.oils_for_unix
674 #py_module = py_rel_path.replace('/', '.')
675
676 py_inputs = py_inputs or [py_main] # if not specified, it's a single file
677 #matrix = matrix or COMPILERS_VARIANTS
678 deps = deps or []
679
680 if preamble is None:
681 p = py_rel_path + '_preamble.h'
682 preamble = p if os.path.exists(p) else "''" # Ninja empty string!
683
684 n = ru.n
685
686 # Two steps
687 raw = '_gen/%s.%s.cc' % (py_rel_path, translator)
688
689 translator_shwrap = SHWRAP[translator]
690
691 n.build(
692 raw,
693 'translate-%s' % translator,
694 py_inputs, # files to translate
695 # Implicit dependency: if the translator changes, regenerate source
696 # code. But don't pass it on the command line.
697 implicit=[translator_shwrap],
698 # examples/parse uses pyext/fastfunc.pyi
699 variables=[('mypypath', mypy_path), ('preamble_path', preamble)])
700
701 ru.cc_library(
702 # e.g. //bin/oils_for_unix.mycpp-souffle
703 '//%s.%s' % (py_rel_path, translator),
704 srcs=[raw],
705 deps=deps,
706 #matrix=matrix,
707 )
708
709
710def main_cc(ru, main_cc, template='unix'):
711 """
712 Generate a .main.cc file, and a cc_library() for it
713 """
714 n = ru.n
715
716 # '_gen/bin/hello.mycpp-souffle.cc' -> hello
717 basename = os.path.basename(main_cc)
718 main_namespace = basename.split('.')[0]
719
720 n.build(
721 [main_cc],
722 'write-main',
723 [],
724 variables=[
725 ('template', template),
726 # e.g. 'hello'
727 ('main_namespace', main_namespace)
728 ])
729
730
731def mycpp_bin(ru,
732 cc_lib,
733 template='unix',
734 matrix=None,
735 bin_path=None,
736 symlinks=None,
737 preprocessed=False,
738 phony_prefix=None):
739 matrix = matrix or []
740
741 assert cc_lib.startswith('//')
742 rel_path = cc_lib[2:]
743 main_cc_path = '_gen/%s-main.cc' % rel_path
744
745 # Generate a main.cc file
746 main_cc(ru, main_cc_path, template=template)
747
748 # Then compile and link it
749 ru.cc_binary(main_cc_path,
750 deps=[cc_lib],
751 matrix=matrix,
752 bin_path=bin_path,
753 symlinks=symlinks,
754 preprocessed=preprocessed,
755 phony_prefix=phony_prefix)
756
757
758def mycpp_binary(ru,
759 py_main,
760 mypy_path=DEFAULT_MYPY_PATH,
761 bin_path=None,
762 symlinks=None,
763 preprocessed=False,
764 preamble=None,
765 translator='mycpp',
766 main_style='main-wrapper',
767 py_inputs=None,
768 phony_prefix=None,
769 matrix=None,
770 deps=None):
771 # e.g. bin/oils_for_unix
772 py_rel_path, _ = os.path.splitext(py_main)
773 # e.g. bin.oils_for_unix
774 py_module = py_rel_path.replace('/', '.')
775
776 py_inputs = py_inputs or [py_main] # if not specified, it's a single file
777 symlinks = symlinks or []
778 matrix = matrix or COMPILERS_VARIANTS
779 deps = deps or []
780 if preamble is None:
781 p = py_rel_path + '_preamble.h'
782 preamble = p if os.path.exists(p) else "''" # Ninja empty string!
783
784 n = ru.n
785
786 # Two steps
787 raw = '_gen/_tmp/%s.%s-raw.cc' % (py_rel_path, translator)
788 main_cc_src = '_gen/%s.%s.cc' % (py_rel_path, translator)
789
790 translator_shwrap = SHWRAP[translator]
791
792 n.build(
793 raw,
794 'translate-%s' % translator,
795 py_inputs, # files to translate
796 # Implicit dependency: if the translator changes, regenerate source
797 # code. But don't pass it on the command line.
798 implicit=[translator_shwrap],
799 # examples/parse uses pyext/fastfunc.pyi
800 variables=[('mypypath', mypy_path), ('preamble_path', preamble)])
801
802 # Make a translation unit
803 n.build(main_cc_src,
804 'wrap-cc',
805 raw,
806 implicit=[RULES_PY],
807 variables=[
808 ('main_namespace', os.path.basename(py_rel_path)),
809 ('main_style', main_style),
810 ('preamble', "''"),
811 ])
812
813 n.newline()
814
815 ru.cc_binary(
816 main_cc_src,
817 bin_path=bin_path,
818 symlinks=symlinks,
819 preprocessed=preprocessed,
820 deps=deps,
821 matrix=matrix,
822 phony_prefix=phony_prefix,
823 )