diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index bc92ab648..bdc8b1a71 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -89,14 +89,70 @@ jobs: "${REGISTRY}/${component}:${{ github.sha }}" done - build-python-wheels: - name: Stage Python Wheels + build-python-wheels-linux: + name: Build Python Wheels (Linux ${{ matrix.arch }}) + needs: [compute-versions] + strategy: + matrix: + include: + - arch: amd64 + runner: build-amd64 + artifact: linux-amd64 + task: python:build:linux:amd64 + output_path: target/wheels/linux-amd64/*.whl + - arch: arm64 + runner: build-arm64 + artifact: linux-arm64 + task: python:build:linux:arm64 + output_path: target/wheels/linux-arm64/*.whl + runs-on: ${{ matrix.runner }} + timeout-minutes: 120 + container: + image: ghcr.io/nvidia/openshell/ci:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --privileged + env: + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }} + OPENSHELL_IMAGE_TAG: dev + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Mark workspace safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Sync Python dependencies + run: uv sync + + - name: Cache Rust target and registry + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + with: + shared-key: python-wheel-linux-${{ matrix.arch }} + cache-directories: .cache/sccache + cache-targets: "true" + + - name: Build Python wheels + run: | + set -euo pipefail + OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run ${{ matrix.task }} + ls -la ${{ matrix.output_path }} + + - name: Upload wheel artifacts + uses: actions/upload-artifact@v4 + with: + name: python-wheels-${{ matrix.artifact }} + path: ${{ matrix.output_path }} + retention-days: 5 + + build-python-wheel-macos: + name: Build Python Wheel (macOS) needs: [compute-versions] runs-on: build-amd64 timeout-minutes: 120 - outputs: - wheel_version: ${{ needs.compute-versions.outputs.python_version }} - wheel_filenames: ${{ steps.filenames.outputs.wheel_filenames }} container: image: ghcr.io/nvidia/openshell/ci:latest credentials: @@ -126,24 +182,16 @@ jobs: - name: Sync Python dependencies run: uv sync - - name: Build Python wheels + - name: Build Python wheel run: | set -euo pipefail - OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:multiarch OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:macos ls -la target/wheels/*.whl - - name: Capture wheel filenames - id: filenames - run: | - set -euo pipefail - WHEEL_FILENAMES=$(ls target/wheels/*.whl | xargs -n1 basename | paste -sd, -) - echo "wheel_filenames=${WHEEL_FILENAMES}" >> "$GITHUB_OUTPUT" - - name: Upload wheel artifacts uses: actions/upload-artifact@v4 with: - name: python-wheels + name: python-wheels-macos path: target/wheels/*.whl retention-days: 5 @@ -335,9 +383,11 @@ jobs: # --------------------------------------------------------------------------- release-dev: name: Release Dev - needs: [build-cli-linux, build-cli-macos, build-python-wheels] + needs: [compute-versions, build-cli-linux, build-cli-macos, build-python-wheels-linux, build-python-wheel-macos] runs-on: build-amd64 timeout-minutes: 10 + outputs: + wheel_filenames: ${{ steps.wheel_filenames.outputs.wheel_filenames }} steps: - uses: actions/checkout@v4 @@ -351,8 +401,17 @@ jobs: - name: Download wheel artifacts uses: actions/download-artifact@v4 with: - name: python-wheels + pattern: python-wheels-* path: release/ + merge-multiple: true + + - name: Capture wheel filenames + id: wheel_filenames + run: | + set -euo pipefail + ls -la release/*.whl + WHEEL_FILENAMES=$(ls release/*.whl | xargs -n1 basename | sort | paste -sd, -) + echo "wheel_filenames=${WHEEL_FILENAMES}" >> "$GITHUB_OUTPUT" - name: Generate checksums run: | @@ -364,7 +423,7 @@ jobs: - name: Prune stale wheel assets from dev release uses: actions/github-script@v7 env: - WHEEL_VERSION: ${{ needs.build-python-wheels.outputs.wheel_version }} + WHEEL_VERSION: ${{ needs.compute-versions.outputs.python_version }} with: script: | const wheelVersion = process.env.WHEEL_VERSION; @@ -442,7 +501,7 @@ jobs: trigger-wheel-publish: name: Trigger Wheel Publish - needs: [compute-versions, build-python-wheels, release-dev] + needs: [compute-versions, release-dev] runs-on: [self-hosted, nv] timeout-minutes: 10 steps: @@ -451,7 +510,7 @@ jobs: GITLAB_CI_TRIGGER_TOKEN: ${{ secrets.GITLAB_CI_TRIGGER_TOKEN }} GITLAB_CI_TRIGGER_URL: ${{ secrets.GITLAB_CI_TRIGGER_URL }} RELEASE_VERSION: ${{ needs.compute-versions.outputs.python_version }} - WHEEL_FILENAMES: ${{ needs.build-python-wheels.outputs.wheel_filenames }} + WHEEL_FILENAMES: ${{ needs.release-dev.outputs.wheel_filenames }} run: | set -euo pipefail if [ -z "${WHEEL_FILENAMES}" ]; then diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 564a88d67..d89d67032 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -109,14 +109,71 @@ jobs: "${REGISTRY}/${component}:${{ github.sha }}" done - build-python-wheels: - name: Stage Python Wheels + build-python-wheels-linux: + name: Build Python Wheels (Linux ${{ matrix.arch }}) + needs: [compute-versions] + strategy: + matrix: + include: + - arch: amd64 + runner: build-amd64 + artifact: linux-amd64 + task: python:build:linux:amd64 + output_path: target/wheels/linux-amd64/*.whl + - arch: arm64 + runner: build-arm64 + artifact: linux-arm64 + task: python:build:linux:arm64 + output_path: target/wheels/linux-arm64/*.whl + runs-on: ${{ matrix.runner }} + timeout-minutes: 120 + container: + image: ghcr.io/nvidia/openshell/ci:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --privileged + env: + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }} + OPENSHELL_IMAGE_TAG: ${{ needs.compute-versions.outputs.semver }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} + fetch-depth: 0 + + - name: Mark workspace safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Sync Python dependencies + run: uv sync + + - name: Cache Rust target and registry + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + with: + shared-key: python-wheel-linux-${{ matrix.arch }} + cache-directories: .cache/sccache + cache-targets: "true" + + - name: Build Python wheels + run: | + set -euo pipefail + OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run ${{ matrix.task }} + ls -la ${{ matrix.output_path }} + + - name: Upload wheel artifacts + uses: actions/upload-artifact@v4 + with: + name: python-wheels-${{ matrix.artifact }} + path: ${{ matrix.output_path }} + retention-days: 5 + + build-python-wheel-macos: + name: Build Python Wheel (macOS) needs: [compute-versions] runs-on: build-amd64 timeout-minutes: 120 - outputs: - wheel_version: ${{ needs.compute-versions.outputs.python_version }} - wheel_filenames: ${{ steps.filenames.outputs.wheel_filenames }} container: image: ghcr.io/nvidia/openshell/ci:latest credentials: @@ -147,24 +204,16 @@ jobs: - name: Sync Python dependencies run: uv sync - - name: Build Python wheels + - name: Build Python wheel run: | set -euo pipefail - OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:multiarch OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:macos ls -la target/wheels/*.whl - - name: Capture wheel filenames - id: filenames - run: | - set -euo pipefail - WHEEL_FILENAMES=$(ls target/wheels/*.whl | xargs -n1 basename | paste -sd, -) - echo "wheel_filenames=${WHEEL_FILENAMES}" >> "$GITHUB_OUTPUT" - - name: Upload wheel artifacts uses: actions/upload-artifact@v4 with: - name: python-wheels + name: python-wheels-macos path: target/wheels/*.whl retention-days: 5 @@ -358,9 +407,11 @@ jobs: # --------------------------------------------------------------------------- release: name: Release - needs: [compute-versions, build-cli-linux, build-cli-macos, build-python-wheels, tag-ghcr-release] + needs: [compute-versions, build-cli-linux, build-cli-macos, build-python-wheels-linux, build-python-wheel-macos, tag-ghcr-release] runs-on: build-amd64 timeout-minutes: 10 + outputs: + wheel_filenames: ${{ steps.wheel_filenames.outputs.wheel_filenames }} steps: - uses: actions/checkout@v4 with: @@ -376,8 +427,17 @@ jobs: - name: Download wheel artifacts uses: actions/download-artifact@v4 with: - name: python-wheels + pattern: python-wheels-* path: release/ + merge-multiple: true + + - name: Capture wheel filenames + id: wheel_filenames + run: | + set -euo pipefail + ls -la release/*.whl + WHEEL_FILENAMES=$(ls release/*.whl | xargs -n1 basename | sort | paste -sd, -) + echo "wheel_filenames=${WHEEL_FILENAMES}" >> "$GITHUB_OUTPUT" - name: Generate checksums run: | @@ -437,7 +497,7 @@ jobs: trigger-wheel-publish: name: Trigger Wheel Publish - needs: [compute-versions, build-python-wheels, release] + needs: [compute-versions, release] runs-on: [self-hosted, nv] timeout-minutes: 10 steps: @@ -447,7 +507,7 @@ jobs: GITLAB_CI_TRIGGER_URL: ${{ secrets.GITLAB_CI_TRIGGER_URL }} RELEASE_VERSION: ${{ needs.compute-versions.outputs.python_version }} RELEASE_TAG: ${{ env.RELEASE_TAG }} - WHEEL_FILENAMES: ${{ needs.build-python-wheels.outputs.wheel_filenames }} + WHEEL_FILENAMES: ${{ needs.release.outputs.wheel_filenames }} run: | set -euo pipefail if [ -z "${WHEEL_FILENAMES}" ]; then diff --git a/architecture/build-containers.md b/architecture/build-containers.md index ee69896e9..493e7207a 100644 --- a/architecture/build-containers.md +++ b/architecture/build-containers.md @@ -21,6 +21,15 @@ The cluster image is a single-container Kubernetes distribution that bundles the The supervisor binary (`openshell-sandbox`) is built by the shared `supervisor-builder` stage in `deploy/docker/Dockerfile.images` and placed at `/opt/openshell/bin/openshell-sandbox`. It is exposed to sandbox pods at runtime via a read-only `hostPath` volume mount — it is not baked into sandbox images. +## Python Wheels + +OpenShell also publishes Python wheels for `linux/amd64`, `linux/arm64`, and macOS ARM64. + +- Linux wheels are built natively on matching Linux runners via `build:python:wheel:linux:amd64` and `build:python:wheel:linux:arm64` in `tasks/python.toml`. +- There is no local Linux multiarch wheel build task. Release workflows own the per-arch Linux wheel production. +- The macOS ARM64 wheel is cross-compiled with `deploy/docker/Dockerfile.python-wheels-macos` via `build:python:wheel:macos`. +- Release workflows mirror the CLI layout: a Linux matrix job for amd64/arm64, a separate macOS job, and release jobs that download the per-platform wheel artifacts directly before publishing. + ## Sandbox Images Sandbox images are **not built in this repository**. They are maintained in the [openshell-community](https://github.com/nvidia/openshell-community) repository and pulled from `ghcr.io/nvidia/openshell-community/sandboxes/` at runtime. @@ -70,4 +79,3 @@ The harness runs isolated scenarios in temporary git worktrees, keeps its own st - auto-detection checks for gateway-only, supervisor-only, shared, Helm-only, unrelated, and explicit-target changes - cold vs warm rebuild comparisons for gateway and supervisor code changes - container-ID invalidation coverage to verify gateway + Helm are retriggered when the cluster container changes - diff --git a/tasks/publish.toml b/tasks/publish.toml deleted file mode 100644 index 7845a1aba..000000000 --- a/tasks/publish.toml +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Publishing / release tasks - -["publish:main"] -description = "Main branch publish job (images with :dev, :latest, and version tag)" -run = """ -#!/usr/bin/env bash -set -euo pipefail -VERSION_DOCKER=$(uv run python tasks/scripts/release.py get-version --docker) -VERSION_PYTHON=$(uv run python tasks/scripts/release.py get-version --python) -CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) -IMAGE_TAG=dev TAG_LATEST=true EXTRA_DOCKER_TAGS="$VERSION_DOCKER" mise run build:docker:cluster:multiarch -OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run build:python:wheel:multiarch -OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run build:python:wheel:macos -uv run python tasks/scripts/release.py python-publish --version "$VERSION_PYTHON" -""" -hide = true - -["publish:tag"] -description = "Tag release publish: versioned Docker to ECR and Python to GitHub Packages" -run = """ -#!/usr/bin/env bash -set -euo pipefail -VERSION_DOCKER=$(uv run python tasks/scripts/release.py get-version --docker) -VERSION_PYTHON=$(uv run python tasks/scripts/release.py get-version --python) -CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) -IMAGE_TAG="$VERSION_DOCKER" TAG_LATEST=false mise run build:docker:cluster:multiarch -OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run build:python:wheel:multiarch -OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run build:python:wheel:macos -uv run python tasks/scripts/release.py python-publish --version "$VERSION_PYTHON" -""" -hide = true diff --git a/tasks/python.toml b/tasks/python.toml index 211e474c1..1e53da7fb 100644 --- a/tasks/python.toml +++ b/tasks/python.toml @@ -20,99 +20,86 @@ description = "Alias for build:python:wheel" depends = ["build:python:wheel"] hide = true -["build:python:wheel:multiarch"] -description = "Build Python wheels for Linux amd64/arm64 with buildx" +["build:python:wheel:linux"] +description = "Build a Linux Python wheel natively on the current host" depends = ["python:proto"] -run = """ +run = ''' #!/usr/bin/env bash set -euo pipefail -sha256_16() { - if command -v sha256sum >/dev/null 2>&1; then - sha256sum "$1" | awk '{print substr($1, 1, 16)}' - else - shasum -a 256 "$1" | awk '{print substr($1, 1, 16)}' - fi +WHEEL_OUTPUT_DIR=${WHEEL_OUTPUT_DIR:?Set WHEEL_OUTPUT_DIR to a per-platform wheel output directory} +EXPECTED_HOST_ARCH=${EXPECTED_HOST_ARCH:?Set EXPECTED_HOST_ARCH to amd64 or arm64} +ORIGINAL_CARGO_TOML="" + +normalize_arch() { + case "$1" in + x86_64|amd64) echo amd64 ;; + aarch64|arm64) echo arm64 ;; + *) echo "$1" ;; + esac } -sha256_16_stdin() { - if command -v sha256sum >/dev/null 2>&1; then - sha256sum | awk '{print substr($1, 1, 16)}' - else - shasum -a 256 | awk '{print substr($1, 1, 16)}' +cleanup() { + if [ -n "$ORIGINAL_CARGO_TOML" ] && [ -f "$ORIGINAL_CARGO_TOML" ]; then + cp "$ORIGINAL_CARGO_TOML" Cargo.toml + rm -f "$ORIGINAL_CARGO_TOML" fi } -PLATFORMS=${DOCKER_PLATFORMS:-linux/amd64,linux/arm64} -CARGO_VERSION=${OPENSHELL_CARGO_VERSION:-} -if [ -z "$CARGO_VERSION" ] && [ -n "${CI:-}" ]; then - CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) -fi - -LOCK_HASH=$(sha256_16 Cargo.lock) -RUST_SCOPE=${RUST_TOOLCHAIN_SCOPE:-rustup-stable} -CACHE_SCOPE_INPUT="v1|python-wheels|base|${LOCK_HASH}|${RUST_SCOPE}" -CARGO_TARGET_CACHE_SCOPE=$(printf '%s' "$CACHE_SCOPE_INPUT" | sha256_16_stdin) +trap cleanup EXIT -VERSION_ARGS=( - --build-arg "CARGO_TARGET_CACHE_SCOPE=${CARGO_TARGET_CACHE_SCOPE}" - ${OPENSHELL_IMAGE_TAG:+--build-arg "OPENSHELL_IMAGE_TAG=${OPENSHELL_IMAGE_TAG}"} -) +if [ "$(uname -s)" != "Linux" ]; then + echo "build:python:wheel:linux requires a Linux host" >&2 + exit 1 +fi -if [ -n "$CARGO_VERSION" ]; then - VERSION_ARGS+=(--build-arg "OPENSHELL_CARGO_VERSION=${CARGO_VERSION}") +HOST_ARCH=$(normalize_arch "$(uname -m)") +EXPECTED_HOST_ARCH=$(normalize_arch "$EXPECTED_HOST_ARCH") +if [ "$HOST_ARCH" != "$EXPECTED_HOST_ARCH" ]; then + echo "build:python:wheel:linux expected ${EXPECTED_HOST_ARCH} host, got ${HOST_ARCH}" >&2 + exit 1 fi -if ! docker buildx inspect multiarch >/dev/null 2>&1; then - echo "Creating multi-platform buildx builder..." - docker buildx create --name multiarch --use --bootstrap +# Avoid failures when RUSTC_WRAPPER is set to an unresolved `sccache` binary. +if command -v sccache >/dev/null 2>&1; then + export RUSTC_WRAPPER="$(command -v sccache)" else - docker buildx use multiarch + unset RUSTC_WRAPPER || true fi -mkdir -p target/wheels - -USE_LOCAL_CACHE=0 -if [ -z "${CI:-}" ] && docker buildx inspect multiarch 2>/dev/null | grep -q "Driver: docker-container"; then - USE_LOCAL_CACHE=1 +if [ -n "${OPENSHELL_CARGO_VERSION:-}" ]; then + ORIGINAL_CARGO_TOML=$(mktemp) + cp Cargo.toml "$ORIGINAL_CARGO_TOML" + sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${OPENSHELL_CARGO_VERSION}"'"/}' Cargo.toml fi -DOCKER_BUILD_CACHE_DIR=${DOCKER_BUILD_CACHE_DIR:-.cache/buildkit/python-wheels} -if [ "$USE_LOCAL_CACHE" -eq 1 ]; then - mkdir -p "$DOCKER_BUILD_CACHE_DIR" -fi +rm -rf "$WHEEL_OUTPUT_DIR" +mkdir -p "$WHEEL_OUTPUT_DIR" -for platform in ${PLATFORMS//,/ }; do - echo "Building wheel for ${platform}..." - CACHE_ARGS=() - if [ "$USE_LOCAL_CACHE" -eq 1 ]; then - PLATFORM_CACHE_KEY=$(printf '%s' "$platform" | tr '/' '-') - PLATFORM_CACHE_PATH="${DOCKER_BUILD_CACHE_DIR}/${PLATFORM_CACHE_KEY}" - mkdir -p "$PLATFORM_CACHE_PATH" - CACHE_ARGS=( - --cache-from "type=local,src=${PLATFORM_CACHE_PATH}" - --cache-to "type=local,dest=${PLATFORM_CACHE_PATH},mode=max" - ) - fi +uv run maturin build --release --features bundled-z3 --out "$WHEEL_OUTPUT_DIR" - docker buildx build \ - --builder multiarch \ - --platform "${platform}" \ - "${CACHE_ARGS[@]}" \ - -f deploy/docker/Dockerfile.python-wheels \ - --target wheels \ - "${VERSION_ARGS[@]}" \ - --output type=local,dest=target/wheels \ - . -done +ls -la "$WHEEL_OUTPUT_DIR"/*.whl +''' +hide = true -ls -la target/wheels/*.whl -""" +["build:python:wheel:linux:amd64"] +description = "Build Python wheel for Linux amd64 natively" +depends = ["EXPECTED_HOST_ARCH=amd64 WHEEL_OUTPUT_DIR=target/wheels/linux-amd64 build:python:wheel:linux"] +hide = true + +["python:build:linux:amd64"] +description = "Alias for build:python:wheel:linux:amd64" +depends = ["build:python:wheel:linux:amd64"] hide = true -["python:build:multiarch"] -description = "Alias for build:python:wheel:multiarch" -depends = ["build:python:wheel:multiarch"] +["build:python:wheel:linux:arm64"] +description = "Build Python wheel for Linux arm64 natively" +depends = ["EXPECTED_HOST_ARCH=arm64 WHEEL_OUTPUT_DIR=target/wheels/linux-arm64 build:python:wheel:linux"] +hide = true + +["python:build:linux:arm64"] +description = "Alias for build:python:wheel:linux:arm64" +depends = ["build:python:wheel:linux:arm64"] hide = true ["build:python:wheel:macos"] @@ -204,16 +191,6 @@ description = "Alias for build:python:wheel:macos:docker" depends = ["build:python:wheel:macos:docker"] hide = true -["build:python:wheel:all"] -description = "Build Python wheels for Linux and macOS" -depends = ["build:python:wheel", "build:python:wheel:multiarch", "build:python:wheel:macos"] -hide = true - -["python:build:all"] -description = "Alias for build:python:wheel:all" -depends = ["build:python:wheel:all"] -hide = true - ["python:lint"] description = "Lint Python code with ruff" depends = ["python:proto"] @@ -237,30 +214,6 @@ depends = ["python:proto"] run = "uv run ty check {{vars.python_paths}}" hide = true -["python:publish"] -description = "Build and publish Python wheels" -run = """ -#!/usr/bin/env bash -set -euo pipefail -VERSION=$(uv run python tasks/scripts/release.py get-version --python) -CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) -OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run build:python:wheel:all -uv run python tasks/scripts/release.py python-publish --version "$VERSION" -""" -hide = true - -["python:publish:macos"] -description = "Build and publish macOS arm64 Python wheel" -run = """ -#!/usr/bin/env bash -set -euo pipefail -VERSION=$(uv run python tasks/scripts/release.py get-version --python) -CARGO_VERSION=$(uv run python tasks/scripts/release.py get-version --cargo) -OPENSHELL_CARGO_VERSION="$CARGO_VERSION" mise run python:build:macos -uv run python tasks/scripts/release.py python-publish --version "$VERSION" --wheel-glob "*macosx*arm64.whl" -""" -hide = true - ["python:proto"] description = "Generate Python protobuf stubs from .proto files" env = { UV_NO_SYNC = "1" }