mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
Make the zero-code dsh-python-runtime-closure depend on the real @deepseek-ai/dsh application and every required profile peer, then package apps/cli's built bin instead of the deleted Python carrier. Rename executables to deepseek-harness-sdk-runtime-<platform>-<arch>, update wheel/platform/build workflow discovery, and install a Python dsh console command that requires explicit DSH_HOME before exec. Include profile, bundle, preset, native addon, and shared-library assets needed by the full CLI. Remove the checked-in default cordis.yml and preserve the existing wheel distribution names, Python module names, sidecar validation, and wire identity. Runtime resolution and release tests pin the new artifacts and dev Node carrier.
369 lines
15 KiB
YAML
369 lines
15 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, label a PR `build-exe`
|
|
# (remove and reapply to rerun), or call it from the Python release workflow.
|
|
# 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 three.
|
|
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.
|
|
Empty builds all three.
|
|
type: string
|
|
required: false
|
|
default: ''
|
|
pull_request:
|
|
types: [labeled]
|
|
|
|
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' || github.event.label.name == 'build-exe'
|
|
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:
|
|
# 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
|
|
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
|
|
|
|
# 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)"
|
|
(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 "$HOME/setup-pnpm:$HOME/setup-pnpm: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
|
|
id: runtime
|
|
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"
|
|
[ -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_14_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: 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 local SDK and runtime wheels into a clean venv
|
|
env:
|
|
RUNTIME_WHEEL: ${{ steps.runtime.outputs.wheel }}
|
|
SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
|
|
run: |
|
|
set -euo pipefail
|
|
python -m venv "$RUNNER_TEMP/dsh-sdk-smoke"
|
|
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" -m pip install \
|
|
"dist-python/$SDK_WHEEL" \
|
|
"dist-python/$RUNTIME_WHEEL"
|
|
|
|
- name: Run installed-wheel keyless black-box tests
|
|
run: |
|
|
set -euo pipefail
|
|
blackbox_root="$RUNNER_TEMP/dsh-sdk-blackbox"
|
|
mkdir -p "$blackbox_root"
|
|
cd "$blackbox_root"
|
|
env -u PYTHONPATH -u DSH_RUNTIME_MODE \
|
|
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" \
|
|
"$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
|
|
--scenario all \
|
|
--installed-wheel
|
|
|
|
- name: Preflight installed-wheel real API test
|
|
if: >-
|
|
inputs.ci
|
|
&& (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: Run installed-wheel real API black-box test
|
|
if: >-
|
|
inputs.ci
|
|
&& (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="$RUNNER_TEMP/dsh-sdk-blackbox-live"
|
|
mkdir -p "$blackbox_root"
|
|
cd "$blackbox_root"
|
|
env -u PYTHONPATH -u DSH_RUNTIME_MODE \
|
|
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" \
|
|
"$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
|
|
--scenario sdk-live \
|
|
--installed-wheel
|
|
|
|
- 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: Check macOS deployment target
|
|
if: runner.os == 'macOS'
|
|
env:
|
|
EXE: ${{ steps.runtime.outputs.exe }}
|
|
run: >-
|
|
python3 scripts/check-macos-deployment-target.py
|
|
"$EXE" "$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.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.outputs.wheel }}
|
|
path: dist-python/${{ steps.runtime.outputs.wheel }}
|
|
if-no-files-found: error
|
|
retention-days: 7
|