Skip to content

feat(bindings): Expose extractor interface via CustomExtractor for Python bindings #178

feat(bindings): Expose extractor interface via CustomExtractor for Python bindings

feat(bindings): Expose extractor interface via CustomExtractor for Python bindings #178

Workflow file for this run

name: Rust CI/CD
on:
push:
branches: ["main"]
tags:
- "v*"
pull_request:
branches: ["main"]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
benchmark:
name: Run Benchmarks
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
# On pull requests, check out the head ref so we can commit to it
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y software-properties-common
sudo add-apt-repository universe
sudo apt-get update
sudo apt-get install -y build-essential simstring-bin
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt, clippy
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
- name: Set up Julia
uses: julia-actions/setup-julia@v2
with:
version: "1.11"
- name: Cache python dependencies
uses: actions/cache@v4
with:
path: |
.venv
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Run and Compare Benchmarks
run: |
# Install python dependencies
pip install uv
uv venv
source .venv/bin/activate
uv pip install simstring-fast pandas maturin tabulate psutil
rm -rf target/wheels
maturin build --release
uv pip install target/wheels/*.whl
# Install ruby dependencies
gem install simstring_pure descriptive_statistics json
# Install julia dependencies
julia -e 'using Pkg; Pkg.add(["SimString", "BenchmarkTools", "Statistics", "JSON"])'
# Run benchmarks
echo "Running and Comparing Benchmarks..."
python benches/run_benches.py
- name: Prepare for benchmark results commit
if: startsWith(github.ref, 'refs/tags/')
run: |
git stash
git checkout main
git stash pop
- name: Commit benchmark results
if: startsWith(github.ref, 'refs/tags/')
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "docs(benchmarks): update benchmark results"
file_pattern: BENCHMARKS.md
- name: Post benchmark results to PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const benchmarks = fs.readFileSync('BENCHMARKS.md', 'utf8');
const body = `## Benchmark Results\n\n${benchmarks}`;
const marker = '## Benchmark Results';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(
(comment) =>
comment.user?.login === 'github-actions[bot]' &&
comment.body?.startsWith(marker)
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
test:
name: Test on ${{ matrix.os }} / ${{ matrix.rust }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: [stable]
include:
- os: ubuntu-latest
rust: stable
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
rust: stable
target: aarch64-unknown-linux-gnu
- os: windows-latest
rust: stable
target: x86_64-pc-windows-msvc
- os: macos-latest
rust: stable
target: x86_64-apple-darwin
- os: macos-latest
rust: stable
target: aarch64-apple-darwin
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
components: rustfmt, clippy
- name: Install cross
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: cargo install cross
- name: Set up cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.target }}-cargo-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Cache python dependencies
uses: actions/cache@v4
with:
path: |
.venv
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
if: matrix.target != 'aarch64-unknown-linux-gnu'
run: cargo clippy --target ${{ matrix.target }} -- -D warnings
- name: Run clippy (cross)
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: cross clippy --target ${{ matrix.target }} -- -D warnings
- name: Run tests
if: matrix.target != 'aarch64-unknown-linux-gnu'
run: cargo test --target ${{ matrix.target }} --verbose
- name: Run tests (cross)
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: cross test --target ${{ matrix.target }} --verbose
- name: Run Python binding tests
shell: bash
run: |
pip install uv
uv venv
if [ "$RUNNER_OS" == "Windows" ]; then
source .venv/Scripts/activate
else
source .venv/bin/activate
fi
uv pip install maturin pytest
rm -rf target/wheels
maturin build --release
uv pip install target/wheels/*.whl
pytest tests/python/ -vv
- name: Build
if: matrix.target != 'aarch64-unknown-linux-gnu'
run: cargo build --target ${{ matrix.target }} --verbose
- name: Build (cross)
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: cross build --target ${{ matrix.target }} --verbose
- name: Check documentation
run: cargo doc --no-deps --document-private-items
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
command: build
args: --release --out dist
# Build universal2 wheels on macOS
target: ${{ runner.os == 'macOS' && 'universal2-apple-darwin' || '' }}
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: dist
build_wheels_linux:
name: Build manylinux wheels
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
command: build
args: --release --out dist
manylinux: "2014"
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux
path: dist
test_wheels:
name: Test wheels on ${{ matrix.os }} / Python ${{ matrix.python-version }}
needs: [build_wheels, build_wheels_linux]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-14, macos-15, macos-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
exclude:
# Exclude Python 3.10 on macos-14 due to known issues with setup-python
- os: macos-14
python-version: "3.10"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: actions/download-artifact@v4
with:
pattern: wheels-*
path: dist
merge-multiple: true
- name: Cache python dependencies
uses: actions/cache@v4
with:
path: |
.venv
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
- name: Install wheel and test dependencies
shell: bash
run: |
pip install uv
uv venv
if [ "$RUNNER_OS" == "Windows" ]; then
source .venv/Scripts/activate
else
source .venv/bin/activate
fi
uv pip install pytest
uv pip install simstring-rust --no-index --find-links dist/ --force-reinstall
- name: Run Python binding tests
shell: bash
run: |
if [ "$RUNNER_OS" == "Windows" ]; then
source .venv/Scripts/activate
else
source .venv/bin/activate
fi
pytest tests/python/ -vv
publish_to_test_pypi:
name: Publish to TestPyPI
needs: [test_wheels]
runs-on: ubuntu-latest
# Run on pushes to main, but not on tags
if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v')
environment:
name: testpypi
url: https://test.pypi.org/p/simstring-rust
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: wheels-*
path: dist
merge-multiple: true
- name: Publish to TestPyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_REPOSITORY_URL: https://test.pypi.org/legacy/
with:
command: upload
args: --non-interactive --skip-existing dist/*.whl
working-directory: .
before-script-linux: |
sudo apt-get update
sudo apt-get install -y tree
tree .
publish_to_pypi:
name: Publish to PyPI
needs: [test_wheels, publish]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
environment:
name: pypi
url: https://pypi.org/p/simstring-rust
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: wheels-*
path: dist
merge-multiple: true
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_REPOSITORY_URL: https://upload.pypi.org/legacy/
with:
command: upload
args: --non-interactive --skip-existing dist/*.whl
working-directory: .
before-script-linux: |
sudo apt-get update
sudo apt-get install -y tree
tree .
run-examples:
name: Run Examples
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run Rust example
run: cargo run --example basic --release
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Cache python dependencies
uses: actions/cache@v4
with:
path: |
.venv
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Run Python example
shell: bash
run: |
pip install uv
uv venv
if [ "$RUNNER_OS" == "Windows" ]; then
source .venv/Scripts/activate
else
source .venv/bin/activate
fi
uv pip install maturin
rm -rf target/wheels
maturin build --release
uv pip install target/wheels/*.whl
python examples/python_basic.py
# Publish to crates.io on new tags
publish:
name: Publish to crates.io
needs: test
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Check package
run: cargo package
- name: Publish to crates.io
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
release:
name: Create GitHub Release
needs: [publish_to_pypi, publish]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # get all tags
- name: Get release notes
id: get_release_notes
run: |
# Get the latest tag
latest_tag=$(git describe --tags --abbrev=0)
echo "latest_tag=${latest_tag}" >> $GITHUB_ENV
# Extract release notes from CHANGELOG.md
# This script extracts the content between the latest tag and the previous tag.
notes=$(awk -v tag="## [${latest_tag##v}]" '
BEGIN { p = 0 }
index($0, tag) { p = 1; next }
p && /^## / { exit }
p { print }
' CHANGELOG.md)
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$notes" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.latest_tag }}
name: "Release ${{ env.latest_tag }}"
body: ${{ steps.get_release_notes.outputs.notes }}
draft: false
prerelease: contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}