feat(scan): add buffer protocol support for zero-copy scanning (#251) #374
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |