indie-status-page/venv/lib/python3.11/site-packages/mypyc/test/librt_cache.py
IndieStatusBot 902133edd3 feat: indie status page MVP -- FastAPI + SQLite
- 8 DB models (services, incidents, monitors, subscribers, etc.)
- Full CRUD API for services, incidents, monitors
- Public status page with live data
- Incident detail page with timeline
- API key authentication
- Uptime monitoring scheduler
- 13 tests passing
- TECHNICAL_DESIGN.md with full spec
2026-04-25 05:00:00 +00:00

236 lines
8.4 KiB
Python

"""Build and cache librt for use in tests.
This module provides a way to build librt extension modules once and cache
them across test runs, and across different test cases in a single run. The
cache is invalidated when source files or details of the build environment change.
Note: Tests must run in a subprocess to use the cached librt, since importing
this module also triggers the import of the regular installed librt.
Usage:
from mypyc.test.librt_cache import get_librt_path, run_with_librt
# Get path to built librt (builds if needed)
path = get_librt_path()
# Run a test file in subprocess with built librt
result = run_with_librt("test_librt.py")
"""
from __future__ import annotations
import hashlib
import os
import shutil
import subprocess
import sys
import sysconfig
from typing import Any
import filelock
from mypyc.build import LIBRT_MODULES, get_cflags, include_dir
from mypyc.common import RUNTIME_C_FILES
from mypyc.test.config import PREFIX
def _librt_build_hash(experimental: bool, opt_level: str) -> str:
"""Compute hash for librt build, including sources and build environment."""
# Import lazily to ensure mypyc.build has ensured that distutils is correctly set up
from distutils import ccompiler
h = hashlib.sha256()
# Include experimental flag
h.update(b"exp" if experimental else b"noexp")
h.update(f"opt={opt_level}".encode())
# Include full Python version string (includes git hash for dev builds)
h.update(sys.version.encode())
# Include debug build status (gettotalrefcount only exists in debug builds)
is_debug = hasattr(sys, "gettotalrefcount")
h.update(b"debug" if is_debug else b"release")
# Include free-threading status (Python 3.13+)
is_free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
h.update(b"freethreaded" if is_free_threaded else b"gil")
# Include compiler type (e.g., "unix" or "msvc")
compiler: Any = ccompiler.new_compiler()
h.update(compiler.compiler_type.encode())
# Include environment variables that affect C compilation
for var in ("CC", "CXX", "CFLAGS", "CPPFLAGS", "LDFLAGS"):
val = os.environ.get(var, "")
h.update(f"{var}={val}".encode())
# Hash runtime files
for name in RUNTIME_C_FILES:
path = os.path.join(include_dir(), name)
h.update(name.encode() + b"|")
with open(path, "rb") as f:
h.update(f.read())
# Hash librt module files
for mod, files, extra, includes in LIBRT_MODULES:
for fname in files + extra:
path = os.path.join(include_dir(), fname)
h.update(fname.encode() + b"|")
with open(path, "rb") as f:
h.update(f.read())
return h.hexdigest()[:16]
def _generate_setup_py(build_dir: str, experimental: bool, opt_level: str) -> str:
"""Generate setup.py content for building librt directly.
We inline LIBRT_MODULES/RUNTIME_C_FILES/include_dir/cflags values to avoid
importing mypyc.build, which recursively imports lots of things.
"""
lib_rt_dir = include_dir()
# Get compiler flags using the shared helper
cflags = get_cflags(opt_level=opt_level, experimental_features=experimental)
# Serialize values to inline in generated setup.py
librt_modules_repr = repr(
[(m.module, m.c_files, m.other_files, m.include_dirs) for m in LIBRT_MODULES]
)
runtime_files_repr = repr(RUNTIME_C_FILES)
cflags_repr = repr(cflags)
return f"""\
import os
from setuptools import setup, Extension
import build_setup # noqa: F401 # Monkey-patches compiler for per-file SIMD flags
build_dir = {build_dir!r}
lib_rt_dir = {lib_rt_dir!r}
RUNTIME_C_FILES = {runtime_files_repr}
LIBRT_MODULES = {librt_modules_repr}
CFLAGS = {cflags_repr}
def write_file(path, contents):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "wb") as f:
f.write(contents)
# Copy runtime C files
for name in RUNTIME_C_FILES:
src = os.path.join(lib_rt_dir, name)
dst = os.path.join(build_dir, name)
with open(src, "rb") as f:
write_file(dst, f.read())
# Build extensions for each librt module
extensions = []
for mod, file_names, extra_files, includes in LIBRT_MODULES:
# Copy source files
for fname in file_names + extra_files:
src = os.path.join(lib_rt_dir, fname)
dst = os.path.join(build_dir, fname)
with open(src, "rb") as f:
write_file(dst, f.read())
extensions.append(Extension(
mod,
sources=[os.path.join(build_dir, f) for f in file_names + RUNTIME_C_FILES],
include_dirs=[lib_rt_dir] + [os.path.join(lib_rt_dir, d) for d in includes],
extra_compile_args=CFLAGS,
))
setup(name='librt_cached', ext_modules=extensions)
"""
def get_librt_path(experimental: bool = True, opt_level: str = "0") -> str:
"""Get path to librt built from the repository, building and caching if necessary.
Uses build/librt-cache/ under the repo root (gitignored). The cache is
keyed by a hash of sources and build environment, so it auto-invalidates
when relevant factors change.
Safe to call from multiple parallel pytest workers - uses file locking.
Args:
experimental: Whether to enable experimental features.
opt_level: Optimization level ("0".."3") used when building librt.
Returns:
Path to directory containing built librt modules.
"""
# Use build/librt-cache/ under the repo root (gitignored)
cache_root = os.path.join(PREFIX, "build", "librt-cache")
build_hash = _librt_build_hash(experimental, opt_level)
build_dir = os.path.join(cache_root, f"librt-{build_hash}")
lock_file = os.path.join(cache_root, f"librt-{build_hash}.lock")
marker = os.path.join(build_dir, ".complete")
os.makedirs(cache_root, exist_ok=True)
with filelock.FileLock(lock_file, timeout=300): # 5 min timeout
if os.path.exists(marker):
return build_dir
# Clean up any partial build
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
os.makedirs(build_dir)
# Create librt package directory for --inplace to copy .so files into
librt_pkg = os.path.join(build_dir, "librt")
os.makedirs(librt_pkg)
with open(os.path.join(librt_pkg, "__init__.py"), "w") as f:
pass
# Copy build_setup.py for per-file SIMD compiler flags
build_setup_src = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "build_setup.py"
)
build_setup_dst = os.path.join(build_dir, "build_setup.py")
shutil.copy(build_setup_src, build_setup_dst)
# Write setup.py
setup_py = os.path.join(build_dir, "setup.py")
with open(setup_py, "w") as f:
f.write(_generate_setup_py(build_dir, experimental, opt_level))
# Build (parallel builds don't work well because multiple extensions
# share the same runtime C files, causing race conditions)
result = subprocess.run(
[sys.executable, setup_py, "build_ext", "--inplace"],
cwd=build_dir,
capture_output=True,
text=True,
)
if result.returncode != 0:
raise RuntimeError(f"librt build failed:\n{result.stdout}\n{result.stderr}")
# Mark complete
with open(marker, "w") as f:
f.write("ok")
return build_dir
def run_with_librt(
file_path: str, experimental: bool = True, check: bool = True, opt_level: str = "0"
) -> subprocess.CompletedProcess[str]:
"""Run a Python file in a subprocess with built librt available.
This runs the file in a fresh Python process where the built librt
is at the front of sys.path, avoiding conflicts with any system librt.
Args:
file_path: Path to Python file to execute.
experimental: Whether to use experimental features.
check: If True, raise CalledProcessError on non-zero exit.
opt_level: Optimization level ("0".."3") used when building librt.
Returns:
CompletedProcess with stdout, stderr, and returncode.
"""
librt_path = get_librt_path(experimental, opt_level=opt_level)
# Prepend librt path to PYTHONPATH
env = os.environ.copy()
existing = env.get("PYTHONPATH", "")
env["PYTHONPATH"] = librt_path + (os.pathsep + existing if existing else "")
return subprocess.run(
[sys.executable, file_path], capture_output=True, text=True, check=check, env=env
)