name: Reusable Build on: workflow_call: inputs: ref: description: 'Git ref to build' required: false type: string default: '' jobs: lint: name: Lint and Format Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install uv uses: astral-sh/setup-uv@v4 - name: Install ruff run: | uv tool install ruff - name: Run ruff check run: | ruff check . - name: Run ruff format check run: | ruff format --check . build: needs: lint name: Build ${{ matrix.os }} Python ${{ matrix.python }} strategy: matrix: include: - os: ubuntu-22.04 python: '3.9' - os: ubuntu-22.04 python: '3.10' - os: ubuntu-22.04 python: '3.11' - os: ubuntu-22.04 python: '3.12' - os: ubuntu-22.04 python: '3.13' - os: macos-14 python: '3.9' - os: macos-14 python: '3.10' - os: macos-14 python: '3.11' - os: macos-14 python: '3.12' - os: macos-14 python: '3.13' - os: macos-13 python: '3.9' - os: macos-13 python: '3.10' - os: macos-13 python: '3.11' - os: macos-13 python: '3.12' # Note: macos-13 + Python 3.13 excluded due to PyTorch compatibility # (PyTorch 2.5+ supports Python 3.13 but not Intel Mac x86_64) runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} submodules: recursive - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install uv uses: astral-sh/setup-uv@v4 - name: Install system dependencies (Ubuntu) if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libomp-dev libboost-all-dev protobuf-compiler libzmq3-dev \ pkg-config libopenblas-dev patchelf libabsl-dev libaio-dev libprotobuf-dev # Install Intel MKL for DiskANN wget -q https://registrationcenter-download.intel.com/akdlm/IRC_NAS/79153e0f-74d7-45af-b8c2-258941adf58a/intel-onemkl-2025.0.0.940.sh sudo sh intel-onemkl-2025.0.0.940.sh -a --components intel.oneapi.lin.mkl.devel --action install --eula accept -s source /opt/intel/oneapi/setvars.sh echo "MKLROOT=/opt/intel/oneapi/mkl/latest" >> $GITHUB_ENV echo "LD_LIBRARY_PATH=/opt/intel/oneapi/mkl/latest/lib/intel64:$LD_LIBRARY_PATH" >> $GITHUB_ENV - name: Install system dependencies (macOS) if: runner.os == 'macOS' run: | # Don't install LLVM, use system clang for better compatibility brew install libomp boost protobuf zeromq - name: Install build dependencies run: | uv pip install --system scikit-build-core numpy swig Cython pybind11 if [[ "$RUNNER_OS" == "Linux" ]]; then uv pip install --system auditwheel else uv pip install --system delocate fi - name: Set macOS environment variables if: runner.os == 'macOS' run: | # Use brew --prefix to automatically detect Homebrew installation path HOMEBREW_PREFIX=$(brew --prefix) echo "HOMEBREW_PREFIX=${HOMEBREW_PREFIX}" >> $GITHUB_ENV echo "OpenMP_ROOT=${HOMEBREW_PREFIX}/opt/libomp" >> $GITHUB_ENV # Set CMAKE_PREFIX_PATH to let CMake find all packages automatically echo "CMAKE_PREFIX_PATH=${HOMEBREW_PREFIX}" >> $GITHUB_ENV # Set compiler flags for OpenMP (required for both backends) echo "LDFLAGS=-L${HOMEBREW_PREFIX}/opt/libomp/lib" >> $GITHUB_ENV echo "CPPFLAGS=-I${HOMEBREW_PREFIX}/opt/libomp/include" >> $GITHUB_ENV - name: Build packages run: | # Build core (platform independent) cd packages/leann-core uv build cd ../.. # Build HNSW backend cd packages/leann-backend-hnsw if [[ "${{ matrix.os }}" == macos-* ]]; then # Use system clang for better compatibility export CC=clang export CXX=clang++ export MACOSX_DEPLOYMENT_TARGET=11.0 uv build --wheel --python ${{ matrix.python }} --find-links ${GITHUB_WORKSPACE}/packages/leann-core/dist else uv build --wheel --python ${{ matrix.python }} --find-links ${GITHUB_WORKSPACE}/packages/leann-core/dist fi cd ../.. # Build DiskANN backend cd packages/leann-backend-diskann if [[ "${{ matrix.os }}" == macos-* ]]; then # Use system clang for better compatibility export CC=clang export CXX=clang++ # DiskANN requires macOS 13.3+ for sgesdd_ LAPACK function export MACOSX_DEPLOYMENT_TARGET=13.3 uv build --wheel --python ${{ matrix.python }} --find-links ${GITHUB_WORKSPACE}/packages/leann-core/dist else uv build --wheel --python ${{ matrix.python }} --find-links ${GITHUB_WORKSPACE}/packages/leann-core/dist fi cd ../.. # Build meta package (platform independent) cd packages/leann uv build cd ../.. - name: Repair wheels (Linux) if: runner.os == 'Linux' run: | # Repair HNSW wheel cd packages/leann-backend-hnsw if [ -d dist ]; then auditwheel repair dist/*.whl -w dist_repaired rm -rf dist mv dist_repaired dist fi cd ../.. # Repair DiskANN wheel cd packages/leann-backend-diskann if [ -d dist ]; then auditwheel repair dist/*.whl -w dist_repaired rm -rf dist mv dist_repaired dist fi cd ../.. - name: Repair wheels (macOS) if: runner.os == 'macOS' run: | # Repair HNSW wheel cd packages/leann-backend-hnsw if [ -d dist ]; then delocate-wheel -w dist_repaired -v dist/*.whl rm -rf dist mv dist_repaired dist fi cd ../.. # Repair DiskANN wheel cd packages/leann-backend-diskann if [ -d dist ]; then delocate-wheel -w dist_repaired -v dist/*.whl rm -rf dist mv dist_repaired dist fi cd ../.. - name: List built packages run: | echo "๐Ÿ“ฆ Built packages:" find packages/*/dist -name "*.whl" -o -name "*.tar.gz" | sort - name: Install built packages for testing run: | # Create a virtual environment with the correct Python version uv venv --python ${{ matrix.python }} source .venv/bin/activate || source .venv/Scripts/activate # Install packages using --find-links to prioritize local builds uv pip install --find-links packages/leann-core/dist --find-links packages/leann-backend-hnsw/dist --find-links packages/leann-backend-diskann/dist packages/leann-core/dist/*.whl || uv pip install --find-links packages/leann-core/dist packages/leann-core/dist/*.tar.gz uv pip install --find-links packages/leann-core/dist packages/leann-backend-hnsw/dist/*.whl uv pip install --find-links packages/leann-core/dist packages/leann-backend-diskann/dist/*.whl uv pip install packages/leann/dist/*.whl || uv pip install packages/leann/dist/*.tar.gz # Install test dependencies using extras uv pip install -e ".[test]" - name: Run tests with pytest env: CI: true # Mark as CI environment to skip memory-intensive tests OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} HF_HUB_DISABLE_SYMLINKS: 1 TOKENIZERS_PARALLELISM: false PYTORCH_ENABLE_MPS_FALLBACK: 0 # Disable MPS on macOS CI to avoid memory issues OMP_NUM_THREADS: 1 # Disable OpenMP parallelism to avoid libomp crashes MKL_NUM_THREADS: 1 # Single thread for MKL operations run: | # Activate virtual environment source .venv/bin/activate || source .venv/Scripts/activate # Add targeted debugging for pytest hangs (especially Ubuntu 22.04) if [[ "${{ matrix.os }}" == "ubuntu-22.04" ]]; then echo "๐Ÿ” [HANG DEBUG] Ubuntu 22.04 detected - enabling enhanced process monitoring" # Pre-test state echo "๐Ÿ“Š [HANG DEBUG] Pre-test process state:" ps aux | grep -E "(python|embedding|zmq)" | grep -v grep || echo "No relevant processes" echo "๐Ÿ”Œ [HANG DEBUG] Pre-test network state:" ss -tulpn | grep -E "(555[0-9]|556[0-9])" || echo "No embedding server ports" # Function to monitor processes during test monitor_processes() { while true; do sleep 30 echo "โฐ [HANG DEBUG] $(date): Process check during test execution" ps aux | grep -E "(python|pytest|embedding)" | grep -v grep | head -10 ss -tulpn | grep -E "(555[0-9]|556[0-9])" || echo "No ports" done } # Start background monitoring monitor_processes & MONITOR_PID=$! echo "๐Ÿ” [HANG DEBUG] Started background monitor (PID: $MONITOR_PID)" # Run pytest with timeout and enhanced logging echo "๐Ÿš€ [HANG DEBUG] Starting pytest with 600s timeout..." # Pre-test import debugging to identify hang point echo "๐Ÿ” [HANG DEBUG] Testing individual imports to identify hang source..." timeout 120 python -c " import sys import time def timed_import(module, desc): start = time.time() print(f'โณ Importing {desc}...', flush=True) try: __import__(module) elapsed = time.time() - start print(f'โœ… {desc} imported in {elapsed:.2f}s', flush=True) except Exception as e: elapsed = time.time() - start print(f'โŒ {desc} failed after {elapsed:.2f}s: {e}', flush=True) raise print('๐Ÿงช Testing individual module imports...', flush=True) timed_import('leann', 'Core LEANN package') timed_import('leann.api', 'LEANN API module') print('๐Ÿงช Testing backend imports (most likely hang point)...', flush=True) timed_import('leann_backend_hnsw', 'HNSW backend package') timed_import('leann_backend_diskann', 'DiskANN backend package') print('๐Ÿงช Testing deep imports...', flush=True) timed_import('leann_backend_hnsw.hnsw_backend', 'HNSW backend module') timed_import('leann_backend_diskann.diskann_backend', 'DiskANN backend module') print('โœ… All imports completed successfully', flush=True) " 2>&1 | while IFS= read -r line; do echo "$(date +"%H:%M:%S") [IMPORT] $line" done IMPORT_EXIT=$? if [ $IMPORT_EXIT -eq 124 ]; then echo "โš ๏ธ [HANG DEBUG] IMPORT TIMEOUT! Import process hung - this is likely the root cause" echo "๐Ÿ’€ [HANG DEBUG] Killing any hanging Python processes..." pkill -KILL -f python || true exit 1 elif [ $IMPORT_EXIT -ne 0 ]; then echo "โŒ [HANG DEBUG] Import tests failed with exit code: $IMPORT_EXIT" exit $IMPORT_EXIT fi echo "โœ… [HANG DEBUG] Import tests passed, proceeding with pytest..." timeout --preserve-status --signal=TERM --kill-after=30 600 bash -c ' echo "โ–ถ๏ธ [HANG DEBUG] Pytest starting at: $(date)" pytest tests/ -v --tb=short --maxfail=5 -x 2>&1 | while IFS= read -r line; do echo "$(date +"%H:%M:%S") [PYTEST] $line" done echo "โœ… [HANG DEBUG] Pytest completed at: $(date)" ' PYTEST_EXIT=$? # Stop background monitoring kill $MONITOR_PID 2>/dev/null || true echo "๐Ÿ”š [HANG DEBUG] Pytest exit code: $PYTEST_EXIT" if [ $PYTEST_EXIT -eq 124 ]; then echo "โš ๏ธ [HANG DEBUG] TIMEOUT! Pytest hung for >600s" echo "๐Ÿ” [HANG DEBUG] Final process state:" ps aux | grep -E "(python|pytest|embedding)" | grep -v grep echo "๐Ÿ” [HANG DEBUG] Final network state:" ss -tulpn | grep -E "(555[0-9]|556[0-9])" || echo "No ports" echo "๐Ÿ’€ [HANG DEBUG] Killing remaining processes..." pkill -TERM -f "pytest\|embedding_server\|zmq" || true sleep 3 pkill -KILL -f "pytest\|embedding_server\|zmq" || true fi exit $PYTEST_EXIT else # For non-Ubuntu or non-22.04, run normally echo "๐Ÿš€ [HANG DEBUG] Running tests on ${{ matrix.os }} (normal mode)" pytest tests/ -v --tb=short fi - name: Run sanity checks (optional) run: | # Activate virtual environment source .venv/bin/activate || source .venv/Scripts/activate # Run distance function tests if available if [ -f test/sanity_checks/test_distance_functions.py ]; then echo "Running distance function sanity checks..." python test/sanity_checks/test_distance_functions.py || echo "โš ๏ธ Distance function test failed, continuing..." fi - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: packages-${{ matrix.os }}-py${{ matrix.python }} path: packages/*/dist/