Files
deepseek-harness/.github/workflows/build-exe-for-python-sdk.yml
2026-08-10 19:45:54 +08:00

250 lines
9.3 KiB
YAML

name: Build single-exe
# Native builds for the release targets; see
# .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md.
# A full target run retains one SDK wheel and three runtime wheels; subset
# dispatch retains the SDK wheel and selected runtime wheels. Bare executables
# and source closures are test inputs. Run manually or label a PR
# `build-exe` (remove and reapply to rerun). Checkout uses the triggering ref,
# so dispatch needs no separate ref input.
on:
workflow_dispatch:
inputs:
targets:
description: >-
Comma-separated pkg targets to build. Any subset of:
node24-linux-x64, node24-linux-arm64, node24-macos-arm64.
Empty builds all three.
type: string
required: false
default: ''
pull_request:
types: [labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# CI runs must never report to the production telemetry endpoint baked
# into apps/cli/cordis.yml (AppCLIEntry disables the row when set).
DSH_TELEMETRY_DISABLED: '1'
jobs:
# Job-level conditions cannot inspect `matrix`, so validate target names and
# construct the matrix before the dependent jobs.
plan:
name: plan targets
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'build-exe'
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.plan.outputs.matrix }}
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: Resolve repository version
id: version
run: |
set -euo pipefail
version="$(jq -r '.version // empty' package.json)"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "::error::package.json version must be stable X.Y.Z, got '$version'"
exit 1
}
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Compute matrix from targets input
id: plan
env:
# Label runs and blank dispatch inputs build all targets.
TARGETS: ${{ inputs.targets || 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64' }}
run: |
set -euo pipefail
matrix='[]'
IFS=',' read -r -a targets <<< "$TARGETS"
for raw in "${targets[@]}"; do
t="$(echo "$raw" | xargs)" # trim surrounding whitespace
[ -z "$t" ] && continue
# Native-only: hosted arm64 Linux uses ubuntu-24.04-arm, while
# macos-latest is Apple Silicon.
case "$t" in
node24-linux-x64) runner=ubuntu-latest ;;
node24-linux-arm64) runner=ubuntu-24.04-arm ;;
node24-macos-arm64) runner=macos-latest ;;
*)
echo "::error::Unknown target '$t'. Supported: node24-linux-x64, node24-linux-arm64, node24-macos-arm64."
exit 1
;;
esac
matrix="$(jq -c --arg target "$t" --arg runner "$runner" '. + [{target: $target, runner: $runner}]' <<< "$matrix")"
done
if [ "$matrix" = '[]' ]; then
echo "::error::The targets input selected nothing to build."
exit 1
fi
echo "Matrix: $matrix"
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
sdk-wheel:
needs: plan
name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6.3.0
with:
python-version: '3.10'
- name: Install Python build tooling
run: python -m pip install uv==0.11.23
- name: Build release-shaped SDK wheel
run: >-
python scripts/build-python-release.py
--package sdk
--output-dir dist-python
- uses: actions/upload-artifact@v7
with:
name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
path: dist-python/deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
if-no-files-found: error
build:
needs: [plan, sdk-wheel]
name: ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.plan.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
# setup-node's built-in pnpm store cache keys on platform AND arch, so
# the Linux architectures sharing runner.os stay on separate caches.
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- uses: actions/setup-python@v6.3.0
with:
python-version: '3.10'
- name: Install Python build tooling
run: python -m pip install uv==0.11.23
# Cache pkg's target Node binary; lockfile changes roll the
# exact key while the restore prefix can seed its replacement.
- uses: actions/cache@v4
with:
path: ~/.pkg-cache
key: pkg-fetch-${{ matrix.target }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
pkg-fetch-${{ matrix.target }}-
- name: Install (immutable)
run: pnpm install --frozen-lockfile
- name: Build single-exe
run: pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }}
- name: Resolve platform outputs
id: runtime
env:
TARGET: ${{ matrix.target }}
VERSION: ${{ needs.plan.outputs.version }}
run: |
set -euo pipefail
platform="${TARGET#node24-}"
exe="$PWD/dist-exe/dsh-jsonrpc-agent-pkg-$platform"
[ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
case "$platform" in
linux-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl ;;
linux-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl ;;
macos-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_11_0_arm64.whl ;;
*) echo "::error::Unsupported runtime platform $platform"; exit 1 ;;
esac
echo "platform=$platform" >> "$GITHUB_OUTPUT"
echo "exe=$exe" >> "$GITHUB_OUTPUT"
echo "wheel=$wheel" >> "$GITHUB_OUTPUT"
- name: Full-turn SDK, executable snapshot, and direct-binary smoke
run: >-
uv run --python 3.10 --group test --project python/sdk
python scripts/smoke-python-runtime.py
--scenario all
--exe "${{ steps.runtime.outputs.exe }}"
- name: Build release-shaped runtime wheel
run: >-
python scripts/build-python-release.py
--package runtime
--platform "${{ steps.runtime.outputs.platform }}"
--runtime-exe "${{ steps.runtime.outputs.exe }}"
--output-dir dist-python
- uses: actions/download-artifact@v8
with:
name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
path: dist-python
- name: Install only the SDK into a clean venv and run zero-config
env:
VERSION: ${{ needs.plan.outputs.version }}
run: |
set -euo pipefail
python -m venv "$RUNNER_TEMP/dsh-sdk-smoke"
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" -m pip install \
--find-links dist-python \
deepseek-harness-sdk=="$VERSION"
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" scripts/smoke-python-runtime.py \
--scenario sdk-default
- name: Check Linux GLIBC requirements
if: runner.os == 'Linux'
run: |
set -euo pipefail
readelf --version-info "${{ steps.runtime.outputs.exe }}" | tee glibc-versions.txt
maximum="$(sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' glibc-versions.txt | sort -V | tail -1)"
[ -n "$maximum" ] || { echo "::error::No GLIBC requirements found"; exit 1; }
dpkg --compare-versions "$maximum" le 2.28 || {
echo "::error::Executable requires GLIBC_$maximum but wheel claims manylinux_2_28"
exit 1
}
- name: Run wheel in a manylinux 2.28 container
if: runner.os == 'Linux'
env:
RUNNER_ARCH: ${{ runner.arch }}
VERSION: ${{ needs.plan.outputs.version }}
run: |
set -euo pipefail
case "$RUNNER_ARCH" in
X64) image=quay.io/pypa/manylinux_2_28_x86_64 ;;
ARM64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
*) echo "::error::Unsupported Linux runner architecture $RUNNER_ARCH"; exit 1 ;;
esac
docker run --rm -e VERSION -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c '
/opt/python/cp310-cp310/bin/python -m venv /tmp/dsh-sdk
/tmp/dsh-sdk/bin/python -m pip install --find-links /work/dist-python deepseek-harness-sdk=="$VERSION"
/tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default
'
- uses: actions/upload-artifact@v7
with:
name: ${{ steps.runtime.outputs.wheel }}
path: dist-python/${{ steps.runtime.outputs.wheel }}
if-no-files-found: error