1- """Build and cache librt for use in non-compiled tests.
1+ """Build and cache librt for use in tests.
22
33This module provides a way to build librt extension modules once and cache
4- them across test runs. The cache is invalidated when source files change.
4+ them across test runs, and across different test cases in a single run. The
5+ cache is invalidated when source files or details of the build environment change.
56
6- Note: Tests must run in a subprocess to use the built librt, since importing
7- this module triggers the system librt import via mypyc.build -> mypy.build .
7+ Note: Tests must run in a subprocess to use the cached librt, since importing
8+ this module also triggers the import of the regular installed librt .
89
910Usage:
1011 from mypyc.test.librt_cache import get_librt_path, run_with_librt
2425import subprocess
2526import sys
2627import sysconfig
28+ from typing import Any
2729
2830import filelock
2931
30- from mypyc .build import LIBRT_MODULES , RUNTIME_C_FILES , get_cflags , include_dir
32+ from mypyc .build import LIBRT_MODULES , get_cflags , include_dir
33+ from mypyc .common import RUNTIME_C_FILES
3134
3235
3336def _librt_build_hash (experimental : bool ) -> str :
@@ -46,8 +49,8 @@ def _librt_build_hash(experimental: bool) -> str:
4649 # Include free-threading status (Python 3.13+)
4750 is_free_threaded = bool (sysconfig .get_config_var ("Py_GIL_DISABLED" ))
4851 h .update (b"freethreaded" if is_free_threaded else b"gil" )
49- # Include compiler type (e.g., "unix", "msvc")
50- compiler = ccompiler .new_compiler ()
52+ # Include compiler type (e.g., "unix" or "msvc")
53+ compiler : Any = ccompiler .new_compiler ()
5154 h .update (compiler .compiler_type .encode ())
5255 # Include environment variables that affect C compilation
5356 for var in ("CC" , "CXX" , "CFLAGS" , "CPPFLAGS" , "LDFLAGS" ):
@@ -56,25 +59,24 @@ def _librt_build_hash(experimental: bool) -> str:
5659 # Hash runtime files
5760 for name in RUNTIME_C_FILES :
5861 path = os .path .join (include_dir (), name )
59- if os . path . exists ( path ):
60- with open (path , "rb" ) as f :
61- h .update (f .read ())
62+ h . update ( name . encode () + b"|" )
63+ with open (path , "rb" ) as f :
64+ h .update (f .read ())
6265 # Hash librt module files
6366 for mod , files , extra , includes in LIBRT_MODULES :
6467 for fname in files + extra :
6568 path = os .path .join (include_dir (), fname )
66- if os . path . exists ( path ):
67- with open (path , "rb" ) as f :
68- h .update (f .read ())
69+ h . update ( fname . encode () + b"|" )
70+ with open (path , "rb" ) as f :
71+ h .update (f .read ())
6972 return h .hexdigest ()[:16 ]
7073
7174
7275def _generate_setup_py (build_dir : str , experimental : bool ) -> str :
7376 """Generate setup.py content for building librt directly.
7477
7578 We inline LIBRT_MODULES/RUNTIME_C_FILES/include_dir/cflags values to avoid
76- importing mypyc.build, which imports mypy.build, which imports librt
77- (circular dependency when librt isn't built yet).
79+ importing mypyc.build, which recursively imports lots of things.
7880 """
7981 lib_rt_dir = include_dir ()
8082
@@ -101,22 +103,15 @@ def _generate_setup_py(build_dir: str, experimental: bool) -> str:
101103CFLAGS = { cflags_repr }
102104
103105def write_file(path, contents):
104- encoded = contents.encode("utf-8")
105106 os.makedirs(os.path.dirname(path), exist_ok=True)
106- try:
107- with open(path, "rb") as f:
108- if f.read() == encoded:
109- return
110- except OSError:
111- pass
112107 with open(path, "wb") as f:
113- f.write(encoded )
108+ f.write(contents )
114109
115110# Copy runtime C files
116111for name in RUNTIME_C_FILES:
117112 src = os.path.join(lib_rt_dir, name)
118113 dst = os.path.join(build_dir, name)
119- with open(src, encoding="utf-8 ") as f:
114+ with open(src, "rb ") as f:
120115 write_file(dst, f.read())
121116
122117# Build extensions for each librt module
@@ -126,7 +121,7 @@ def write_file(path, contents):
126121 for fname in file_names + extra_files:
127122 src = os.path.join(lib_rt_dir, fname)
128123 dst = os.path.join(build_dir, fname)
129- with open(src, encoding="utf-8 ") as f:
124+ with open(src, "rb ") as f:
130125 write_file(dst, f.read())
131126
132127 extensions.append(Extension(
@@ -141,7 +136,7 @@ def write_file(path, contents):
141136
142137
143138def get_librt_path (experimental : bool = True ) -> str :
144- """Get path to built librt , building and caching if necessary.
139+ """Get path to librt built from the repository , building and caching if necessary.
145140
146141 Uses build/librt-cache/ under the repo root (gitignored). The cache is
147142 keyed by a hash of sources and build environment, so it auto-invalidates
@@ -234,9 +229,5 @@ def run_with_librt(
234229 env ["PYTHONPATH" ] = librt_path + (os .pathsep + existing if existing else "" )
235230
236231 return subprocess .run (
237- [sys .executable , file_path ],
238- capture_output = True ,
239- text = True ,
240- check = check ,
241- env = env ,
232+ [sys .executable , file_path ], capture_output = True , text = True , check = check , env = env
242233 )
0 commit comments