build-exe-for-python-sdk.yml 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. name: Build single-exe
  2. # Native builds for the release targets; see
  3. # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md.
  4. # A full target run retains one SDK wheel and five runtime wheels; subset
  5. # dispatch retains the SDK wheel and selected runtime wheels. Bare executables
  6. # and source closures are test inputs. Run manually or call it from the Python
  7. # release workflow. There is no `pull_request` trigger: a label trigger would
  8. # list gray skipped checks on every unrelated PR label event. Checkout uses the
  9. # triggering ref, so dispatch needs no separate ref input.
  10. on:
  11. workflow_call:
  12. inputs:
  13. targets:
  14. description: Comma-separated pkg targets to build; empty builds all five.
  15. type: string
  16. required: false
  17. default: ''
  18. release:
  19. description: Run as the native builder for the Python release workflow.
  20. type: boolean
  21. required: false
  22. default: false
  23. ci:
  24. description: Run Python runtime CI validation for the selected targets.
  25. type: boolean
  26. required: false
  27. default: false
  28. secrets:
  29. DEEPSEEK_API_KEY_EXTERNAL:
  30. description: Real DeepSeek API key for trusted installed-wheel pull-request tests.
  31. required: false
  32. workflow_dispatch:
  33. inputs:
  34. targets:
  35. description: >-
  36. Comma-separated pkg targets to build. Any subset of:
  37. node24-linux-x64, node24-linux-arm64, node24-macos-arm64,
  38. node24-macos-x64, node24-win-x64. Empty builds all five.
  39. type: string
  40. required: false
  41. default: ''
  42. concurrency:
  43. # Keep the called workflow distinct from its caller's concurrency group;
  44. # github.workflow identifies the caller inside a reusable workflow and keeps
  45. # an ordinary CI run from cancelling a full release validation on the same ref.
  46. group: build-single-exe-${{ github.workflow }}-${{ github.ref }}
  47. # Release-owned builds are part of an intentional publication transaction.
  48. cancel-in-progress: ${{ !inputs.release }}
  49. permissions:
  50. contents: read
  51. env:
  52. # CI runs must never report to the production telemetry endpoint baked
  53. # into apps/cli/cordis.yml (AppCLIEntry disables the row when set).
  54. DSH_TELEMETRY_DISABLED: '1'
  55. jobs:
  56. # Job-level conditions cannot inspect `matrix`, so validate target names and
  57. # construct the matrix before the dependent jobs.
  58. plan:
  59. name: plan targets
  60. if: inputs.ci || inputs.release || github.event_name == 'workflow_dispatch'
  61. runs-on: >-
  62. ${{ inputs.ci && !inputs.release && vars.DSH_CI_FAILOVER_LINUX == 'blacksmith'
  63. && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
  64. timeout-minutes: 5
  65. outputs:
  66. matrix: ${{ steps.plan.outputs.matrix }}
  67. version: ${{ steps.version.outputs.version }}
  68. repository-version: ${{ steps.version.outputs.repository-version }}
  69. steps:
  70. - uses: actions/checkout@v6
  71. - name: Resolve repository version
  72. id: version
  73. run: |
  74. set -euo pipefail
  75. python3 - <<'PY' >> "$GITHUB_OUTPUT"
  76. import runpy
  77. release = runpy.run_path("scripts/build-python-release.py")
  78. repository_version = release["repository_version"]()
  79. wheel_version = release["pep440_version"](repository_version)
  80. print(f"repository-version={repository_version}")
  81. print(f"version={wheel_version}")
  82. PY
  83. - name: Compute matrix from targets input
  84. id: plan
  85. env:
  86. # Blank dispatch inputs build all targets.
  87. TARGETS: ${{ inputs.targets || 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64' }}
  88. run: |
  89. set -euo pipefail
  90. matrix='[]'
  91. IFS=',' read -r -a targets <<< "$TARGETS"
  92. for raw in "${targets[@]}"; do
  93. t="$(echo "$raw" | xargs)" # trim surrounding whitespace
  94. [ -z "$t" ] && continue
  95. # Native-only: hosted arm64 Linux uses ubuntu-24.04-arm, while
  96. # macos-latest is Apple Silicon; macos-15-intel is native x64.
  97. case "$t" in
  98. node24-linux-x64) runner=ubuntu-latest ;;
  99. node24-linux-arm64) runner=ubuntu-24.04-arm ;;
  100. node24-macos-arm64) runner=macos-latest ;;
  101. node24-macos-x64) runner=macos-15-intel ;;
  102. node24-win-x64) runner=windows-2025 ;;
  103. *)
  104. echo "::error::Unknown target '$t'. Supported: node24-linux-x64, node24-linux-arm64, node24-macos-arm64, node24-macos-x64, node24-win-x64."
  105. exit 1
  106. ;;
  107. esac
  108. matrix="$(jq -c --arg target "$t" --arg runner "$runner" '. + [{target: $target, runner: $runner}]' <<< "$matrix")"
  109. done
  110. if [ "$matrix" = '[]' ]; then
  111. echo "::error::The targets input selected nothing to build."
  112. exit 1
  113. fi
  114. echo "Matrix: $matrix"
  115. echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
  116. sdk-wheel:
  117. needs: plan
  118. name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
  119. runs-on: >-
  120. ${{ inputs.ci && !inputs.release && vars.DSH_CI_FAILOVER_LINUX == 'blacksmith'
  121. && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
  122. timeout-minutes: 5
  123. steps:
  124. - uses: actions/checkout@v6
  125. - uses: actions/setup-python@v6.3.0
  126. with:
  127. python-version: '3.10'
  128. - name: Install Python build tooling
  129. run: python -m pip install uv==0.11.23
  130. - name: Build release-shaped SDK wheel
  131. run: >-
  132. python scripts/build-python-release.py
  133. --package sdk
  134. --output-dir dist-python
  135. - uses: actions/upload-artifact@v7
  136. with:
  137. name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
  138. path: dist-python/deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
  139. if-no-files-found: error
  140. retention-days: 7
  141. build:
  142. needs: [plan, sdk-wheel]
  143. name: ${{ matrix.target }}
  144. runs-on: >-
  145. ${{ inputs.ci && !inputs.release && matrix.target == 'node24-linux-x64'
  146. && vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-16vcpu-ubuntu-2404'
  147. || inputs.ci && !inputs.release && matrix.target == 'node24-win-x64'
  148. && vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-16vcpu-windows-2025'
  149. || matrix.runner }}
  150. timeout-minutes: 45
  151. strategy:
  152. fail-fast: false
  153. matrix:
  154. include: ${{ fromJSON(needs.plan.outputs.matrix) }}
  155. steps:
  156. - uses: actions/checkout@v6
  157. with:
  158. persist-credentials: false
  159. - uses: pnpm/action-setup@v4
  160. with:
  161. dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
  162. - name: Enable Windows Developer Mode (symlink support)
  163. if: runner.os == 'Windows'
  164. shell: pwsh
  165. run: >-
  166. reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
  167. /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
  168. # setup-node's built-in pnpm store cache keys on platform AND arch, so
  169. # the Linux architectures sharing runner.os stay on separate caches.
  170. - uses: actions/setup-node@v6
  171. with:
  172. node-version: 24
  173. cache: pnpm
  174. - uses: actions/setup-python@v6.3.0
  175. with:
  176. python-version: '3.10'
  177. - name: Install Python build tooling
  178. run: python -m pip install uv==0.11.23
  179. # Cache pkg's target Node binary; lockfile changes roll the
  180. # exact key while the restore prefix can seed its replacement.
  181. - uses: actions/cache@v4
  182. with:
  183. path: ~/.pkg-cache
  184. key: pkg-fetch-${{ matrix.target }}-${{ hashFiles('pnpm-lock.yaml') }}
  185. restore-keys: |
  186. pkg-fetch-${{ matrix.target }}-
  187. - name: Install (immutable)
  188. # node-gyp fetches Node headers from nodejs.org during install; that
  189. # endpoint stalls intermittently (observed 10 s connect timeouts).
  190. # Bounded retries mirror the wine lane's transfer policy without a
  191. # mirror, since these installs also resolve native addons.
  192. shell: bash
  193. run: |
  194. for attempt in 1 2 3; do
  195. if pnpm install --frozen-lockfile; then exit 0; fi
  196. [ "$attempt" -lt 3 ] && sleep 10
  197. done
  198. exit 1
  199. - name: Rebuild Linux node-pty against manylinux 2.28
  200. if: runner.os == 'Linux'
  201. env:
  202. RUNNER_ARCH: ${{ runner.arch }}
  203. run: |
  204. set -euo pipefail
  205. case "$RUNNER_ARCH" in
  206. X64) image=quay.io/pypa/manylinux_2_28_x86_64 ;;
  207. ARM64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
  208. *) echo "::error::Unsupported Linux runner architecture $RUNNER_ARCH"; exit 1 ;;
  209. esac
  210. addon_dir="$(realpath packages/subprocess/subprocess-local/node_modules/node-pty)"
  211. pnpm_setup_root="$(realpath "$(dirname "$(dirname "$PNPM_HOME")")")"
  212. (cd "$addon_dir" && npm_config_build_from_source=true pnpm run install)
  213. addon="$addon_dir/build/Release/pty.node"
  214. [ -f "$addon_dir/build/Makefile" ] || {
  215. echo "::error::node-pty install did not generate $addon_dir/build/Makefile"
  216. exit 1
  217. }
  218. docker run --rm \
  219. --user "$(id -u):$(id -g)" \
  220. -v "$PWD:$PWD" \
  221. -v "$HOME/.cache/node-gyp:$HOME/.cache/node-gyp:ro" \
  222. -v "$pnpm_setup_root:$pnpm_setup_root:ro" \
  223. -w "$addon_dir" \
  224. "$image" \
  225. bash -euxo pipefail -c \
  226. 'rm -rf build/Release && make -C build -j2 BUILDTYPE=Release'
  227. [ -f "$addon" ] || { echo "::error::$addon missing after manylinux rebuild"; exit 1; }
  228. readelf --version-info "$addon" | tee node-pty-glibc-versions.txt
  229. maximum="$(sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' node-pty-glibc-versions.txt | sort -V | tail -1)"
  230. [ -n "$maximum" ] || { echo "::error::No GLIBC requirements found in $addon"; exit 1; }
  231. dpkg --compare-versions "$maximum" le 2.28 || {
  232. echo "::error::node-pty addon requires GLIBC_$maximum but wheel claims manylinux_2_28"
  233. exit 1
  234. }
  235. - name: Build single-exe
  236. env:
  237. DSH_BUILD_CLIENT_PROFILE: official
  238. run: pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }}
  239. - name: Resolve platform outputs (POSIX)
  240. id: runtime-posix
  241. if: runner.os != 'Windows'
  242. env:
  243. TARGET: ${{ matrix.target }}
  244. VERSION: ${{ needs.plan.outputs.version }}
  245. run: |
  246. set -euo pipefail
  247. platform="${TARGET#node24-}"
  248. exe="$PWD/dist-exe/deepseek-harness-sdk-runtime-$platform"
  249. case "$platform" in
  250. linux-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl ;;
  251. linux-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl ;;
  252. macos-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl ;;
  253. macos-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_x86_64.whl ;;
  254. *) echo "::error::Unsupported runtime platform $platform"; exit 1 ;;
  255. esac
  256. [ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
  257. echo "platform=$platform" >> "$GITHUB_OUTPUT"
  258. echo "exe=$exe" >> "$GITHUB_OUTPUT"
  259. echo "wheel=$wheel" >> "$GITHUB_OUTPUT"
  260. - name: Resolve platform outputs (Windows)
  261. id: runtime-windows
  262. if: runner.os == 'Windows'
  263. shell: pwsh
  264. env:
  265. TARGET: ${{ matrix.target }}
  266. VERSION: ${{ needs.plan.outputs.version }}
  267. run: |
  268. if ($env:TARGET -ne 'node24-win-x64') { throw "Unsupported runtime target $env:TARGET" }
  269. $platform = 'win-x64'
  270. $exe = Join-Path $PWD 'dist-exe\deepseek-harness-sdk-runtime-win-x64.exe'
  271. $wheel = "deepseek_harness_runtime_bin-$env:VERSION-py3-none-win_amd64.whl"
  272. if (-not (Test-Path -LiteralPath $exe -PathType Leaf)) { throw "Runtime executable is missing at $exe" }
  273. "platform=$platform" >> $env:GITHUB_OUTPUT
  274. "exe=$exe" >> $env:GITHUB_OUTPUT
  275. "wheel=$wheel" >> $env:GITHUB_OUTPUT
  276. - name: Build release-shaped runtime wheel
  277. run: >-
  278. python scripts/build-python-release.py
  279. --package runtime
  280. --platform "${{ steps.runtime-posix.outputs.platform || steps.runtime-windows.outputs.platform }}"
  281. --runtime-exe "${{ steps.runtime-posix.outputs.exe || steps.runtime-windows.outputs.exe }}"
  282. --output-dir dist-python
  283. - uses: actions/download-artifact@v8
  284. with:
  285. name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
  286. path: dist-python
  287. - name: Install local SDK and runtime wheels into a clean venv (POSIX)
  288. id: smoke-venv-posix
  289. if: runner.os != 'Windows'
  290. env:
  291. RUNTIME_WHEEL: ${{ steps.runtime-posix.outputs.wheel }}
  292. SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
  293. run: |
  294. set -euo pipefail
  295. venv="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-smoke-"))')"
  296. python -m venv "$venv"
  297. smoke_python="$venv/bin/python"
  298. "$smoke_python" -m pip install \
  299. "dist-python/$SDK_WHEEL" \
  300. "dist-python/$RUNTIME_WHEEL"
  301. echo "python=$smoke_python" >> "$GITHUB_OUTPUT"
  302. - name: Install local SDK and runtime wheels into a clean venv (Windows)
  303. id: smoke-venv-windows
  304. if: runner.os == 'Windows'
  305. shell: pwsh
  306. env:
  307. RUNTIME_WHEEL: ${{ steps.runtime-windows.outputs.wheel }}
  308. SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
  309. run: |
  310. $venv = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-smoke-"))').Trim()
  311. python -m venv $venv
  312. $smokePython = Join-Path $venv 'Scripts\python.exe'
  313. & $smokePython -m pip install "dist-python/$env:SDK_WHEEL" "dist-python/$env:RUNTIME_WHEEL"
  314. if ($LASTEXITCODE -ne 0) { throw "Wheel installation failed with exit code $LASTEXITCODE" }
  315. "python=$smokePython" >> $env:GITHUB_OUTPUT
  316. - name: Run installed-wheel keyless black-box tests (POSIX)
  317. if: runner.os != 'Windows'
  318. run: |
  319. set -euo pipefail
  320. blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-"))')"
  321. cd "$blackbox_root"
  322. env -u PYTHONPATH -u DSH_RUNTIME_MODE \
  323. "${{ steps.smoke-venv-posix.outputs.python }}" \
  324. "$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
  325. --scenario all \
  326. --installed-wheel
  327. - name: Run installed-wheel keyless black-box tests (Windows)
  328. if: runner.os == 'Windows'
  329. shell: pwsh
  330. run: |
  331. $blackboxRoot = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-"))').Trim()
  332. Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
  333. Remove-Item Env:DSH_RUNTIME_MODE -ErrorAction SilentlyContinue
  334. Push-Location $blackboxRoot
  335. try {
  336. & "${{ steps.smoke-venv-windows.outputs.python }}" "$env:GITHUB_WORKSPACE\scripts\smoke-python-runtime.py" --scenario all --installed-wheel
  337. if ($LASTEXITCODE -ne 0) { throw "Installed-wheel black-box failed with exit code $LASTEXITCODE" }
  338. } finally {
  339. Pop-Location
  340. }
  341. - name: Preflight installed-wheel real API test (POSIX)
  342. if: >-
  343. inputs.ci
  344. && runner.os != 'Windows'
  345. && (github.event_name != 'pull_request'
  346. || !(github.event.pull_request.head.repo.fork
  347. || github.event.pull_request.user.login == 'dependabot[bot]'))
  348. env:
  349. DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
  350. run: |
  351. set -euo pipefail
  352. if [ -z "${DEEPSEEK_API_KEY:-}" ]; then
  353. echo "::error::DEEPSEEK_API_KEY_EXTERNAL is empty; the installed-wheel real API test cannot self-skip."
  354. exit 1
  355. fi
  356. - name: Preflight installed-wheel real API test (Windows)
  357. if: >-
  358. inputs.ci
  359. && runner.os == 'Windows'
  360. && (github.event_name != 'pull_request'
  361. || !(github.event.pull_request.head.repo.fork
  362. || github.event.pull_request.user.login == 'dependabot[bot]'))
  363. shell: pwsh
  364. env:
  365. DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
  366. run: |
  367. if ([string]::IsNullOrWhiteSpace($env:DEEPSEEK_API_KEY)) {
  368. throw 'DEEPSEEK_API_KEY_EXTERNAL is empty; the installed-wheel real API test cannot self-skip.'
  369. }
  370. - name: Run installed-wheel real API black-box test (POSIX)
  371. if: >-
  372. inputs.ci
  373. && runner.os != 'Windows'
  374. && (github.event_name != 'pull_request'
  375. || !(github.event.pull_request.head.repo.fork
  376. || github.event.pull_request.user.login == 'dependabot[bot]'))
  377. env:
  378. DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
  379. DEEPSEEK_BASE_URL: https://api.deepseek.com/anthropic
  380. run: |
  381. set -euo pipefail
  382. blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-live-"))')"
  383. cd "$blackbox_root"
  384. env -u PYTHONPATH -u DSH_RUNTIME_MODE \
  385. "${{ steps.smoke-venv-posix.outputs.python }}" \
  386. "$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
  387. --scenario sdk-live \
  388. --installed-wheel
  389. - name: Run installed-wheel real API black-box test (Windows)
  390. if: >-
  391. inputs.ci
  392. && runner.os == 'Windows'
  393. && (github.event_name != 'pull_request'
  394. || !(github.event.pull_request.head.repo.fork
  395. || github.event.pull_request.user.login == 'dependabot[bot]'))
  396. shell: pwsh
  397. env:
  398. DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
  399. DEEPSEEK_BASE_URL: https://api.deepseek.com/anthropic
  400. run: |
  401. $blackboxRoot = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-live-"))').Trim()
  402. Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
  403. Remove-Item Env:DSH_RUNTIME_MODE -ErrorAction SilentlyContinue
  404. Push-Location $blackboxRoot
  405. try {
  406. & "${{ steps.smoke-venv-windows.outputs.python }}" "$env:GITHUB_WORKSPACE\scripts\smoke-python-runtime.py" --scenario sdk-live --installed-wheel
  407. if ($LASTEXITCODE -ne 0) { throw "Installed-wheel live API smoke failed with exit code $LASTEXITCODE" }
  408. } finally {
  409. Pop-Location
  410. }
  411. - name: Check Linux GLIBC requirements
  412. if: runner.os == 'Linux'
  413. run: |
  414. set -euo pipefail
  415. readelf --version-info "${{ steps.runtime-posix.outputs.exe }}" | tee glibc-versions.txt
  416. maximum="$(sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' glibc-versions.txt | sort -V | tail -1)"
  417. [ -n "$maximum" ] || { echo "::error::No GLIBC requirements found"; exit 1; }
  418. dpkg --compare-versions "$maximum" le 2.28 || {
  419. echo "::error::Executable requires GLIBC_$maximum but wheel claims manylinux_2_28"
  420. exit 1
  421. }
  422. - name: Check macOS payload architecture and deployment target
  423. if: runner.os == 'macOS'
  424. env:
  425. EXE: ${{ steps.runtime-posix.outputs.exe }}
  426. PLATFORM: ${{ steps.runtime-posix.outputs.platform }}
  427. run: |
  428. set -euo pipefail
  429. case "$PLATFORM" in
  430. macos-arm64) macho_arch=arm64 ;;
  431. macos-x64) macho_arch=x86_64 ;;
  432. *) echo "::error::Unsupported macOS platform $PLATFORM"; exit 1 ;;
  433. esac
  434. for payload in "$EXE" "$EXE-rg" "$EXE-spawn-helper"; do
  435. lipo "$payload" -verify_arch "$macho_arch"
  436. done
  437. python3 scripts/check-macos-deployment-target.py \
  438. --platform "$PLATFORM" "$EXE" "$EXE-rg" "$EXE-spawn-helper"
  439. - name: Run wheel in a manylinux 2.28 container
  440. if: runner.os == 'Linux'
  441. env:
  442. RUNNER_ARCH: ${{ runner.arch }}
  443. RUNTIME_WHEEL: ${{ steps.runtime-posix.outputs.wheel }}
  444. SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
  445. run: |
  446. set -euo pipefail
  447. case "$RUNNER_ARCH" in
  448. X64) image=quay.io/pypa/manylinux_2_28_x86_64 ;;
  449. ARM64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
  450. *) echo "::error::Unsupported Linux runner architecture $RUNNER_ARCH"; exit 1 ;;
  451. esac
  452. docker run --rm -e RUNTIME_WHEEL -e SDK_WHEEL -e DSH_TELEMETRY_DISABLED -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c '
  453. /opt/python/cp310-cp310/bin/python -m venv /tmp/dsh-sdk
  454. /tmp/dsh-sdk/bin/python -m pip install "/work/dist-python/$SDK_WHEEL" "/work/dist-python/$RUNTIME_WHEEL"
  455. mkdir -p /tmp/dsh-sdk-manylinux-smoke
  456. cd /tmp/dsh-sdk-manylinux-smoke
  457. env -u PYTHONPATH -u DSH_RUNTIME_MODE /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default --installed-wheel
  458. env -u PYTHONPATH -u DSH_RUNTIME_MODE /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-mcp --installed-wheel
  459. '
  460. - uses: actions/upload-artifact@v7
  461. with:
  462. name: ${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }}
  463. path: dist-python/${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }}
  464. if-no-files-found: error
  465. retention-days: 7