Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 173 additions & 19 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@ on:
- main
- master
tags:
- '*'
- "*"
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
validate_tag_version:
name: Validate tag version alignment
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Ensure tag version matches rust/Cargo.toml
shell: bash
run: |
python scripts/validate_tag_version.py

linux:
needs: [validate_tag_version]
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
Expand All @@ -29,19 +39,53 @@ jobs:
# target: armv7
# - runner: ubuntu-22.04
# target: ppc64le
python_version:
["3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
- id: version
name: Resolve package version
shell: bash
run: |
VERSION="$(python scripts/resolve_release_version.py)"
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "Resolved version: ${VERSION}"
- name: Apply package version
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
python - <<'PY'
import os
import re
from pathlib import Path

version = os.environ["VERSION"]
cargo_toml = Path("rust/Cargo.toml")
text = cargo_toml.read_text()
pattern = r'(^\[package\][\s\S]*?^version\s*=\s*)"[^"]+"'
new_text, count = re.subn(
pattern,
lambda m: f'{m.group(1)}"{version}"',
text,
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise SystemExit("Failed to update [package] version in rust/Cargo.toml")
cargo_toml.write_text(new_text)
print(f"Updated rust/Cargo.toml version to {version}")
PY
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --interpreter ${{ matrix.python_version }}
sccache: 'true'
args: --release --out dist -i python${{ matrix.python_version }}
sccache: "true"
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -81,27 +125,65 @@ jobs:
# path: dist

windows:
needs: [validate_tag_version]
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: windows-latest
target: x64
python_arch: x64
- runner: windows-latest
target: x86
python_version:
["3.9", "3.10", "3.11", "3.12"]
python_arch: x86
python_version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
architecture: ${{ matrix.platform.python_arch }}
- id: version
name: Resolve package version
shell: bash
run: |
VERSION="$(python scripts/resolve_release_version.py)"
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "Resolved version: ${VERSION}"
- name: Apply package version
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
python - <<'PY'
import os
import re
from pathlib import Path

version = os.environ["VERSION"]
cargo_toml = Path("rust/Cargo.toml")
text = cargo_toml.read_text()
pattern = r'(^\[package\][\s\S]*?^version\s*=\s*)"[^"]+"'
new_text, count = re.subn(
pattern,
lambda m: f'{m.group(1)}"{version}"',
text,
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise SystemExit("Failed to update [package] version in rust/Cargo.toml")
cargo_toml.write_text(new_text)
print(f"Updated rust/Cargo.toml version to {version}")
PY
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --interpreter ${{ matrix.python_version }}
sccache: 'true'
args: --release --out dist -i python
sccache: "true"
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v4
Expand All @@ -110,27 +192,62 @@ jobs:
path: dist

macos:
needs: [validate_tag_version]
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: macos-13
- runner: macos-latest
target: x86_64
- runner: macos-14
- runner: macos-latest
target: aarch64
python_version:
["3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
- id: version
name: Resolve package version
shell: bash
run: |
VERSION="$(python scripts/resolve_release_version.py)"
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "Resolved version: ${VERSION}"
- name: Apply package version
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
python - <<'PY'
import os
import re
from pathlib import Path

version = os.environ["VERSION"]
cargo_toml = Path("rust/Cargo.toml")
text = cargo_toml.read_text()
pattern = r'(^\[package\][\s\S]*?^version\s*=\s*)"[^"]+"'
new_text, count = re.subn(
pattern,
lambda m: f'{m.group(1)}"{version}"',
text,
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise SystemExit("Failed to update [package] version in rust/Cargo.toml")
cargo_toml.write_text(new_text)
print(f"Updated rust/Cargo.toml version to {version}")
PY
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --interpreter ${{ matrix.python_version }}
sccache: 'true'
args: --release --out dist -i python
sccache: "true"
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v4
Expand All @@ -139,9 +256,45 @@ jobs:
path: dist

sdist:
needs: [validate_tag_version]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: version
name: Resolve package version
shell: bash
run: |
VERSION="$(python scripts/resolve_release_version.py)"
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "Resolved version: ${VERSION}"
- name: Apply package version
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
python - <<'PY'
import os
import re
from pathlib import Path

version = os.environ["VERSION"]
cargo_toml = Path("rust/Cargo.toml")
text = cargo_toml.read_text()
pattern = r'(^\[package\][\s\S]*?^version\s*=\s*)"[^"]+"'
new_text, count = re.subn(
pattern,
lambda m: f'{m.group(1)}"{version}"',
text,
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise SystemExit("Failed to update [package] version in rust/Cargo.toml")
cargo_toml.write_text(new_text)
print(f"Updated rust/Cargo.toml version to {version}")
PY
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
Expand Down Expand Up @@ -170,7 +323,7 @@ jobs:
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: 'wheels-*/*'
subject-path: "wheels-*/*"
# - name: Display structure of downloaded files
# run: ls -R
- name: Create 'wheels' directory
Expand Down Expand Up @@ -204,7 +357,7 @@ jobs:
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: 'wheels-*/*'
subject-path: "wheels-*/*"
- name: Create 'wheels' directory
run: mkdir -p wheels
- name: Copy wheels artifacts
Expand All @@ -214,4 +367,5 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: wheels/
verbose: true
skip-existing: true
verbose: true
3 changes: 2 additions & 1 deletion conda/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ dependencies:
- matplotlib-base
- maturin
- nox
- python>=3.9,<3.13
- numba
- numpy
- numpy>=1.24,<2.0
- openblas
- opencv
- openssl
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ features = ["pyo3/extension-module"]
module-name = "av2._r"

[tool.ruff]

[tool.ruff.lint]
select = ["D"]

[tool.ruff.pydocstyle]
[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.mypy]
Expand Down
66 changes: 66 additions & 0 deletions scripts/resolve_release_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Resolve release version from GitHub ref and git tags.

Behavior:
- On tag refs (refs/tags/vX.Y.Z), use X.Y.Z.
- Otherwise, bump patch from latest v* tag.
- If no tags exist, default to 0.1.0.
"""

from __future__ import annotations

import os
import re
import subprocess
from typing import Optional

_VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$")


def normalize_version(raw: str) -> str:
"""Normalize a semantic version or v-prefixed tag to MAJOR.MINOR.PATCH."""
match = _VERSION_RE.match(raw.strip())
if match is None:
raise ValueError(f"Invalid version/tag format: {raw}")
major, minor, patch = match.groups()
return f"{int(major)}.{int(minor)}.{int(patch)}"


def bump_patch(version: str) -> str:
"""Return the next patch version for the provided semantic version."""
normalized = normalize_version(version)
major, minor, patch = normalized.split(".")
return f"{major}.{minor}.{int(patch) + 1}"


def resolve_version(ref: str, ref_name: str, latest_tag: Optional[str]) -> str:
"""Resolve package version from GitHub ref metadata and latest release tag."""
if ref.startswith("refs/tags/"):
return normalize_version(ref_name)
if latest_tag is None or latest_tag == "":
return "0.1.0"
return bump_patch(latest_tag)


def latest_version_tag() -> Optional[str]:
"""Return the latest `v*` git tag by semantic version order, if available."""
result = subprocess.run(
["git", "tag", "-l", "v*", "--sort=-v:refname"],
check=True,
capture_output=True,
text=True,
)
tags = [line.strip() for line in result.stdout.splitlines() if line.strip()]
return tags[0] if tags else None


def main() -> int:
"""Resolve and print the release version for workflow consumption."""
ref = os.environ.get("GITHUB_REF", "")
ref_name = os.environ.get("GITHUB_REF_NAME", "")
tag = latest_version_tag()
print(resolve_version(ref, ref_name, tag))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading