18
18
hackily decide based on whether setuptools has been imported already.
19
19
"""
20
20
21
- import glob
22
21
import sys
23
22
import os .path
24
23
import hashlib
25
24
import time
26
25
import re
27
26
28
- from typing import List , Tuple , Any , Optional , Dict , Union , Set , cast
27
+ from typing import List , Tuple , Any , Optional , Dict , Union , Set , Iterable , cast
29
28
from typing_extensions import TYPE_CHECKING , NoReturn , Type
30
29
31
30
from mypy .main import process_options
32
31
from mypy .errors import CompileError
33
32
from mypy .options import Options
34
33
from mypy .build import BuildSource
34
+ from mypy .fscache import FileSystemCache
35
+
35
36
from mypyc .namegen import exported_name
36
37
from mypyc .options import CompilerOptions
37
38
from mypyc .errors import Errors
38
- from mypyc .common import BUILD_DIR , shared_lib_name
39
+ from mypyc .common import shared_lib_name
39
40
from mypyc .ops import format_modules
40
41
41
42
from mypyc import emitmodule
@@ -78,16 +79,21 @@ def fail(message: str) -> NoReturn:
78
79
sys .exit (message )
79
80
80
81
81
- def get_mypy_config (paths : List [str ],
82
- mypy_options : Optional [List [str ]],
83
- compiler_options : CompilerOptions ) -> Tuple [List [BuildSource ], Options ]:
82
+ def get_mypy_config (mypy_options : List [str ],
83
+ only_compile_paths : Optional [Iterable [str ]],
84
+ compiler_options : CompilerOptions ,
85
+ fscache : Optional [FileSystemCache ],
86
+ ) -> Tuple [List [BuildSource ], List [BuildSource ], Options ]:
84
87
"""Construct mypy BuildSources and Options from file and options lists"""
85
- # It is kind of silly to do this but oh well
86
- mypy_options = mypy_options or []
87
- mypy_options .append ('--' )
88
- mypy_options .extend (paths )
88
+ all_sources , options = process_options (mypy_options , fscache = fscache )
89
+ if only_compile_paths :
90
+ paths_set = set (only_compile_paths )
91
+ mypyc_sources = [s for s in all_sources if s .path in paths_set ]
92
+ else :
93
+ mypyc_sources = all_sources
89
94
90
- sources , options = process_options (mypy_options )
95
+ if not mypyc_sources :
96
+ return mypyc_sources , all_sources , options
91
97
92
98
# Override whatever python_version is inferred from the .ini file,
93
99
# and set the python_version to be the currently used version.
@@ -104,10 +110,10 @@ def get_mypy_config(paths: List[str],
104
110
options .incremental = compiler_options .separate
105
111
options .preserve_asts = True
106
112
107
- for source in sources :
113
+ for source in mypyc_sources :
108
114
options .per_module_options .setdefault (source .module , {})['mypyc' ] = True
109
115
110
- return sources , options
116
+ return mypyc_sources , all_sources , options
111
117
112
118
113
119
shim_template = """\
@@ -170,6 +176,7 @@ def include_dir() -> str:
170
176
def generate_c (sources : List [BuildSource ],
171
177
options : Options ,
172
178
groups : emitmodule .Groups ,
179
+ fscache : FileSystemCache ,
173
180
compiler_options : Optional [CompilerOptions ] = None
174
181
) -> Tuple [List [List [Tuple [str , str ]]], str ]:
175
182
"""Drive the actual core compilation step.
@@ -185,7 +192,8 @@ def generate_c(sources: List[BuildSource],
185
192
# Do the actual work now
186
193
t0 = time .time ()
187
194
try :
188
- result = emitmodule .parse_and_typecheck (sources , options , groups )
195
+ result = emitmodule .parse_and_typecheck (
196
+ sources , options , compiler_options , groups , fscache )
189
197
except CompileError as e :
190
198
for line in e .messages :
191
199
print (line )
@@ -195,10 +203,6 @@ def generate_c(sources: List[BuildSource],
195
203
if compiler_options .verbose :
196
204
print ("Parsed and typechecked in {:.3f}s" .format (t1 - t0 ))
197
205
198
- all_module_names = []
199
- for group_sources , _ in groups :
200
- all_module_names .extend ([source .module for source in group_sources ])
201
-
202
206
errors = Errors ()
203
207
204
208
modules , ctext = emitmodule .compile_modules_to_c (result ,
@@ -293,6 +297,7 @@ def write_file(path: str, contents: str) -> None:
293
297
except IOError :
294
298
old_contents = None
295
299
if old_contents != encoded_contents :
300
+ os .makedirs (os .path .dirname (path ), exist_ok = True )
296
301
with open (path , 'wb' ) as f :
297
302
f .write (encoded_contents )
298
303
@@ -363,24 +368,29 @@ def get_header_deps(cfiles: List[Tuple[str, str]]) -> List[str]:
363
368
364
369
def mypycify (
365
370
paths : List [str ],
366
- mypy_options : Optional [List [str ]] = None ,
367
371
* ,
372
+ only_compile_paths : Optional [Iterable [str ]] = None ,
368
373
verbose : bool = False ,
369
374
opt_level : str = '3' ,
370
375
strip_asserts : bool = False ,
371
376
multi_file : bool = False ,
372
377
separate : Union [bool , List [Tuple [List [str ], Optional [str ]]]] = False ,
373
- skip_cgen_input : Optional [Any ] = None
378
+ skip_cgen_input : Optional [Any ] = None ,
379
+ target_dir : Optional [str ] = None ,
380
+ include_runtime_files : Optional [bool ] = None
374
381
) -> List ['Extension' ]:
375
382
"""Main entry point to building using mypyc.
376
383
377
384
This produces a list of Extension objects that should be passed as the
378
385
ext_modules parameter to setup.
379
386
380
387
Arguments:
381
- paths: A list of file paths to build. It may contain globs.
382
- mypy_options: Optionally, a list of command line flags to pass to mypy.
383
- (This can also contain additional files, for compatibility reasons.)
388
+ paths: A list of file paths to build. It may also contain mypy options.
389
+ only_compile_paths: If not None, an iterable of paths that are to be
390
+ the only modules compiled, even if other modules
391
+ appear in the mypy command line given to paths.
392
+ (These modules must still be passed to paths.)
393
+
384
394
verbose: Should mypyc be more verbose. Defaults to false.
385
395
386
396
opt_level: The optimization level, as a string. Defaults to '3' (meaning '-O3').
@@ -401,6 +411,11 @@ def mypycify(
401
411
speed up compilation, but calls between groups can
402
412
be slower than calls within a group and can't be
403
413
inlined.
414
+ target_dir: The directory to write C output files. Defaults to 'build'.
415
+ include_runtime_files: If not None, whether the mypyc runtime library
416
+ should be directly #include'd instead of linked
417
+ separately in order to reduce compiler invocations.
418
+ Defaults to False in multi_file mode, True otherwise.
404
419
"""
405
420
406
421
setup_mypycify_vars ()
@@ -409,6 +424,8 @@ def mypycify(
409
424
multi_file = multi_file ,
410
425
verbose = verbose ,
411
426
separate = separate is not False ,
427
+ target_dir = target_dir ,
428
+ include_runtime_files = include_runtime_files ,
412
429
)
413
430
414
431
# Create a compiler object so we can make decisions based on what
@@ -417,32 +434,25 @@ def mypycify(
417
434
compiler = ccompiler .new_compiler () # type: Any
418
435
sysconfig .customize_compiler (compiler )
419
436
420
- expanded_paths = []
421
- for path in paths :
422
- expanded_paths .extend (glob .glob (path ))
423
-
424
- build_dir = BUILD_DIR # TODO: can this be overridden??
425
- try :
426
- os .mkdir (build_dir )
427
- except FileExistsError :
428
- pass
437
+ build_dir = compiler_options .target_dir
429
438
430
- sources , options = get_mypy_config (expanded_paths , mypy_options , compiler_options )
439
+ fscache = FileSystemCache ()
440
+ mypyc_sources , all_sources , options = get_mypy_config (
441
+ paths , only_compile_paths , compiler_options , fscache )
431
442
# We generate a shared lib if there are multiple modules or if any
432
443
# of the modules are in package. (Because I didn't want to fuss
433
444
# around with making the single module code handle packages.)
434
- use_shared_lib = len (sources ) > 1 or any ('.' in x .module for x in sources )
445
+ use_shared_lib = len (mypyc_sources ) > 1 or any ('.' in x .module for x in mypyc_sources )
435
446
436
- groups = construct_groups (sources , separate , use_shared_lib )
447
+ groups = construct_groups (mypyc_sources , separate , use_shared_lib )
437
448
438
449
# We let the test harness just pass in the c file contents instead
439
450
# so that it can do a corner-cutting version without full stubs.
440
451
if not skip_cgen_input :
441
- group_cfiles , ops_text = generate_c (sources , options , groups ,
452
+ group_cfiles , ops_text = generate_c (all_sources , options , groups , fscache ,
442
453
compiler_options = compiler_options )
443
454
# TODO: unique names?
444
- with open (os .path .join (build_dir , 'ops.txt' ), 'w' ) as f :
445
- f .write (ops_text )
455
+ write_file (os .path .join (build_dir , 'ops.txt' ), ops_text )
446
456
else :
447
457
group_cfiles = skip_cgen_input
448
458
@@ -487,10 +497,11 @@ def mypycify(
487
497
'/wd9025' , # warning about overriding /GL
488
498
]
489
499
490
- # In multi-file mode, copy the runtime library in.
491
- # Otherwise it just gets #included to save on compiler invocations
500
+ # If configured to (defaults to yes in multi-file mode), copy the
501
+ # runtime library in. Otherwise it just gets #included to save on
502
+ # compiler invocations.
492
503
shared_cfilenames = []
493
- if multi_file :
504
+ if not compiler_options . include_runtime_files :
494
505
for name in ['CPy.c' , 'getargs.c' ]:
495
506
rt_file = os .path .join (build_dir , name )
496
507
with open (os .path .join (include_dir (), name ), encoding = 'utf-8' ) as f :
0 commit comments