| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- 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 five runtime wheels; subset
- # dispatch retains the SDK wheel and selected runtime wheels. Bare executables
- # and source closures are test inputs. Run manually or call it from the Python
- # release workflow. There is no `pull_request` trigger: a label trigger would
- # list gray skipped checks on every unrelated PR label event. Checkout uses the
- # triggering ref, so dispatch needs no separate ref input.
- on:
- workflow_call:
- inputs:
- targets:
- description: Comma-separated pkg targets to build; empty builds all five.
- type: string
- required: false
- default: ''
- release:
- description: Run as the native builder for the Python release workflow.
- type: boolean
- required: false
- default: false
- ci:
- description: Run as the required all-target Python runtime pull-request check.
- type: boolean
- required: false
- default: false
- secrets:
- DEEPSEEK_API_KEY_EXTERNAL:
- description: Real DeepSeek API key for trusted installed-wheel pull-request tests.
- required: false
- workflow_dispatch:
- inputs:
- targets:
- description: >-
- Comma-separated pkg targets to build. Any subset of:
- node24-linux-x64, node24-linux-arm64, node24-macos-arm64,
- node24-macos-x64, node24-win-x64. Empty builds all five.
- type: string
- required: false
- default: ''
- concurrency:
- # Keep the called workflow distinct from its caller's concurrency group;
- # github.workflow identifies the caller inside a reusable workflow and keeps
- # an ordinary CI run from cancelling a full release validation on the same ref.
- group: build-single-exe-${{ 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: inputs.ci || inputs.release || github.event_name == 'workflow_dispatch'
- runs-on: ubuntu-latest
- timeout-minutes: 5
- outputs:
- matrix: ${{ steps.plan.outputs.matrix }}
- version: ${{ steps.version.outputs.version }}
- repository-version: ${{ steps.version.outputs.repository-version }}
- steps:
- - uses: actions/checkout@v6
- - name: Resolve repository version
- id: version
- run: |
- set -euo pipefail
- 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: Compute matrix from targets input
- id: plan
- env:
- # Blank dispatch inputs build all targets.
- TARGETS: ${{ inputs.targets || 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64' }}
- 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; macos-15-intel is native x64.
- case "$t" in
- node24-linux-x64) runner=ubuntu-latest ;;
- node24-linux-arm64) runner=ubuntu-24.04-arm ;;
- node24-macos-arm64) runner=macos-latest ;;
- node24-macos-x64) runner=macos-15-intel ;;
- node24-win-x64) runner=windows-2025 ;;
- *)
- echo "::error::Unknown target '$t'. Supported: node24-linux-x64, node24-linux-arm64, node24-macos-arm64, node24-macos-x64, node24-win-x64."
- 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
- retention-days: 7
- 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
- with:
- dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
- - name: Enable Windows Developer Mode (symlink support)
- if: runner.os == 'Windows'
- shell: pwsh
- run: >-
- reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
- /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
- # 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: Rebuild Linux node-pty against manylinux 2.28
- if: runner.os == 'Linux'
- env:
- RUNNER_ARCH: ${{ runner.arch }}
- 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
- addon_dir="$(realpath packages/subprocess/subprocess-local/node_modules/node-pty)"
- pnpm_setup_root="$(realpath "$(dirname "$(dirname "$PNPM_HOME")")")"
- (cd "$addon_dir" && npm_config_build_from_source=true pnpm run install)
- addon="$addon_dir/build/Release/pty.node"
- [ -f "$addon_dir/build/Makefile" ] || {
- echo "::error::node-pty install did not generate $addon_dir/build/Makefile"
- exit 1
- }
- docker run --rm \
- --user "$(id -u):$(id -g)" \
- -v "$PWD:$PWD" \
- -v "$HOME/.cache/node-gyp:$HOME/.cache/node-gyp:ro" \
- -v "$pnpm_setup_root:$pnpm_setup_root:ro" \
- -w "$addon_dir" \
- "$image" \
- bash -euxo pipefail -c \
- 'rm -rf build/Release && make -C build -j2 BUILDTYPE=Release'
- [ -f "$addon" ] || { echo "::error::$addon missing after manylinux rebuild"; exit 1; }
- readelf --version-info "$addon" | tee node-pty-glibc-versions.txt
- maximum="$(sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' node-pty-glibc-versions.txt | sort -V | tail -1)"
- [ -n "$maximum" ] || { echo "::error::No GLIBC requirements found in $addon"; exit 1; }
- dpkg --compare-versions "$maximum" le 2.28 || {
- echo "::error::node-pty addon requires GLIBC_$maximum but wheel claims manylinux_2_28"
- exit 1
- }
- - name: Build single-exe
- env:
- DSH_BUILD_CLIENT_PROFILE: official
- run: pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }}
- - name: Resolve platform outputs (POSIX)
- id: runtime-posix
- if: runner.os != 'Windows'
- env:
- TARGET: ${{ matrix.target }}
- VERSION: ${{ needs.plan.outputs.version }}
- run: |
- set -euo pipefail
- platform="${TARGET#node24-}"
- exe="$PWD/dist-exe/deepseek-harness-sdk-runtime-$platform"
- 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_14_0_arm64.whl ;;
- macos-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_x86_64.whl ;;
- *) echo "::error::Unsupported runtime platform $platform"; exit 1 ;;
- esac
- [ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
- echo "platform=$platform" >> "$GITHUB_OUTPUT"
- echo "exe=$exe" >> "$GITHUB_OUTPUT"
- echo "wheel=$wheel" >> "$GITHUB_OUTPUT"
- - name: Resolve platform outputs (Windows)
- id: runtime-windows
- if: runner.os == 'Windows'
- shell: pwsh
- env:
- TARGET: ${{ matrix.target }}
- VERSION: ${{ needs.plan.outputs.version }}
- run: |
- if ($env:TARGET -ne 'node24-win-x64') { throw "Unsupported runtime target $env:TARGET" }
- $platform = 'win-x64'
- $exe = Join-Path $PWD 'dist-exe\deepseek-harness-sdk-runtime-win-x64.exe'
- $wheel = "deepseek_harness_runtime_bin-$env:VERSION-py3-none-win_amd64.whl"
- if (-not (Test-Path -LiteralPath $exe -PathType Leaf)) { throw "Runtime executable is missing at $exe" }
- "platform=$platform" >> $env:GITHUB_OUTPUT
- "exe=$exe" >> $env:GITHUB_OUTPUT
- "wheel=$wheel" >> $env:GITHUB_OUTPUT
- - name: Build release-shaped runtime wheel
- run: >-
- python scripts/build-python-release.py
- --package runtime
- --platform "${{ steps.runtime-posix.outputs.platform || steps.runtime-windows.outputs.platform }}"
- --runtime-exe "${{ steps.runtime-posix.outputs.exe || steps.runtime-windows.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 local SDK and runtime wheels into a clean venv (POSIX)
- id: smoke-venv-posix
- if: runner.os != 'Windows'
- env:
- RUNTIME_WHEEL: ${{ steps.runtime-posix.outputs.wheel }}
- SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
- run: |
- set -euo pipefail
- venv="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-smoke-"))')"
- python -m venv "$venv"
- smoke_python="$venv/bin/python"
- "$smoke_python" -m pip install \
- "dist-python/$SDK_WHEEL" \
- "dist-python/$RUNTIME_WHEEL"
- echo "python=$smoke_python" >> "$GITHUB_OUTPUT"
- - name: Install local SDK and runtime wheels into a clean venv (Windows)
- id: smoke-venv-windows
- if: runner.os == 'Windows'
- shell: pwsh
- env:
- RUNTIME_WHEEL: ${{ steps.runtime-windows.outputs.wheel }}
- SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
- run: |
- $venv = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-smoke-"))').Trim()
- python -m venv $venv
- $smokePython = Join-Path $venv 'Scripts\python.exe'
- & $smokePython -m pip install "dist-python/$env:SDK_WHEEL" "dist-python/$env:RUNTIME_WHEEL"
- if ($LASTEXITCODE -ne 0) { throw "Wheel installation failed with exit code $LASTEXITCODE" }
- "python=$smokePython" >> $env:GITHUB_OUTPUT
- - name: Run installed-wheel keyless black-box tests (POSIX)
- if: runner.os != 'Windows'
- run: |
- set -euo pipefail
- blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-"))')"
- cd "$blackbox_root"
- env -u PYTHONPATH -u DSH_RUNTIME_MODE \
- "${{ steps.smoke-venv-posix.outputs.python }}" \
- "$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
- --scenario all \
- --installed-wheel
- - name: Run installed-wheel keyless black-box tests (Windows)
- if: runner.os == 'Windows'
- shell: pwsh
- run: |
- $blackboxRoot = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-"))').Trim()
- Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
- Remove-Item Env:DSH_RUNTIME_MODE -ErrorAction SilentlyContinue
- Push-Location $blackboxRoot
- try {
- & "${{ steps.smoke-venv-windows.outputs.python }}" "$env:GITHUB_WORKSPACE\scripts\smoke-python-runtime.py" --scenario all --installed-wheel
- if ($LASTEXITCODE -ne 0) { throw "Installed-wheel black-box failed with exit code $LASTEXITCODE" }
- } finally {
- Pop-Location
- }
- - name: Preflight installed-wheel real API test (POSIX)
- if: >-
- inputs.ci
- && runner.os != 'Windows'
- && (github.event_name != 'pull_request'
- || !(github.event.pull_request.head.repo.fork
- || github.event.pull_request.user.login == 'dependabot[bot]'))
- env:
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
- run: |
- set -euo pipefail
- if [ -z "${DEEPSEEK_API_KEY:-}" ]; then
- echo "::error::DEEPSEEK_API_KEY_EXTERNAL is empty; the installed-wheel real API test cannot self-skip."
- exit 1
- fi
- - name: Preflight installed-wheel real API test (Windows)
- if: >-
- inputs.ci
- && runner.os == 'Windows'
- && (github.event_name != 'pull_request'
- || !(github.event.pull_request.head.repo.fork
- || github.event.pull_request.user.login == 'dependabot[bot]'))
- shell: pwsh
- env:
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
- run: |
- if ([string]::IsNullOrWhiteSpace($env:DEEPSEEK_API_KEY)) {
- throw 'DEEPSEEK_API_KEY_EXTERNAL is empty; the installed-wheel real API test cannot self-skip.'
- }
- - name: Run installed-wheel real API black-box test (POSIX)
- if: >-
- inputs.ci
- && runner.os != 'Windows'
- && (github.event_name != 'pull_request'
- || !(github.event.pull_request.head.repo.fork
- || github.event.pull_request.user.login == 'dependabot[bot]'))
- env:
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
- DEEPSEEK_BASE_URL: https://api.deepseek.com
- run: |
- set -euo pipefail
- blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-live-"))')"
- cd "$blackbox_root"
- env -u PYTHONPATH -u DSH_RUNTIME_MODE \
- "${{ steps.smoke-venv-posix.outputs.python }}" \
- "$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
- --scenario sdk-live \
- --installed-wheel
- - name: Run installed-wheel real API black-box test (Windows)
- if: >-
- inputs.ci
- && runner.os == 'Windows'
- && (github.event_name != 'pull_request'
- || !(github.event.pull_request.head.repo.fork
- || github.event.pull_request.user.login == 'dependabot[bot]'))
- shell: pwsh
- env:
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
- DEEPSEEK_BASE_URL: https://api.deepseek.com
- run: |
- $blackboxRoot = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-live-"))').Trim()
- Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
- Remove-Item Env:DSH_RUNTIME_MODE -ErrorAction SilentlyContinue
- Push-Location $blackboxRoot
- try {
- & "${{ steps.smoke-venv-windows.outputs.python }}" "$env:GITHUB_WORKSPACE\scripts\smoke-python-runtime.py" --scenario sdk-live --installed-wheel
- if ($LASTEXITCODE -ne 0) { throw "Installed-wheel live API smoke failed with exit code $LASTEXITCODE" }
- } finally {
- Pop-Location
- }
- - name: Check Linux GLIBC requirements
- if: runner.os == 'Linux'
- run: |
- set -euo pipefail
- readelf --version-info "${{ steps.runtime-posix.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: Check macOS payload architecture and deployment target
- if: runner.os == 'macOS'
- env:
- EXE: ${{ steps.runtime-posix.outputs.exe }}
- PLATFORM: ${{ steps.runtime-posix.outputs.platform }}
- run: |
- set -euo pipefail
- case "$PLATFORM" in
- macos-arm64) macho_arch=arm64 ;;
- macos-x64) macho_arch=x86_64 ;;
- *) echo "::error::Unsupported macOS platform $PLATFORM"; exit 1 ;;
- esac
- for payload in "$EXE" "$EXE-rg" "$EXE-spawn-helper"; do
- lipo "$payload" -verify_arch "$macho_arch"
- done
- python3 scripts/check-macos-deployment-target.py \
- --platform "$PLATFORM" "$EXE" "$EXE-rg" "$EXE-spawn-helper"
- - name: Run wheel in a manylinux 2.28 container
- if: runner.os == 'Linux'
- env:
- RUNNER_ARCH: ${{ runner.arch }}
- RUNTIME_WHEEL: ${{ steps.runtime-posix.outputs.wheel }}
- SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
- 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 RUNTIME_WHEEL -e SDK_WHEEL -e DSH_TELEMETRY_DISABLED -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 "/work/dist-python/$SDK_WHEEL" "/work/dist-python/$RUNTIME_WHEEL"
- mkdir -p /tmp/dsh-sdk-manylinux-smoke
- cd /tmp/dsh-sdk-manylinux-smoke
- env -u PYTHONPATH -u DSH_RUNTIME_MODE /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default --installed-wheel
- env -u PYTHONPATH -u DSH_RUNTIME_MODE /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-mcp --installed-wheel
- '
- - uses: actions/upload-artifact@v7
- with:
- name: ${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }}
- path: dist-python/${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }}
- if-no-files-found: error
- retention-days: 7
|