Skip to content

Commit 91c616a

Browse files
committed
[mypyc] Support incremental compilation
This works by reworking IR generation to proceed a SCC at a time and writing out caches of serialized IR information so that we can generated code that calls into a module without compiling the module in full. A mypy plugin is used to ensure cache validity by checking that a hash of the metadata matches and that all of the generated source is present and matches. Closes mypyc/mypyc#682.
1 parent e5f19dd commit 91c616a

File tree

6 files changed

+513
-67
lines changed

6 files changed

+513
-67
lines changed

mypyc/build.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from mypyc.namegen import exported_name
3838
from mypyc.options import CompilerOptions
3939
from mypyc.errors import Errors
40-
from mypyc.common import shared_lib_name
40+
from mypyc.common import BUILD_DIR, shared_lib_name
4141
from mypyc.ops import format_modules
4242

4343
from mypyc import emitmodule
@@ -79,7 +79,8 @@ def fail(message: str) -> 'NoReturn':
7979

8080

8181
def get_mypy_config(paths: List[str],
82-
mypy_options: Optional[List[str]]) -> Tuple[List[BuildSource], Options]:
82+
mypy_options: Optional[List[str]],
83+
compiler_options: CompilerOptions) -> Tuple[List[BuildSource], Options]:
8384
"""Construct mypy BuildSources and Options from file and options lists"""
8485
# It is kind of silly to do this but oh well
8586
mypy_options = mypy_options or []
@@ -99,8 +100,7 @@ def get_mypy_config(paths: List[str],
99100
options.show_traceback = True
100101
# Needed to get types for all AST nodes
101102
options.export_types = True
102-
# TODO: Support incremental checking
103-
options.incremental = False
103+
options.incremental = compiler_options.separate
104104
options.preserve_asts = True
105105

106106
for source in sources:
@@ -184,7 +184,7 @@ def generate_c(sources: List[BuildSource],
184184
# Do the actual work now
185185
t0 = time.time()
186186
try:
187-
result = emitmodule.parse_and_typecheck(sources, options)
187+
result = emitmodule.parse_and_typecheck(sources, options, groups)
188188
except CompileError as e:
189189
for line in e.messages:
190190
print(line)
@@ -283,14 +283,17 @@ def write_file(path: str, contents: str) -> None:
283283
want to write, skip writing so as to preserve the mtime
284284
and avoid triggering recompilation.
285285
"""
286+
# We encode it ourselves and open the files as binary to avoid windows
287+
# newline translation
288+
encoded_contents = contents.encode('utf-8')
286289
try:
287-
with open(path, 'r', encoding='utf-8') as f:
288-
old_contents = f.read() # type: Optional[str]
290+
with open(path, 'rb') as f:
291+
old_contents = f.read() # type: Optional[bytes]
289292
except IOError:
290293
old_contents = None
291-
if old_contents != contents:
292-
with open(path, 'w', encoding='utf-8') as f:
293-
f.write(contents)
294+
if old_contents != encoded_contents:
295+
with open(path, 'wb') as f:
296+
f.write(encoded_contents)
294297

295298
# Fudge the mtime forward because otherwise when two builds happen close
296299
# together (like in a test) setuptools might not realize the source is newer
@@ -400,8 +403,12 @@ def mypycify(
400403
"""
401404

402405
setup_mypycify_vars()
403-
compiler_options = CompilerOptions(strip_asserts=strip_asserts,
404-
multi_file=multi_file, verbose=verbose)
406+
compiler_options = CompilerOptions(
407+
strip_asserts=strip_asserts,
408+
multi_file=multi_file,
409+
verbose=verbose,
410+
separate=separate is not False,
411+
)
405412

406413
# Create a compiler object so we can make decisions based on what
407414
# compiler is being used. typeshed is missing some attribues on the
@@ -413,13 +420,13 @@ def mypycify(
413420
for path in paths:
414421
expanded_paths.extend(glob.glob(path))
415422

416-
build_dir = 'build' # TODO: can this be overridden??
423+
build_dir = BUILD_DIR # TODO: can this be overridden??
417424
try:
418425
os.mkdir(build_dir)
419426
except FileExistsError:
420427
pass
421428

422-
sources, options = get_mypy_config(expanded_paths, mypy_options)
429+
sources, options = get_mypy_config(expanded_paths, mypy_options, compiler_options)
423430
# We generate a shared lib if there are multiple modules or if any
424431
# of the modules are in package. (Because I didn't want to fuss
425432
# around with making the single module code handle packages.)

mypyc/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
if MYPY:
33
from typing_extensions import Final
44

5+
BUILD_DIR = 'build'
6+
57
PREFIX = 'CPyPy_' # type: Final # Python wrappers
68
NATIVE_PREFIX = 'CPyDef_' # type: Final # Native functions etc.
79
DUNDER_PREFIX = 'CPyDunder_' # type: Final # Wrappers for exposing dunder methods to the API

0 commit comments

Comments
 (0)