Skip to content

feat(scan): add buffer protocol support for zero-copy scanning #371

feat(scan): add buffer protocol support for zero-copy scanning

feat(scan): add buffer protocol support for zero-copy scanning #371

Workflow file for this run

name: Build distributions
on:
push:
branches-ignore:
- "dependabot/**"
pull_request:
branches:
- main
workflow_call:
inputs:
force_build:
description: 'Force build regardless of file changes'
required: false
type: boolean
default: false
outputs:
valid_event:
description: 'Whether the build is allowed to run'
value: ${{ jobs.check_event.outputs.valid_event }}
should_build:
description: 'Whether the build meets the pre-conditions'
value: ${{ jobs.check_changes.outputs.should_build }}
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
env:
PYTHON_UNBUFFERED: "1"
jobs:
check_event:
name: Repo and event checks
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
valid_event: ${{ steps.check.outputs.valid_event }}
steps:
- name: Check if build is allowed
id: check
# This condition determines if a build should run.
# The build proceeds if ANY of the following are true:
#
# 1. The event is a push to the 'darvid/python-hyperscan'
# (main) repository.
# 2. The event is a pull request from a fork
# (i.e., not from 'darvid/python-hyperscan').
# 3. The event is a pull request that has been merged
# AND the head ref starts with the release branch prefix
# (e.g., 'create-pull-request/...').
# 4. The event is specifically on the 'darvid/python-hyperscan'
# repository AND the commit message contains '[build]'.
if: >
github.event_name == 'pull_request' ||
github.event_name == 'workflow_dispatch' ||
github.event_name == 'workflow_call' ||
(
github.event_name == 'push' &&
(
github.repository == 'darvid/python-hyperscan' ||
contains(github.event.head_commit.message, '[build]')
)
)
run: |
echo "valid_event=true" >> "$GITHUB_OUTPUT"
check_changes:
name: Build pre-conditions check
runs-on: ubuntu-latest
needs: check_event
if: needs.check_event.outputs.valid_event == 'true'
permissions:
contents: read
outputs:
should_build: ${{ steps.check.outputs.should_build }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Check if build is needed
id: check
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
if [[ "${{ inputs.force_build || false }}" == "true" ]]; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
echo "Running build because force_build is true"
exit 0
fi
# Check for [build] tag in commit messages or PR title
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# For PRs, check if PR title contains [build]
if [[ "$PR_TITLE" == *"[build]"* ]]; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
echo "Running build because PR title contains [build]"
exit 0
fi
# Set up PR refs - fetch BOTH base and head to ensure commits are available
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
git fetch origin "${{ github.event.pull_request.base.ref }}" --quiet || true
git fetch origin "pull/${{ github.event.pull_request.number }}/head:pr-head" --quiet || true
# Check all commits in the PR for [build]
COMMIT_MSGS=$(git log --format=%B "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null || echo "")
if echo "$COMMIT_MSGS" | grep -q "\[build\]"; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
echo "Running build because a commit in the PR contains [build]"
exit 0
fi
# Check which files changed in the PR
CHANGED_FILES=$(git diff --name-only "${BASE_SHA}" "${HEAD_SHA}" 2>/dev/null || echo "")
# Debug: show what we found
echo "Changed files in PR:"
echo "$CHANGED_FILES"
else
# For pushes, check if the head commit message contains [build]
if [[ "${{ contains(github.event.head_commit.message, '[build]') }}" == "true" ]]; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
echo "Running build because commit message contains [build]"
exit 0
fi
# For pushes, use the before/after SHAs or fallback to comparing with parent
BEFORE_SHA="${{ github.event.before }}"
AFTER_SHA="${{ github.event.after }}"
# Sometimes github.event.before is not available or is all zeros in the first push to a new branch
if [[ -z "$BEFORE_SHA" || "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then
CHANGED_FILES=$(git diff --name-only HEAD^ || echo "")
else
# Try to fetch the commits first to make sure they exist
git fetch --depth=1 origin "${BEFORE_SHA}" || true
git fetch --depth=1 origin "${AFTER_SHA}" || true
# Check if both SHAs exist in the repository
if git cat-file -e "${BEFORE_SHA}" 2>/dev/null && git cat-file -e "${AFTER_SHA}" 2>/dev/null; then
CHANGED_FILES=$(git diff --name-only "${BEFORE_SHA}" "${AFTER_SHA}" || echo "")
else
# Fallback to comparing with parent commit
echo "Cannot find one of the SHAs, falling back to HEAD^"
CHANGED_FILES=$(git diff --name-only HEAD^ || echo "")
fi
fi
fi
RESULT=0
echo "$CHANGED_FILES" | grep -q -E '^(src/hyperscan/|README.md|CMakeLists.txt|pyproject.toml|MANIFEST.in|cmake/)' || RESULT=$?
if [[ "$RESULT" -eq 0 ]]; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "Running build because PR touches relevant files"
else
echo "Running build because relevant files were changed"
fi
else
echo "should_build=false" >> "$GITHUB_OUTPUT"
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "Skipping build because PR does not change build-relevant files and lacks [build] tag"
else
echo "Skipping build because no relevant files were changed and commit doesn't have [build] tag"
fi
fi
sdist:
name: Source distribution
needs: [check_changes, check_event]
if: needs.check_event.outputs.valid_event == 'true' && needs.check_changes.outputs.should_build == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Remove project venv
run: python -c "import pathlib, shutil; p = pathlib.Path('.venv'); shutil.rmtree(p) if p.exists() else None"
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies
run: |
uv venv --python 3.12 .venv
uv pip install --python .venv --group build --quiet
- name: Build source distribution
run: uvx --from build pyproject-build --installer=uv --sdist --verbose
- uses: actions/upload-artifact@v4
with:
name: sdist
path: "dist/*.tar.gz"
if-no-files-found: error
wheels:
name: Binary wheel (${{ matrix.python_id }}-${{ matrix.platform_id }})
runs-on: ${{ matrix.os }}
needs: [check_changes, check_event]
if: needs.check_event.outputs.valid_event == 'true' && needs.check_changes.outputs.should_build == 'true'
permissions:
contents: read
actions: write
strategy:
fail-fast: false
matrix:
include:
# 🐧 manylinux x86_64
- os: ubuntu-24.04
host_python: "3.12"
python_id: cp310
platform_id: manylinux_x86_64
- os: ubuntu-24.04
host_python: "3.12"
python_id: cp311
platform_id: manylinux_x86_64
- os: ubuntu-24.04
host_python: "3.12"
python_id: cp312
platform_id: manylinux_x86_64
- os: ubuntu-24.04
host_python: "3.12"
python_id: cp313
platform_id: manylinux_x86_64
- os: ubuntu-24.04
host_python: "3.12"
python_id: cp314
platform_id: manylinux_x86_64
- os: ubuntu-24.04
host_python: "3.12"
python_id: cp314t
platform_id: manylinux_x86_64
# 🐧 manylinux aarch64
- os: ubuntu-24.04-arm
host_python: "3.12"
python_id: cp310
platform_id: manylinux_aarch64
- os: ubuntu-24.04-arm
host_python: "3.12"
python_id: cp311
platform_id: manylinux_aarch64
- os: ubuntu-24.04-arm
host_python: "3.12"
python_id: cp312
platform_id: manylinux_aarch64
- os: ubuntu-24.04-arm
host_python: "3.12"
python_id: cp313
platform_id: manylinux_aarch64
- os: ubuntu-24.04-arm
host_python: "3.12"
python_id: cp314
platform_id: manylinux_aarch64
- os: ubuntu-24.04-arm
host_python: "3.12"
python_id: cp314t
platform_id: manylinux_aarch64
# 🐧 manylinux2014 PyPy x86_64
- os: ubuntu-24.04
python: "3.11"
python_id: pp310
platform_id: manylinux_x86_64
# 🐧 manylinux2014 PyPy ARM
- os: ubuntu-24.04-arm
python: "3.11"
python_id: pp310
platform_id: manylinux_aarch64
# 🦀 musllinux x86_64
- os: ubuntu-24.04
python: "3.11"
python_id: cp310
platform_id: musllinux_x86_64
- os: ubuntu-24.04
python: "3.11"
python_id: cp311
platform_id: musllinux_x86_64
- os: ubuntu-24.04
python: "3.12"
python_id: cp312
platform_id: musllinux_x86_64
- os: ubuntu-24.04
python: "3.13"
python_id: cp313
platform_id: musllinux_x86_64
- os: ubuntu-24.04
python: "3.14"
python_id: cp314
platform_id: musllinux_x86_64
- os: ubuntu-24.04
python: "3.14"
python_id: cp314t
platform_id: musllinux_x86_64
# 🦀 musllinux ARM
- os: ubuntu-24.04-arm
python: "3.11"
python_id: cp310
platform_id: musllinux_aarch64
- os: ubuntu-24.04-arm
python: "3.11"
python_id: cp311
platform_id: musllinux_aarch64
- os: ubuntu-24.04-arm
python: "3.12"
python_id: cp312
platform_id: musllinux_aarch64
- os: ubuntu-24.04-arm
python: "3.13"
python_id: cp313
platform_id: musllinux_aarch64
- os: ubuntu-24.04-arm
python: "3.14"
python_id: cp314
platform_id: musllinux_aarch64
- os: ubuntu-24.04-arm
python: "3.14"
python_id: cp314t
platform_id: musllinux_aarch64
# 🍎 macOS x86_64
- os: macos-13
host_python: "3.12"
python_id: cp310
platform_id: macosx_x86_64
- os: macos-13
host_python: "3.12"
python_id: cp311
platform_id: macosx_x86_64
- os: macos-13
host_python: "3.12"
python_id: cp312
platform_id: macosx_x86_64
- os: macos-13
host_python: "3.12"
python_id: cp313
platform_id: macosx_x86_64
- os: macos-13
host_python: "3.12"
python_id: cp314
platform_id: macosx_x86_64
- os: macos-13
host_python: "3.12"
python_id: cp314t
platform_id: macosx_x86_64
# 🍎 macOS arm64 (Apple silicon)
- os: macos-15
host_python: "3.12"
python_id: cp310
platform_id: macosx_arm64
- os: macos-15
host_python: "3.12"
python_id: cp311
platform_id: macosx_arm64
- os: macos-15
host_python: "3.12"
python_id: cp312
platform_id: macosx_arm64
- os: macos-15
host_python: "3.12"
python_id: cp313
platform_id: macosx_arm64
- os: macos-15
host_python: "3.12"
python_id: cp314
platform_id: macosx_arm64
- os: macos-15
host_python: "3.12"
python_id: cp314t
platform_id: macosx_arm64
# 🪟 Windows x86_64
- os: windows-2025
host_python: "3.12"
python_id: cp310
platform_id: win_amd64
- os: windows-2025
host_python: "3.12"
python_id: cp311
platform_id: win_amd64
- os: windows-2025
host_python: "3.12"
python_id: cp312
platform_id: win_amd64
- os: windows-2025
host_python: "3.12"
python_id: cp313
platform_id: win_amd64
- os: windows-2025
host_python: "3.12"
python_id: cp314
platform_id: win_amd64
- os: windows-2025
host_python: "3.12"
python_id: cp314t
platform_id: win_amd64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Remove project venv
run: python -c "import pathlib, shutil; p = pathlib.Path('.venv'); shutil.rmtree(p) if p.exists() else None"
- name: Install host Python
shell: bash
run: |
PY_VERSION="${{ matrix.host_python }}"
if [ -z "$PY_VERSION" ]; then
PY_VERSION="${{ matrix.python }}"
fi
if [ -z "$PY_VERSION" ]; then
PY_VERSION="3.12"
fi
uv python install "$PY_VERSION"
- name: Install build dependencies
shell: bash
run: |
PY_VERSION="${{ matrix.host_python }}"
if [ -z "$PY_VERSION" ]; then
PY_VERSION="${{ matrix.python }}"
fi
if [ -z "$PY_VERSION" ]; then
PY_VERSION="3.12"
fi
uv venv --python "$PY_VERSION" .venv
uv pip install --python .venv --group build --quiet
- name: Build and test wheels
env:
CIBW_ARCHS_MACOS: ${{ matrix.platform_id == 'macosx_arm64' && 'arm64' || 'x86_64' }}
CIBW_ARCHS_LINUX: auto aarch64
CIBW_BUILD: ${{ matrix.python_id }}-${{ matrix.platform_id }}
CIBW_BUILD_VERBOSITY: "1"
shell: bash
run: |
PY_VERSION="${{ matrix.host_python }}"
if [ -z "$PY_VERSION" ]; then
PY_VERSION="${{ matrix.python }}"
fi
if [ -z "$PY_VERSION" ]; then
PY_VERSION="3.12"
fi
uv run --python "$PY_VERSION" --no-sync cibuildwheel --output-dir wheelhouse
- name: Save build artifacts
uses: actions/cache/save@v4
with:
key: ${{ runner.os }}-${{ matrix.python_id }}-${{ matrix.platform_id }}-${{ hashFiles('src/**', 'CMakeLists.txt') }}
path: |
wheelhouse/*.whl
- name: Upload wheels to artifacts
uses: actions/upload-artifact@v4
with:
name: wheel-${{ matrix.python_id }}-${{ matrix.platform_id }}
path: |
wheelhouse/*.whl