headroom/.github/workflows/ci.yml
chopratejas 7fd5b2196c ci: use maturin build + pip install instead of maturin develop
`maturin develop` requires a virtualenv (it errors with "Couldn't find a
virtualenv or conda environment"). CI's setup-python provides a bare
system Python without a venv, so the dev script's build path doesn't
work there.

This switches CI to build a release wheel via `maturin build` and
install it with `pip install --force-reinstall --no-deps`. Then symlink
the installed `.so` into the in-tree `headroom/` package so the
editable install resolves `import headroom._core` past the source-dir
shadowing of site-packages.

`scripts/build_rust_extension.sh` is unchanged — it stays optimized for
local dev (where there IS a venv).
2026-04-26 10:04:06 -07:00

283 lines
8.6 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
# `headroom.transforms.diff_compressor` is now a thin pyo3 shim that
# delegates to `headroom._core` (built from `crates/headroom-py`). Without
# the wheel installed, every `DiffCompressor()` call raises
# `ModuleNotFoundError: No module named 'headroom._core'` and ~25 tests
# in `tests/test_transforms/test_diff_compressor.py` fail. The script
# below runs `maturin develop` and symlinks the built `.so` into the
# in-tree `headroom/` package so the editable install resolves it.
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry + build
uses: Swatinem/rust-cache@v2
with:
workspaces: ". -> target"
- name: Install maturin
run: |
pip install 'maturin>=1.5,<2.0'
# `scripts/build_rust_extension.sh` uses `maturin develop` which requires
# a virtualenv. CI runs against bare setup-python with no venv, so build
# a wheel + pip-install it instead. Then symlink the `.so` into the
# in-tree `headroom/` package so the editable install resolves
# `import headroom._core` past the source dir shadowing site-packages.
- name: Build Rust extension (headroom._core)
run: |
maturin build --release -m crates/headroom-py/Cargo.toml --out dist
pip install --force-reinstall --no-deps dist/headroom_core_py-*.whl
SO_FILE=$(python -c "import headroom._core, pathlib; print(pathlib.Path(headroom._core.__file__).resolve())")
ln -sf "$SO_FILE" "headroom/$(basename "$SO_FILE")"
python -c "from headroom._core import DiffCompressor; print('headroom._core OK:', DiffCompressor)"
- name: Run linting
if: matrix.python-version == '3.12'
run: |
ruff check .
ruff format --check .
- name: Run type checking
if: matrix.python-version == '3.12'
run: |
mypy headroom --ignore-missing-imports
- name: Run tests
run: |
pytest -v --tb=short tests scripts/tests
- name: Run tests with coverage
if: matrix.python-version == '3.11'
run: |
pytest tests scripts/tests --cov=headroom --cov-report=xml --cov-report=term-missing
- name: Upload coverage to Codecov
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
fail_ci_if_error: false
test-extras:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install with relevance extras
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,relevance]"
- name: Run relevance tests
run: |
pytest tests/test_relevance.py -v
test-agno:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install with agno extras
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,agno]"
- name: Run agno tests
run: |
pytest tests/test_integrations/agno/ -v
docker-native-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Build local Headroom image
run: |
docker build -t headroom-native-e2e:latest .
- name: Run Docker-native installer e2e
env:
HEADROOM_DOCKER_IMAGE: headroom-native-e2e:latest
run: |
bash e2e/docker-native-install.sh
- name: Run Docker-native compose smoke test
env:
HEADROOM_IMAGE: headroom-native-e2e:latest
HEADROOM_HOST_HOME: ${{ github.workspace }}
HEADROOM_WORKSPACE: ${{ github.workspace }}
run: |
mkdir -p .headroom .claude .codex .gemini
trap 'docker compose -f docker/docker-compose.native.yml down -v' EXIT
docker compose -f docker/docker-compose.native.yml up -d proxy
for attempt in $(seq 1 30); do
if curl --fail --silent http://127.0.0.1:8787/readyz >/dev/null; then
break
fi
if [ "$attempt" -eq 30 ]; then
docker compose -f docker/docker-compose.native.yml logs proxy
exit 1
fi
sleep 1
done
- name: Run Docker-native wrap e2e
run: |
docker build -f e2e/wrap/Dockerfile -t headroom-wrap-e2e .
docker run --rm headroom-wrap-e2e
- name: Run Docker-native init e2e
run: |
docker build -f e2e/init/Dockerfile -t headroom-init-e2e .
docker run --rm headroom-init-e2e
windows-native-wrapper:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install test dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
- name: Run native installer wrapper tests
run: |
pytest tests/test_install/test_native_installers.py -q
macos-native-wrapper:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install bash and test dependencies
run: |
brew install bash
python -m pip install --upgrade pip
python -m pip install --retries 10 --timeout 60 pytest
- name: Run native installer wrapper tests
run: |
BASH_PREFIX="$(brew --prefix bash)"
export PATH="$BASH_PREFIX/bin:$PATH"
pytest tests/test_install/test_native_installers.py -q
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install build tools
run: |
python -m pip install --upgrade pip build twine
- name: Build package
run: |
python -m build
- name: Check package
run: |
twine check dist/*
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
commitlint:
if: github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'Merge pull request ')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v5
with:
configFile: .commitlintrc.json
workflow-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install actionlint
run: |
curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash | bash
sudo mv ./actionlint /usr/local/bin/actionlint
- name: Install act
run: |
curl -fsSL https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
sudo install ./bin/act /usr/local/bin/act
- name: Validate workflow files
run: |
bash scripts/validate-workflows.sh