| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- name: Release (Python)
- # A manual run with publish=false builds and validates the complete release
- # without registry credentials. Publication is accepted only from a manual run
- # on the matching python-v* tag when the private publisher-repository identity
- # and public-PyPI switch are configured.
- on:
- workflow_dispatch:
- inputs:
- publish:
- description: Publish the validated wheels to public PyPI. Must run from a python-v* tag.
- required: true
- type: boolean
- default: false
- permissions:
- contents: read
- concurrency:
- # Public runs stay globally serialized across tags. Dry runs remain isolated
- # by ref so they do not block an intentional publication.
- group: ${{ github.event_name == 'workflow_dispatch' && inputs.publish && 'python-publication' || format('{0}-{1}', github.workflow, github.ref) }}
- cancel-in-progress: false
- jobs:
- build:
- name: Build six wheels
- uses: ./.github/workflows/build-exe-for-python-sdk.yml
- with:
- targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64
- release: true
- python-compat:
- name: Python ${{ matrix.python }} / installed SDK
- needs: build
- runs-on: ubuntu-latest
- timeout-minutes: 10
- strategy:
- fail-fast: false
- matrix:
- python: ['3.10', '3.14']
- steps:
- - uses: actions/checkout@v6
- with:
- persist-credentials: false
- - uses: actions/setup-python@v6.3.0
- with:
- python-version: ${{ matrix.python }}
- - uses: actions/download-artifact@v8
- with:
- pattern: deepseek_harness_*
- path: dist
- merge-multiple: true
- - name: Resolve installed wheel version
- id: compatibility-version
- run: |
- python - <<'PY' >> "$GITHUB_OUTPUT"
- import runpy
- release = runpy.run_path("scripts/build-python-release.py")
- repository_version = release["repository_version"]()
- print(f"version={release['pep440_version'](repository_version)}")
- PY
- - name: Install local release wheels and run the public entry path
- env:
- VERSION: ${{ steps.compatibility-version.outputs.version }}
- run: |
- python -m pip install \
- "dist/deepseek_harness_sdk-$VERSION-py3-none-any.whl" \
- "dist/deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl"
- python scripts/smoke-python-runtime.py --scenario sdk-default
- python scripts/smoke-python-runtime.py --scenario sdk-mcp
- validate:
- name: Validate release candidate
- needs: [build, python-compat]
- runs-on: ubuntu-latest
- timeout-minutes: 10
- outputs:
- version: ${{ steps.version.outputs.version }}
- steps:
- - uses: actions/checkout@v6
- with:
- persist-credentials: false
- - uses: actions/setup-python@v6.3.0
- with:
- python-version: '3.10'
- - name: Resolve release version
- id: version
- run: |
- python3 - <<'PY' >> "$GITHUB_OUTPUT"
- import runpy
- release = runpy.run_path("scripts/build-python-release.py")
- repository_version = release["repository_version"]()
- wheel_version = release["pep440_version"](repository_version)
- print(f"repository-version={repository_version}")
- print(f"version={wheel_version}")
- PY
- - name: Authorize publication request
- env:
- PUBLISH: ${{ github.event_name == 'workflow_dispatch' && inputs.publish }}
- PUBLIC_PYPI_RELEASE_ENABLED: ${{ vars.PUBLIC_PYPI_RELEASE_ENABLED }}
- PYPI_PUBLISHER_REPOSITORY: ${{ vars.PYPI_PUBLISHER_REPOSITORY }}
- REPOSITORY: ${{ github.repository }}
- REF_NAME: ${{ github.ref_name }}
- REF_TYPE: ${{ github.ref_type }}
- REPOSITORY_VERSION: ${{ steps.version.outputs.repository-version }}
- run: |
- set -euo pipefail
- if [ "$PUBLISH" = true ]; then
- [ -n "$PYPI_PUBLISHER_REPOSITORY" ] || {
- echo "::error::Set the repository variable PYPI_PUBLISHER_REPOSITORY before publication."
- exit 1
- }
- [ "$REPOSITORY" = "$PYPI_PUBLISHER_REPOSITORY" ] || {
- echo "::error::This repository is not the configured PyPI publisher repository."
- exit 1
- }
- [ "$PUBLIC_PYPI_RELEASE_ENABLED" = true ] || {
- echo "::error::Set PUBLIC_PYPI_RELEASE_ENABLED=true before public publication."
- exit 1
- }
- [ "$REF_TYPE" = tag ] && [ "$REF_NAME" = "python-v$REPOSITORY_VERSION" ] || {
- echo "::error::Publication must run from tag python-v$REPOSITORY_VERSION."
- exit 1
- }
- fi
- - uses: actions/download-artifact@v8
- with:
- pattern: deepseek_harness_*
- path: dist
- merge-multiple: true
- - name: Check release contents
- env:
- VERSION: ${{ steps.version.outputs.version }}
- run: |
- set -euo pipefail
- expected="$(mktemp)"
- actual="$(mktemp)"
- printf '%s\n' \
- "deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl" \
- "deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_x86_64.whl" \
- "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl" \
- "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl" \
- "deepseek_harness_runtime_bin-$VERSION-py3-none-win_amd64.whl" \
- "deepseek_harness_sdk-$VERSION-py3-none-any.whl" > "$expected"
- find dist -maxdepth 1 -type f -name '*.whl' -exec basename {} \; | sort > "$actual"
- diff -u "$expected" "$actual"
- while IFS= read -r wheel; do
- size="$(stat -c '%s' "dist/$wheel")"
- [ "$size" -lt 100000000 ] || {
- echo "::error::$wheel is $size bytes; public PyPI accepts at most 100000000 bytes by default."
- exit 1
- }
- done < "$actual"
- - name: Validate package metadata
- run: |
- python -m pip install twine==6.2.0
- python -m twine check dist/*.whl
- - name: Record artifact hashes
- run: |
- cd dist
- sha256sum *.whl | sort -k2 > SHA256SUMS
- cat SHA256SUMS
- - uses: actions/upload-artifact@v7
- with:
- name: python-release-${{ steps.version.outputs.version }}
- path: dist/*
- if-no-files-found: error
- retention-days: 7
- publish-runtime:
- name: Publish runtime wheels to public PyPI
- if: github.event_name == 'workflow_dispatch' && inputs.publish
- needs: validate
- runs-on: ubuntu-latest
- timeout-minutes: 10
- environment: pypi-runtime
- permissions:
- contents: read
- id-token: write
- steps:
- - uses: actions/download-artifact@v8
- with:
- name: python-release-${{ needs.validate.outputs.version }}
- path: dist
- - name: Verify release artifact hashes
- run: cd dist && sha256sum -c SHA256SUMS
- - name: Select runtime wheels
- run: |
- mkdir -p dist/runtime
- mv dist/deepseek_harness_runtime_bin-*.whl dist/runtime/
- - name: Publish runtime wheels
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- packages-dir: dist/runtime/
- # Public attestations reveal the private publisher repository. OIDC
- # authentication remains enabled without uploading that provenance.
- attestations: false
- # Keep the SDK in a dependent job. If its upload fails after the immutable
- # runtime files arrive, "re-run failed jobs" resumes here without attempting
- # to overwrite the runtime release.
- publish-sdk:
- name: Publish SDK wheel to public PyPI
- if: github.event_name == 'workflow_dispatch' && inputs.publish
- needs: [validate, publish-runtime]
- runs-on: ubuntu-latest
- timeout-minutes: 10
- environment: pypi
- permissions:
- contents: read
- id-token: write
- steps:
- - uses: actions/download-artifact@v8
- with:
- name: python-release-${{ needs.validate.outputs.version }}
- path: dist
- - name: Verify release artifact hashes
- run: cd dist && sha256sum -c SHA256SUMS
- - name: Select SDK wheel
- run: |
- mkdir -p dist/sdk
- mv dist/deepseek_harness_sdk-*.whl dist/sdk/
- - name: Publish SDK wheel
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- packages-dir: dist/sdk/
- attestations: false
|