""" Build script for Cython extensions. Usage: python scripts/build_cython.py build_ext --inplace Or via make: make build-cython """ import os import subprocess import sys from pathlib import Path def get_pcre2_config(): include_dirs = [] library_dirs = [] libraries = ["pcre2-8"] try: cflags = subprocess.check_output( ["pkg-config", "--cflags", "libpcre2-8"], stderr=subprocess.DEVNULL ).decode().strip() libs = subprocess.check_output( ["pkg-config", "--libs", "libpcre2-8"], stderr=subprocess.DEVNULL ).decode().strip() for flag in cflags.split(): if flag.startswith("-I"): include_dirs.append(flag[2:]) for flag in libs.split(): if flag.startswith("-L"): library_dirs.append(flag[2:]) elif flag.startswith("-l"): lib = flag[2:] if lib not in libraries: libraries.append(lib) return include_dirs, library_dirs, libraries except (subprocess.CalledProcessError, FileNotFoundError): pass try: cflags = subprocess.check_output( ["pcre2-config", "--cflags"], stderr=subprocess.DEVNULL ).decode().strip() libs = subprocess.check_output( ["pcre2-config", "--libs8"], stderr=subprocess.DEVNULL ).decode().strip() for flag in cflags.split(): if flag.startswith("-I"): include_dirs.append(flag[2:]) for flag in libs.split(): if flag.startswith("-L"): library_dirs.append(flag[2:]) elif flag.startswith("-l"): lib = flag[2:] if lib not in libraries: libraries.append(lib) return include_dirs, library_dirs, libraries except (subprocess.CalledProcessError, FileNotFoundError): pass # Fallback: try common paths common_paths = [ "/opt/homebrew", # macOS ARM "/usr/local", # macOS Intel / Linux "/usr", # Linux ] for base in common_paths: include_path = Path(base) / "include" lib_path = Path(base) / "lib" if (include_path / "pcre2.h").exists(): include_dirs.append(str(include_path)) library_dirs.append(str(lib_path)) break return include_dirs, library_dirs, libraries def build_extensions(): try: from Cython.Build import cythonize except ImportError: print("Cython not installed. Skipping Cython build.") print("Install with: pip install cython") return False try: from setuptools import Extension from setuptools.dist import Distribution from setuptools.command.build_ext import build_ext except ImportError: print("setuptools not installed. Skipping Cython build.") print("Install with: pip install setuptools") return False pcre2_include, pcre2_libdir, pcre2_libs = get_pcre2_config() if not pcre2_include: print("WARNING: PCRE2 not found. Routing module may not compile.") print("Install PCRE2: brew install pcre2 (macOS) or apt install libpcre2-dev (Linux)") else: print(f"Found PCRE2: includes={pcre2_include}, libs={pcre2_libdir}") extensions = [ Extension( "pyserve._path_matcher", sources=["pyserve/_path_matcher.pyx"], extra_compile_args=["-O3", "-ffast-math"], define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], ), Extension( "pyserve._routing", sources=["pyserve/_routing.pyx"], include_dirs=pcre2_include, library_dirs=pcre2_libdir, libraries=pcre2_libs, extra_compile_args=["-O3", "-ffast-math"], define_macros=[ ("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION"), ("PCRE2_CODE_UNIT_WIDTH", "8"), ], ), ] ext_modules = cythonize( extensions, compiler_directives={ "language_level": "3", "boundscheck": False, "wraparound": False, "cdivision": True, "embedsignature": True, }, annotate=True, ) dist = Distribution({"ext_modules": ext_modules}) dist.package_dir = {"": "."} cmd = build_ext(dist) cmd.ensure_finalized() cmd.inplace = True cmd.run() print("\nCython extensions built successfully!") ext_suffix = ".pyd" if sys.platform == "win32" else ".so" print(f" - pyserve/_path_matcher{ext_suffix}") print(f" - pyserve/_routing{ext_suffix}") return True if __name__ == "__main__": project_root = Path(__file__).parent.parent os.chdir(project_root) success = build_extensions() sys.exit(0 if success else 1)