MCPcopy
hub / github.com/python/mypy / _patch_setuptools_copy_extensions_to_source

Function _patch_setuptools_copy_extensions_to_source

mypyc/build.py:457–514  ·  view source on GitHub ↗

Skip redundant `.so` copies for extensions we generated. setuptools' copy_extensions_to_source rewrites every `.so` in the source tree on every build_ext, even when nothing changed. On macOS this invalidates AMFI's signature cache (~100 ms re-verification per `.so` on the next impor

()

Source from the content-addressed store, hash-verified

455
456
457def _patch_setuptools_copy_extensions_to_source() -> None:
458 """Skip redundant `.so` copies for extensions we generated.
459
460 setuptools' copy_extensions_to_source rewrites every `.so` in the
461 source tree on every build_ext, even when nothing changed. On macOS
462 this invalidates AMFI's signature cache (~100 ms re-verification per
463 `.so` on the next import), eating most of the separate=True
464 incremental speedup.
465
466 The patch is global because copy_extensions_to_source runs during
467 setup()'s build_ext command, after mypycify() has already returned;
468 we can't scope a context manager around it. Instead the skip only
469 fires for extensions tagged by mypycify (via the marker attribute),
470 so other setuptools users in the same setup.py see the unmodified
471 upstream behavior, including stub writes.
472 """
473 global _setuptools_patch_applied
474 if _setuptools_patch_applied:
475 return
476 _setuptools_patch_applied = True
477
478 from setuptools.command.build_ext import build_ext as _build_ext
479
480 original = _build_ext.copy_extensions_to_source
481
482 def _files_match(a: str, b: str) -> bool:
483 try:
484 sa = os.stat(a)
485 sb = os.stat(b)
486 except OSError:
487 return False
488 # Compare size + whole-second mtime. distutils' copy_file
489 # propagates the source mtime, but macOS drops sub-second
490 # precision on write so the float values never match verbatim.
491 return sa.st_size == sb.st_size and int(sa.st_mtime) == int(sb.st_mtime)
492
493 def patched(self: Any) -> None:
494 build_py = self.get_finalized_command("build_py")
495
496 def is_redundant(ext: Any) -> bool:
497 if not getattr(ext, _MYPYC_EXTENSION_MARKER, False):
498 return False
499 inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext)
500 return _files_match(regular_file, inplace_file)
501
502 # Hide our already-fresh extensions from setuptools' loop and
503 # let it handle whatever's left. Delegating instead of
504 # reimplementing the body means future setuptools changes carry
505 # over for free. self.extensions is restored before we return
506 # so anything that inspects it later sees the original list.
507 saved = self.extensions
508 self.extensions = [ext for ext in saved if not is_redundant(ext)]
509 try:
510 original(self)
511 finally:
512 self.extensions = saved
513
514 _build_ext.copy_extensions_to_source = patched # type: ignore[method-assign]

Callers 1

mypycifyFunction · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected

Used in the wild real call sites across dependent graphs

searching dependent graphs…