mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(build): stage platform-specific PTY artifacts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
2026-07-10-single-file-executable-sdk-runtime-distribution.md: 39cfb2999dea7767a18702ad7d160c9e88d7bf20
|
||||
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: e1a21c40647e1418d4afd02c0bc6b44ef0d4a8cf
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
|
||||
2026-07-10-single-file-executable-sdk-runtime-distribution.md: fac3d9527b496adaaffdbd8e78401a17c6f0cc0b
|
||||
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: ff6a8be16c5f8591efa9bf383cd47c8fe251fe39
|
||||
|
||||
@@ -40,15 +40,15 @@ The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-ru
|
||||
|
||||
### Build pipeline and artifacts
|
||||
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/` → inject the pkg configuration (`bin` points at `node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js` inside the closure, `assets` is a full glob — dynamic import is invisible to pkg's static analysis, so everything must be packed in explicitly) → one `pkg --sea` per target → the executables `dsh-jsonrpc-agent-pkg-<platform>-<arch>` land in `dist-exe/` and are copied back into the runtime directory. CI treats them as intermediate test inputs and retains their platform wheels. All four deploy flags are grounded in measurement: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted yields a zero-symlink file tree (most stable for the pkg VFS, physically guaranteeing a single cordis instance); disabling automatic peer installation keeps unpublished package names from triggering registry resolution; link-workspace-packages points the closure at workspace/vendor sources.
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/` → inject the pkg configuration (`bin` points at `node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js` inside the closure, `assets` is a full glob — dynamic import is invisible to pkg's static analysis, so everything must be packed in explicitly) → stage the target `node-pty` addon → one `pkg --sea` per target → the executables `dsh-jsonrpc-agent-pkg-<platform>-<arch>` land in `dist-exe/` and are copied back into the runtime directory. Linux installs build `pty.node` from source, so the builder copies it from the root install into the staged closure because legacy deploy omits that side-effect directory; macOS uses its target prebuild and emits the required `-spawn-helper` beside the executable. CI treats these products as intermediate test inputs and retains their platform wheels. All four deploy flags are grounded in measurement: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted yields a zero-symlink file tree (most stable for the pkg VFS, physically guaranteeing a single cordis instance); disabling automatic peer installation keeps unpublished package names from triggering registry resolution; link-workspace-packages points the closure at workspace/vendor sources.
|
||||
|
||||
CI: [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml), triggered explicitly only — `workflow_dispatch`, or the `build-exe` label on a pull request; native builds on the three platforms linux-x64 / linux-arm64 (`ubuntu-24.04-arm`) / macos-arm64, with `~/.pkg-cache` cached; macOS ad-hoc signing is handled by pkg. Each leg drives a mock SSE model through the SDK with the default config and a custom `cordis.yml`, drives the exe directly over NDJSON JSON-RPC, verifies the JSONL and final response, and installs release-shaped wheels into a clean venv without `runtime_bin`; Linux additionally inspects GLIBC requirements and runs in a manylinux 2.28 container. A full three-target run retains four artifacts, each containing one release file: the platform-independent SDK wheel and three native runtime wheels; a subset dispatch retains the SDK wheel and selected runtime wheels. Bare executables and source bundles remain intermediate test inputs. [`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) accepts only `python-vX.Y.Z` tag pipelines whose version matches the root `package.json`, builds one SDK wheel and three native runtime wheels, then a single serialized job checks and publishes all four to the project PyPI registry. Windows is a non-goal.
|
||||
|
||||
### Python SDK distribution: two carriers, exe for production, node for development
|
||||
|
||||
The Python SDK lives at [`python/`](../../../../python/README.md): `python/sdk` (the client) + `python/sdk-runtime` (the runtime carrier package). The runtime package's data directory holds three kinds of content: the checked-in default `runtime/cordis.yml`, the build-injected platform exe, and the build-injected `runtime/node/` closure tree. `resolve_bundled_launch_args()` automatic resolution **finds the exe only**; the node carrier is enabled only by an explicit `DSH_RUNTIME_MODE=node` (running `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js`, requiring a system node ≥22.19), positioned as the development-verification channel for members of this repo, and does not enter wheel distributions.
|
||||
The Python SDK lives at [`python/`](../../../../python/README.md): `python/sdk` (the client) + `python/sdk-runtime` (the runtime carrier package). The runtime package's data directory holds the checked-in default `runtime/cordis.yml`, the build-injected platform exe and optional helper, and the build-injected `runtime/node/` closure tree. `resolve_bundled_launch_args()` automatic resolution **finds the exe only**; the node carrier is enabled only by an explicit `DSH_RUNTIME_MODE=node` (running `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js`, requiring a system node ≥22.19), positioned as the development-verification channel for members of this repo, and does not enter wheel distributions.
|
||||
|
||||
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) reads the authoritative stable `X.Y.Z` from the repository root `package.json` and stages both packages at that version, with the SDK depending exactly on `deepseek-harness-runtime-bin==X.Y.Z`. An optional `python-vX.Y.Z` release tag is a consistency assertion and is rejected when it differs from the repository version; the source `pyproject.toml` development sentinel never determines a release version. The SDK is a `py3-none-any` wheel; the wheel-only runtime package contains exactly one exe and uses one of `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, or `py3-none-macosx_11_0_arm64`. Its Hatch hook rejects sdists, universal tags, mixed executable payloads, and unsupported platforms.
|
||||
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) reads the authoritative stable `X.Y.Z` from the repository root `package.json` and stages both packages at that version, with the SDK depending exactly on `deepseek-harness-runtime-bin==X.Y.Z`. An optional `python-vX.Y.Z` release tag is a consistency assertion and is rejected when it differs from the repository version; the source `pyproject.toml` development sentinel never determines a release version. The SDK is a `py3-none-any` wheel; each wheel-only runtime package contains one exe, and the macOS wheel also contains its architecture-matched helper. Runtime wheels use one of `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, or `py3-none-macosx_11_0_arm64`; the Hatch hook rejects sdists, universal tags, mixed-platform payloads, missing or extra helpers, and unsupported platforms.
|
||||
|
||||
The exe's "must be explicitly configured" hard semantic is unchanged; the zero-config experience is restored by the wrapper: when the caller gave no `cordis`, named no explicit runtime, and the environment has no `DSH_CORDIS_CONFIG`, the client explicitly injects the checked-in default `cordis.yml` (agent-core + preloaded llm-deepseek + JSONL persistence + bash-local + the `dsh-jsonrpc` serving entry, with `!!js` environment-variable fallbacks) via `DSH_CORDIS_CONFIG`.
|
||||
|
||||
|
||||
@@ -40,15 +40,15 @@ exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真
|
||||
|
||||
### 构建管线与产物
|
||||
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts):运行时闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **直接写入** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/` → 注入 pkg 配置(`bin` 指向闭包内的 `node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js`;`assets` 使用全量 glob,因为动态 `import()` 对 pkg 静态分析不可见,必须显式打入全部内容)→ 每个构建目标调用一次 `pkg --sea` → 可执行文件 `dsh-jsonrpc-agent-pkg-<platform>-<arch>` 写入 `dist-exe/`,并拷回运行时目录。CI 将这些文件作为测试中间输入,只保留对应平台的 wheel 包。四个部署标志都有实测依据:未启用 `inject-workspace-packages` 时必须使用 `--legacy`;`hoisted` 产出无符号链接的文件树(对 pkg VFS 最稳定,并从物理上保证只有一个 Cordis 实例);关闭对等依赖自动安装可避免未发布包名触发注册表解析;`link-workspace-packages` 让闭包指向工作区/vendor 源码。
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts):运行时闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-jsonrpc-agent-pkg deploy --legacy --prod --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **直接写入** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/` → 注入 pkg 配置(`bin` 指向闭包内的 `node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js`;`assets` 使用全量 glob,因为动态 `import()` 对 pkg 静态分析不可见,必须显式打入全部内容)→ 暂存目标平台的 `node-pty` addon → 每个构建目标调用一次 `pkg --sea` → 可执行文件 `dsh-jsonrpc-agent-pkg-<platform>-<arch>` 写入 `dist-exe/`,并拷回运行时目录。Linux 安装会从源码构建 `pty.node`,而 `--legacy` 部署会省略该副作用目录,因此构建器会把它从根安装目录复制到暂存闭包;macOS 使用对应目标的预构建产物,并在可执行文件旁生成所需的 `-spawn-helper`。CI 将这些产物作为测试中间输入,只保留对应平台的 wheel 包。四个部署标志都有实测依据:未启用 `inject-workspace-packages` 时必须使用 `--legacy`;`hoisted` 产出无符号链接的文件树(对 pkg VFS 最稳定,并从物理上保证只有一个 Cordis 实例);关闭对等依赖自动安装可避免未发布包名触发注册表解析;`link-workspace-packages` 让闭包指向工作区/vendor 源码。
|
||||
|
||||
CI 使用 [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml),且只允许显式触发:手动派发 `workflow_dispatch`,或给 PR 添加 `build-exe` 标签。linux-x64、linux-arm64(`ubuntu-24.04-arm`)和 macos-arm64 三个平台分别进行原生构建,并缓存 `~/.pkg-cache`;macOS 的 ad-hoc 签名由 pkg 处理。每个平台都使用模拟 SSE 模型,分别通过默认配置和自定义 `cordis.yml` 驱动 SDK,再通过 NDJSON JSON-RPC 直接驱动 exe,校验 JSONL 与最终响应;最后把发布形态的 wheel 包安装到干净的 venv 中,并在不传 `runtime_bin` 的情况下运行。Linux 还会检查 GLIBC 依赖,并在 manylinux 2.28 容器中运行。完整构建三个目标时保留 4 个产物,每个产物只含一个发布文件:平台无关的 SDK wheel 包与 3 个原生运行时 wheel 包;手动选择部分目标时保留 SDK wheel 与所选运行时 wheel。裸 exe 与源码包只作为测试中间输入。[`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) 只接受版本与根目录 `package.json` 匹配的 `python-vX.Y.Z` 标签流水线,构建一个 SDK wheel 包和 3 个原生运行时 wheel 包,再由单个串行任务校验并将这 4 个文件发布到项目的 PyPI 注册表。Windows 不在目标范围内。
|
||||
|
||||
### Python SDK 分发:双载体,exe 用于生产,`node` 用于开发
|
||||
|
||||
Python SDK 位于 [`python/`](../../../../python/README.md):`python/sdk` 是客户端,`python/sdk-runtime` 是运行时载体包。运行时包的数据目录包含三类内容:检入的默认 `runtime/cordis.yml`、构建注入的平台 exe,以及构建注入的 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 的自动解析**只查找 exe**;`node` 载体仅在显式设置 `DSH_RUNTIME_MODE=node` 时启用(运行 `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js`,需要系统 Node ≥22.19),定位为本仓库成员的开发验证通道,不随 wheel 包分发。
|
||||
Python SDK 位于 [`python/`](../../../../python/README.md):`python/sdk` 是客户端,`python/sdk-runtime` 是运行时载体包。运行时包的数据目录包含检入的默认 `runtime/cordis.yml`、构建注入的平台 exe 与可选 helper,以及构建注入的 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 的自动解析**只查找 exe**;`node` 载体仅在显式设置 `DSH_RUNTIME_MODE=node` 时启用(运行 `runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js`,需要系统 Node ≥22.19),定位为本仓库成员的开发验证通道,不随 wheel 包分发。
|
||||
|
||||
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) 从仓库根目录的 `package.json` 读取权威的稳定版本 `X.Y.Z`,以该版本暂存两个包,并让 SDK 精确依赖 `deepseek-harness-runtime-bin==X.Y.Z`。可选的 `python-vX.Y.Z` 发布标签只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `pyproject.toml` 中的开发占位版本从不决定发布版本。SDK 是 `py3-none-any` wheel 包;只提供 wheel 包的运行时包恰好包含一个 exe,标签为 `py3-none-manylinux_2_28_x86_64`、`py3-none-manylinux_2_28_aarch64` 或 `py3-none-macosx_11_0_arm64`。其 Hatch 钩子拒绝 sdist、通用标签、混合可执行载荷以及不支持的平台。
|
||||
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) 从仓库根目录的 `package.json` 读取权威的稳定版本 `X.Y.Z`,以该版本暂存两个包,并让 SDK 精确依赖 `deepseek-harness-runtime-bin==X.Y.Z`。可选的 `python-vX.Y.Z` 发布标签只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `pyproject.toml` 中的开发占位版本从不决定发布版本。SDK 是 `py3-none-any` wheel 包;每个只提供 wheel 包的运行时包都包含一个 exe,macOS wheel 包还包含与其架构匹配的 helper。运行时 wheel 包使用 `py3-none-manylinux_2_28_x86_64`、`py3-none-manylinux_2_28_aarch64` 或 `py3-none-macosx_11_0_arm64` 三种标签之一;Hatch 钩子拒绝 sdist、通用标签、混合平台载荷、helper 缺失或多余,以及不支持的平台。
|
||||
|
||||
exe“必须显式配置”的硬语义不变;零配置体验由包装层恢复:调用方没有提供 `cordis`、没有显式指定运行时,且环境中没有 `DSH_CORDIS_CONFIG` 时,客户端将检入的默认 `cordis.yml`(`agent-core` + 预载的 `llm-deepseek` + JSONL 持久化 + `bash-local` + `dsh-jsonrpc` 对外服务条目,并通过 `!!js` 使用环境变量兜底)显式注入 `DSH_CORDIS_CONFIG`。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-29-persistent-bash-str-replace-editor.md
|
||||
2026-07-29-persistent-bash-str-replace-editor.md: b1be9cc40e11b07b722877e666de0e0328636a05
|
||||
2026-07-29-persistent-bash-str-replace-editor.zh.md: 0bf100514e971dfa759f44dcca15a7ad6a2fdd8a
|
||||
2026-07-29-persistent-bash-str-replace-editor.md: 6e8a1df7f04340f4a97c0b799aaace0a78526ba5
|
||||
2026-07-29-persistent-bash-str-replace-editor.zh.md: 256ffad7945cbb4367b9cc8bb92e9b2968ab4501
|
||||
|
||||
@@ -16,7 +16,7 @@ Some deployments need a one-call Bash schema whose shell state survives across m
|
||||
|
||||
`dsh-system-prompt` accepts `includeHarnessIdentity: false`, while `dsh-agent-spine-demo` forwards that setting and accepts `toolBash: false`. A deployment can therefore own an exact persona and replace the spine's native Bash without duplicate prompt or tool registrations. Existing defaults remain unchanged.
|
||||
|
||||
Both plugins are included in the Python runtime closure. The persistent Bash closure also includes the PTY service/local backend and the sandbox services required by that backend. Because `node-pty` executes a native `spawn-helper`, each packaged runtime executable ships with an architecture-matched `-spawn-helper` sibling. A pinned `node-pty` patch resolves that sibling only when present, preserving upstream lookup in ordinary Node runs. The explicit `DSH_NODE_PTY_SPAWN_HELPER` override remains for a current external consumer that supplies a non-sibling helper. The executable and runtime-wheel builders inspect ELF or thin Mach-O headers and fail before publication when the helper is absent, mismatched, or not executable.
|
||||
Both plugins are included in the Python runtime closure. The persistent Bash closure also includes the PTY service/local backend and the sandbox services required by that backend. Because `node-pty` executes a native `spawn-helper` on macOS, each packaged macOS runtime executable ships with an architecture-matched `-spawn-helper` sibling; Linux uses `forkpty` directly. A pinned `node-pty` patch resolves the sibling only when present, preserving upstream lookup in ordinary Node runs. The explicit `DSH_NODE_PTY_SPAWN_HELPER` override remains for a current external consumer that supplies a non-sibling helper. The macOS executable and runtime-wheel builders inspect the thin Mach-O header and fail before publication when the helper is absent, mismatched, or not executable.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -30,4 +30,4 @@ Both plugins are included in the Python runtime closure. The persistent Bash clo
|
||||
|
||||
## Consequences
|
||||
|
||||
Profiles can reproduce an external agent by configuring persona and descriptions while the underlying packages remain general. Persistent Bash requires an owning Agent and real PTY backend. Shell exit, timeout, or cancellation loses state. The editor delegates security and mutation policy to the mounted filesystem stack. Runtime-wheel consumers still need no Node installation, but the wheel now contains a main executable plus its private native helper rather than one physical file.
|
||||
Profiles can reproduce an external agent by configuring persona and descriptions while the underlying packages remain general. Persistent Bash requires an owning Agent and real PTY backend. Shell exit, timeout, or cancellation loses state. The editor delegates security and mutation policy to the mounted filesystem stack. Runtime-wheel consumers still need no Node installation; Linux wheels contain one executable, while macOS wheels also contain its private native helper.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
`dsh-system-prompt` 接受 `includeHarnessIdentity: false`;`dsh-agent-spine-demo` 会转发该设置,并接受 `toolBash: false`。因此部署可以拥有精确 persona,并替换 spine 的原生 Bash,而不会重复注册提示词或工具。既有默认值不变。
|
||||
|
||||
两个插件都进入 Python runtime 闭包。持久 Bash 的闭包还包含 PTY 服务/本地后端,以及该后端要求的沙箱服务。由于 `node-pty` 会执行原生 `spawn-helper`,每个打包后的运行时可执行文件都会携带一个架构匹配的 `-spawn-helper` 伴随文件。固定版本的 `node-pty` 补丁只在该伴随文件存在时解析它,普通 Node 运行仍保留上游查找方式。显式的 `DSH_NODE_PTY_SPAWN_HELPER` 覆盖仍予保留,供当前提供非伴随 helper 的外部消费方使用。可执行文件与运行时 wheel 包的构建器会检查 ELF 或 thin Mach-O 文件头;若 helper 缺失、架构不匹配或不可执行,构建会在发布前失败。
|
||||
两个插件都进入 Python runtime 闭包。持久 Bash 的闭包还包含 PTY 服务/本地后端,以及该后端要求的沙箱服务。由于 `node-pty` 在 macOS 上会执行原生 `spawn-helper`,每个打包后的 macOS 运行时可执行文件都会携带一个架构匹配的 `-spawn-helper` 伴随文件;Linux 直接使用 `forkpty`。固定版本的 `node-pty` 补丁只在该伴随文件存在时解析它,普通 Node 运行仍保留上游查找方式。显式的 `DSH_NODE_PTY_SPAWN_HELPER` 覆盖仍予保留,供当前提供非伴随 helper 的外部消费方使用。macOS 可执行文件与运行时 wheel 包的构建器会检查 thin Mach-O 文件头;若 helper 缺失、架构不匹配或不可执行,构建会在发布前失败。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -30,4 +30,4 @@
|
||||
|
||||
## 后果
|
||||
|
||||
Profile 可以通过配置 persona 和描述复现外部 Agent,而底层包保持通用。持久 Bash 需要拥有它的 Agent 与真实 PTY 后端;shell 退出、超时或取消会丢失状态。编辑器把安全与变更策略委托给挂载的文件系统栈。runtime wheel 的使用者仍不需要安装 Node,但 wheel 现在包含主可执行文件及其私有原生 helper,而不是单个物理文件。
|
||||
Profile 可以通过配置 persona 和描述复现外部 Agent,而底层包保持通用。持久 Bash 需要拥有它的 Agent 与真实 PTY 后端;shell 退出、超时或取消会丢失状态。编辑器把安全与变更策略委托给挂载的文件系统栈。运行时 wheel 包的消费方仍无需安装 Node;Linux wheel 包包含一个可执行文件,macOS wheel 包还包含其私有原生 helper。
|
||||
|
||||
@@ -40,7 +40,7 @@ allowBuilds:
|
||||
# JSONL durability calls MoveFileExW with write-through publication on Windows.
|
||||
koffi: true
|
||||
# The Python runtime deploy includes the reviewed workspace postinstall that
|
||||
# places node-pty's spawn helper beside the compiled PTY backend.
|
||||
# restores the executable bit on node-pty's macOS spawn helper.
|
||||
'@deepseek-ai/dsh-pty-local@file:packages/pty/pty-local': true
|
||||
|
||||
# The Landlock launcher family is our own sibling-repo release, consumed
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write python/sdk-runtime/README.md
|
||||
README.md: 0869b4a9dce0e261b168f21c90a80faf81ea4a64
|
||||
README.zh.md: 3f2342c4c66e24b8213c78d4e7530c3360497022
|
||||
README.md: 29ffc1dcc3ec3b273ccaee64734739f3c4f34b9c
|
||||
README.zh.md: d79c87090867c60e09c50016bad4797170b34400
|
||||
|
||||
@@ -8,7 +8,7 @@ Runtime carrier package for the Python SDK (dist `deepseek-harness-runtime-bin`,
|
||||
|
||||
Two carriers coexist under `src/deepseek_harness_runtime/runtime/`, both injected by the repo's `scripts/build-exe-for-python-sdk.ts` build and both gitignored:
|
||||
|
||||
- **exe (production)** — a single-file Node executable `dsh-jsonrpc-agent-pkg-<platform>-<arch>` plus its native `-spawn-helper` sibling (platform: `linux`/`macos`; arch: `x64`/`arm64`). The helper is required by `node-pty`; both files are built as one runtime product, and ELF or thin Mach-O headers must match the target. No Node installation is needed on the target machine. This is the only carrier that ships in wheel distributions; this package does not publish sdists.
|
||||
- **exe (production)** — a single-file Node executable `dsh-jsonrpc-agent-pkg-<platform>-<arch>` (platform: `linux`/`macos`; arch: `x64`/`arm64`). macOS builds also ship the native `-spawn-helper` sibling that `node-pty` uses there, and its thin Mach-O header must match the target. No Node installation is needed on the target machine. This is the only carrier that ships in wheel distributions; this package does not publish sdists.
|
||||
- **node (dev-only)** — the full deploy closure under `runtime/node/` (`package.json` + `node_modules/`), executed as `node runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js` on a system Node >= 22.19. It is the current checkout's source build, meant for repo-local development and verification only; it is never selected automatically and is excluded from distributions.
|
||||
|
||||
Both carriers hold the same content, defined once: the [package.json](package.json) at this package's root is the deploy root of the single-exe pipeline — a pure dependency manifest (no code of its own) whose dependency closure IS both the plugin set compiled into the exe and the tree materialized into `runtime/node/`. Adding a plugin to the distribution means adding one dependency line there and rebuilding.
|
||||
|
||||
@@ -8,7 +8,7 @@ Python SDK 的运行时载体包(分发名 `deepseek-harness-runtime-bin`,
|
||||
|
||||
两种载体并存于 `src/deepseek_harness_runtime/runtime/` 之下,均由仓库的 `scripts/build-exe-for-python-sdk.ts` 构建注入,且均被 git 忽略:
|
||||
|
||||
- **exe(生产)**——单文件 Node 可执行程序 `dsh-jsonrpc-agent-pkg-<platform>-<arch>` 及其原生 `-spawn-helper` 伴随文件(platform:`linux`/`macos`;arch:`x64`/`arm64`)。`node-pty` 需要该 helper;两者作为一个运行时产物构建,且 ELF 或 thin Mach-O 文件头必须与目标匹配。目标机器无需安装 Node。这是唯一随 wheel 包分发的载体;本包不发布 sdist。
|
||||
- **exe(生产)**——单文件 Node 可执行程序 `dsh-jsonrpc-agent-pkg-<platform>-<arch>`(platform:`linux`/`macos`;arch:`x64`/`arm64`)。macOS 构建还会随附 `node-pty` 在该平台使用的原生 `-spawn-helper` 伴随文件,其 thin Mach-O 文件头必须与目标匹配。目标机器无需安装 Node。这是唯一随 wheel 包分发的载体;本包不发布 sdist。
|
||||
- **`node`(仅限开发)**——`runtime/node/` 下的完整部署闭包(`package.json` + `node_modules/`),在系统 Node >= 22.19 上以 `node runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js` 执行。它是当前检出的源码构建,仅用于仓库本地的开发与验证;不会被自动选中,也不进入分发物。
|
||||
|
||||
两种载体承载相同的内容,且只定义一次:本包根目录的 [package.json](package.json) 是 single-exe 流水线的部署根目录——一份零代码的纯依赖 manifest,其依赖闭包既是编译进 exe 的插件集,也是物化到 `runtime/node/` 的文件树。往分发物里加插件,就是在那里加一行依赖再重新构建。
|
||||
|
||||
@@ -17,26 +17,18 @@ _SPAWN_HELPER_SUFFIX = "-spawn-helper"
|
||||
|
||||
|
||||
def _spawn_helper_binary_target(header: bytes) -> str | None:
|
||||
if (
|
||||
len(header) >= 20
|
||||
and header[:4] == b"\x7fELF"
|
||||
and header[4] == 2
|
||||
and header[5] == 1
|
||||
):
|
||||
machine = int.from_bytes(header[18:20], "little")
|
||||
if machine == 62:
|
||||
return "linux-x64"
|
||||
if machine == 183:
|
||||
return "linux-arm64"
|
||||
if len(header) >= 8 and header[:4] == b"\xcf\xfa\xed\xfe":
|
||||
if int.from_bytes(header[4:8], "little") == 0x0100000C:
|
||||
cpu_type = int.from_bytes(header[4:8], "little")
|
||||
if cpu_type == 0x01000007:
|
||||
return "macos-x64"
|
||||
if cpu_type == 0x0100000C:
|
||||
return "macos-arm64"
|
||||
return None
|
||||
|
||||
|
||||
def _validate_spawn_helper(path: Path, expected_target: str) -> None:
|
||||
with path.open("rb") as helper:
|
||||
actual_target = _spawn_helper_binary_target(helper.read(20))
|
||||
actual_target = _spawn_helper_binary_target(helper.read(8))
|
||||
if actual_target != expected_target:
|
||||
raise RuntimeError(
|
||||
f"runtime spawn helper binary mismatch: expected {expected_target}, "
|
||||
@@ -84,15 +76,18 @@ class RuntimeBuildHook(BuildHookInterface):
|
||||
f"runtime wheel {platform_tag} must contain only {expected_executable}; found {found}"
|
||||
)
|
||||
expected_helper = f"{expected_executable}{_SPAWN_HELPER_SUFFIX}"
|
||||
if [path.name for path in helpers] != [expected_helper]:
|
||||
expected_helpers = [expected_helper] if expected_target.startswith("macos-") else []
|
||||
if [path.name for path in helpers] != expected_helpers:
|
||||
expected = ", ".join(expected_helpers) or "none"
|
||||
found = ", ".join(path.name for path in helpers) or "none"
|
||||
raise RuntimeError(
|
||||
f"runtime wheel {platform_tag} must contain only {expected_helper}; found {found}"
|
||||
f"runtime wheel {platform_tag} helper payload mismatch: expected {expected}; found {found}"
|
||||
)
|
||||
for executable in [executables[0], helpers[0]]:
|
||||
for executable in [executables[0], *helpers]:
|
||||
if executable.stat().st_mode & stat.S_IXUSR == 0:
|
||||
raise RuntimeError(f"runtime executable is not executable: {executable}")
|
||||
_validate_spawn_helper(helpers[0], expected_target)
|
||||
if helpers:
|
||||
_validate_spawn_helper(helpers[0], expected_target)
|
||||
|
||||
build_data["pure_python"] = False
|
||||
build_data["infer_tag"] = False
|
||||
|
||||
@@ -17,14 +17,10 @@ build_python_release = SimpleNamespace(**runpy.run_path(str(SCRIPT)))
|
||||
|
||||
|
||||
def helper_header(target: str) -> bytes:
|
||||
header = bytearray(20)
|
||||
if target.startswith("linux-"):
|
||||
header[:6] = b"\x7fELF\x02\x01"
|
||||
machine = 62 if target == "linux-x64" else 183
|
||||
header[18:20] = machine.to_bytes(2, "little")
|
||||
else:
|
||||
header[:4] = b"\xcf\xfa\xed\xfe"
|
||||
header[4:8] = (0x0100000C).to_bytes(4, "little")
|
||||
header = bytearray(8)
|
||||
header[:4] = b"\xcf\xfa\xed\xfe"
|
||||
cpu_type = 0x01000007 if target == "macos-x64" else 0x0100000C
|
||||
header[4:8] = cpu_type.to_bytes(4, "little")
|
||||
return bytes(header)
|
||||
|
||||
|
||||
@@ -76,7 +72,7 @@ def test_stage_runtime_copies_executable_and_spawn_helper(tmp_path: Path) -> Non
|
||||
|
||||
|
||||
def test_stage_runtime_rejects_missing_spawn_helper(tmp_path: Path) -> None:
|
||||
executable = tmp_path / "dsh-jsonrpc-agent-pkg-linux-x64"
|
||||
executable = tmp_path / "dsh-jsonrpc-agent-pkg-macos-arm64"
|
||||
executable.write_bytes(b"runtime")
|
||||
executable.chmod(0o755)
|
||||
|
||||
@@ -89,20 +85,36 @@ def test_stage_runtime_rejects_missing_spawn_helper(tmp_path: Path) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", ["linux-x64", "linux-arm64", "macos-arm64"])
|
||||
@pytest.mark.parametrize("target", ["linux-x64", "linux-arm64"])
|
||||
def test_stage_runtime_copies_linux_executable_without_spawn_helper(
|
||||
tmp_path: Path, target: str
|
||||
) -> None:
|
||||
executable = tmp_path / f"dsh-jsonrpc-agent-pkg-{target}"
|
||||
executable.write_bytes(b"runtime")
|
||||
executable.chmod(0o755)
|
||||
destination = tmp_path / "staging"
|
||||
|
||||
build_python_release.stage_runtime(destination, "1.2.3", executable, executable.name)
|
||||
|
||||
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
|
||||
runtime_files = [path.name for path in runtime_dir.glob("dsh-jsonrpc-agent-pkg-*")]
|
||||
assert runtime_files == [executable.name]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", ["macos-x64", "macos-arm64"])
|
||||
def test_spawn_helper_binary_target(target: str) -> None:
|
||||
assert build_python_release.spawn_helper_binary_target(helper_header(target)) == target
|
||||
|
||||
|
||||
def test_stage_runtime_rejects_mismatched_spawn_helper(tmp_path: Path) -> None:
|
||||
executable = tmp_path / "dsh-jsonrpc-agent-pkg-linux-x64"
|
||||
executable = tmp_path / "dsh-jsonrpc-agent-pkg-macos-arm64"
|
||||
executable.write_bytes(b"runtime")
|
||||
executable.chmod(0o755)
|
||||
spawn_helper = Path(f"{executable}-spawn-helper")
|
||||
spawn_helper.write_bytes(helper_header("linux-arm64"))
|
||||
spawn_helper.write_bytes(helper_header("macos-x64"))
|
||||
spawn_helper.chmod(0o755)
|
||||
|
||||
with pytest.raises(ValueError, match="expected linux-x64, found linux-arm64"):
|
||||
with pytest.raises(ValueError, match="expected macos-arm64, found macos-x64"):
|
||||
build_python_release.stage_runtime(
|
||||
tmp_path / "staging",
|
||||
"1.2.3",
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
import { spawn } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs'
|
||||
import { chmod, copyFile, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { basename, join, resolve, sep } from 'node:path'
|
||||
import { chmod, copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { basename, dirname, join, resolve, sep } from 'node:path'
|
||||
import { parseArgs } from 'node:util'
|
||||
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
@@ -55,19 +55,11 @@ type Arch = (typeof ARCHES)[number]
|
||||
|
||||
interface RuntimeProduct {
|
||||
executable: string
|
||||
spawnHelper: string
|
||||
spawnHelper?: string
|
||||
}
|
||||
|
||||
function spawnHelperBinaryTarget(path: string): string | undefined {
|
||||
const header = readFileSync(path).subarray(0, 20)
|
||||
if (header.length >= 20
|
||||
&& header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46]))
|
||||
&& header[4] === 2
|
||||
&& header[5] === 1) {
|
||||
const machine = header.readUInt16LE(18)
|
||||
if (machine === 62) return 'linux-x64'
|
||||
if (machine === 183) return 'linux-arm64'
|
||||
}
|
||||
const header = readFileSync(path).subarray(0, 8)
|
||||
if (header.length >= 8 && header.readUInt32LE(0) === 0xfeedfacf) {
|
||||
const cpuType = header.readUInt32LE(4)
|
||||
if (cpuType === 0x01000007) return 'macos-x64'
|
||||
@@ -76,6 +68,10 @@ function spawnHelperBinaryTarget(path: string): string | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function runtimeProductFiles(product: RuntimeProduct): string[] {
|
||||
return [product.executable, ...(product.spawnHelper === undefined ? [] : [product.spawnHelper])]
|
||||
}
|
||||
|
||||
function isPlatform(value: string): value is Platform {
|
||||
return (PLATFORMS as readonly string[]).includes(value)
|
||||
}
|
||||
@@ -317,7 +313,7 @@ class SingleExeBuild {
|
||||
*/
|
||||
async pack(target: Target): Promise<RuntimeProduct> {
|
||||
const product = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
|
||||
const spawnHelper = `${product}${SPAWN_HELPER_SUFFIX}`
|
||||
await this.prepareNativePty(target)
|
||||
if (!this.cli.dryRun) mkdirSync(this.outDir, { recursive: true })
|
||||
await this.run(`pkg ${target.spec}`, pnpmBin(), [
|
||||
'dlx',
|
||||
@@ -332,6 +328,8 @@ class SingleExeBuild {
|
||||
if (!this.cli.dryRun && !existsSync(product)) {
|
||||
throw new Error(`build-exe-for-python-sdk: product ${product} is missing after the pkg run; inspect ${this.outDir}.`)
|
||||
}
|
||||
if (target.platform !== 'macos') return { executable: product }
|
||||
const spawnHelper = `${product}${SPAWN_HELPER_SUFFIX}`
|
||||
if (this.cli.dryRun) {
|
||||
console.log(`build-exe-for-python-sdk: [dry-run] copy target node-pty spawn-helper to ${spawnHelper}`)
|
||||
} else {
|
||||
@@ -342,6 +340,38 @@ class SingleExeBuild {
|
||||
return { executable: product, spawnHelper }
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the target node-pty addon in the staged closure. Linux npm installs
|
||||
* build it from source, but legacy deploy omits that side-effect directory.
|
||||
* @param target - the pkg target whose native addon is being staged.
|
||||
*/
|
||||
private async prepareNativePty(target: Target): Promise<void> {
|
||||
const stagedRoot = join(this.staging, 'node_modules', 'node-pty')
|
||||
const stagedBuild = join(stagedRoot, 'build')
|
||||
if (this.cli.dryRun) console.log(`build-exe-for-python-sdk: [dry-run] rm -rf ${stagedBuild}`)
|
||||
else await rm(stagedBuild, { recursive: true, force: true })
|
||||
|
||||
const nativePlatform = target.platform === 'macos' ? 'darwin' : 'linux'
|
||||
const prebuilt = join(stagedRoot, 'prebuilds', `${nativePlatform}-${target.arch}`, 'pty.node')
|
||||
const source = join(root, 'packages', 'pty', 'pty-local', 'node_modules', 'node-pty', 'build', 'Release', 'pty.node')
|
||||
const destination = join(stagedBuild, 'Release', 'pty.node')
|
||||
if (this.cli.dryRun) {
|
||||
if (target.platform === 'linux') console.log(`build-exe-for-python-sdk: [dry-run] cp ${source} ${destination}`)
|
||||
return
|
||||
}
|
||||
if (existsSync(prebuilt)) return
|
||||
|
||||
const host = Target.host()
|
||||
if (target.platform !== host.platform || target.arch !== host.arch || !existsSync(source)) {
|
||||
throw new Error(
|
||||
`build-exe-for-python-sdk: node-pty native addon for ${target.platform}-${target.arch} is missing; `
|
||||
+ `checked ${prebuilt}, ${source}. Build the Linux runtime on its target architecture.`,
|
||||
)
|
||||
}
|
||||
await mkdir(dirname(destination), { recursive: true })
|
||||
await copyFile(source, destination)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the node-pty helper that matches a pkg target.
|
||||
* @param target - the pkg target whose helper must be shipped.
|
||||
@@ -349,14 +379,12 @@ class SingleExeBuild {
|
||||
*/
|
||||
private resolveSpawnHelper(target: Target): string {
|
||||
const nodePtyRoot = join(this.staging, 'node_modules', 'node-pty')
|
||||
const nativePlatform = target.platform === 'macos' ? 'darwin' : 'linux'
|
||||
const candidates = [
|
||||
join(nodePtyRoot, 'prebuilds', `${nativePlatform}-${target.arch}`, 'spawn-helper'),
|
||||
join(nodePtyRoot, 'prebuilds', `darwin-${target.arch}`, 'spawn-helper'),
|
||||
]
|
||||
const hostPlatform = process.platform === 'darwin' ? 'macos' : process.platform
|
||||
const hostArch = process.arch === 'x64' || process.arch === 'arm64' ? process.arch : undefined
|
||||
if (target.platform === hostPlatform && target.arch === hostArch) {
|
||||
candidates.push(join(nodePtyRoot, 'build', 'Release', 'spawn-helper'))
|
||||
const host = Target.host()
|
||||
if (target.platform === host.platform && target.arch === host.arch) {
|
||||
candidates.push(join(root, 'packages', 'pty', 'pty-local', 'node_modules', 'node-pty', 'build', 'Release', 'spawn-helper'))
|
||||
}
|
||||
const helper = candidates.find(candidate => existsSync(candidate))
|
||||
if (helper === undefined) {
|
||||
@@ -387,11 +415,10 @@ class SingleExeBuild {
|
||||
console.log(this.cli.dryRun ? 'build-exe-for-python-sdk: [dry-run] would produce:' : 'build-exe-for-python-sdk: products:')
|
||||
for (const product of products) {
|
||||
if (this.cli.dryRun) {
|
||||
console.log(` ${product.executable}`)
|
||||
console.log(` ${product.spawnHelper}`)
|
||||
for (const path of runtimeProductFiles(product)) console.log(` ${path}`)
|
||||
continue
|
||||
}
|
||||
for (const path of [product.executable, product.spawnHelper]) {
|
||||
for (const path of runtimeProductFiles(product)) {
|
||||
const megabytes = statSync(path).size / (1024 * 1024)
|
||||
console.log(` ${path} (${megabytes.toFixed(1)} MB)`)
|
||||
}
|
||||
@@ -407,7 +434,7 @@ class SingleExeBuild {
|
||||
const destDir = resolve(root, PYTHON_RUNTIME_DIR)
|
||||
if (this.cli.dryRun) {
|
||||
for (const product of products) {
|
||||
for (const path of [product.executable, product.spawnHelper]) {
|
||||
for (const path of runtimeProductFiles(product)) {
|
||||
console.log(`build-exe-for-python-sdk: [dry-run] cp ${path} ${join(destDir, basename(path))}`)
|
||||
}
|
||||
}
|
||||
@@ -415,7 +442,7 @@ class SingleExeBuild {
|
||||
}
|
||||
mkdirSync(destDir, { recursive: true })
|
||||
for (const product of products) {
|
||||
for (const path of [product.executable, product.spawnHelper]) {
|
||||
for (const path of runtimeProductFiles(product)) {
|
||||
const destination = join(destDir, basename(path))
|
||||
await copyFile(path, destination)
|
||||
await chmod(destination, statSync(path).mode & 0o777)
|
||||
|
||||
@@ -27,26 +27,18 @@ EXECUTABLE_TARGETS = {value[1]: key for key, value in PLATFORMS.items()}
|
||||
|
||||
|
||||
def spawn_helper_binary_target(header: bytes) -> str | None:
|
||||
if (
|
||||
len(header) >= 20
|
||||
and header[:4] == b"\x7fELF"
|
||||
and header[4] == 2
|
||||
and header[5] == 1
|
||||
):
|
||||
machine = int.from_bytes(header[18:20], "little")
|
||||
if machine == 62:
|
||||
return "linux-x64"
|
||||
if machine == 183:
|
||||
return "linux-arm64"
|
||||
if len(header) >= 8 and header[:4] == b"\xcf\xfa\xed\xfe":
|
||||
if int.from_bytes(header[4:8], "little") == 0x0100000C:
|
||||
cpu_type = int.from_bytes(header[4:8], "little")
|
||||
if cpu_type == 0x01000007:
|
||||
return "macos-x64"
|
||||
if cpu_type == 0x0100000C:
|
||||
return "macos-arm64"
|
||||
return None
|
||||
|
||||
|
||||
def validate_spawn_helper(path: Path, expected_target: str) -> None:
|
||||
with path.open("rb") as helper:
|
||||
actual_target = spawn_helper_binary_target(helper.read(20))
|
||||
actual_target = spawn_helper_binary_target(helper.read(8))
|
||||
if actual_target != expected_target:
|
||||
raise ValueError(
|
||||
f"runtime spawn helper binary mismatch: expected {expected_target}, "
|
||||
@@ -166,12 +158,14 @@ def stage_runtime(destination: Path, version: str, executable: Path, executable_
|
||||
raise FileNotFoundError(f"runtime executable does not exist: {executable}")
|
||||
if executable.stat().st_mode & stat.S_IXUSR == 0:
|
||||
raise PermissionError(f"runtime executable is not executable: {executable}")
|
||||
expected_target = EXECUTABLE_TARGETS[executable_name]
|
||||
spawn_helper = Path(f"{executable}{SPAWN_HELPER_SUFFIX}")
|
||||
if not spawn_helper.is_file():
|
||||
raise FileNotFoundError(f"runtime spawn helper does not exist: {spawn_helper}")
|
||||
if spawn_helper.stat().st_mode & stat.S_IXUSR == 0:
|
||||
raise PermissionError(f"runtime spawn helper is not executable: {spawn_helper}")
|
||||
validate_spawn_helper(spawn_helper, EXECUTABLE_TARGETS[executable_name])
|
||||
if expected_target.startswith("macos-"):
|
||||
if not spawn_helper.is_file():
|
||||
raise FileNotFoundError(f"runtime spawn helper does not exist: {spawn_helper}")
|
||||
if spawn_helper.stat().st_mode & stat.S_IXUSR == 0:
|
||||
raise PermissionError(f"runtime spawn helper is not executable: {spawn_helper}")
|
||||
validate_spawn_helper(spawn_helper, expected_target)
|
||||
copy_package(ROOT / "python" / "sdk-runtime", destination)
|
||||
rewrite_version(destination / "pyproject.toml", version)
|
||||
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
|
||||
@@ -179,9 +173,10 @@ def stage_runtime(destination: Path, version: str, executable: Path, executable_
|
||||
destination_executable = runtime_dir / executable_name
|
||||
shutil.copyfile(executable, destination_executable)
|
||||
destination_executable.chmod(executable.stat().st_mode & 0o777)
|
||||
destination_helper = runtime_dir / f"{executable_name}{SPAWN_HELPER_SUFFIX}"
|
||||
shutil.copyfile(spawn_helper, destination_helper)
|
||||
destination_helper.chmod(spawn_helper.stat().st_mode & 0o777)
|
||||
if expected_target.startswith("macos-"):
|
||||
destination_helper = runtime_dir / f"{executable_name}{SPAWN_HELPER_SUFFIX}"
|
||||
shutil.copyfile(spawn_helper, destination_helper)
|
||||
destination_helper.chmod(spawn_helper.stat().st_mode & 0o777)
|
||||
|
||||
|
||||
def verify_wheel(
|
||||
@@ -209,20 +204,27 @@ def verify_wheel(
|
||||
assert platform is not None
|
||||
if len(executables) != 1 or not executables[0].endswith(f"/runtime/{platform[1]}"):
|
||||
raise RuntimeError(f"{wheel} must contain exactly {platform[1]}, found {executables}")
|
||||
expected_target = EXECUTABLE_TARGETS[platform[1]]
|
||||
expected_helper = f"{platform[1]}{SPAWN_HELPER_SUFFIX}"
|
||||
if len(helpers) != 1 or not helpers[0].endswith(f"/runtime/{expected_helper}"):
|
||||
raise RuntimeError(f"{wheel} must contain exactly {expected_helper}, found {helpers}")
|
||||
for executable in [executables[0], helpers[0]]:
|
||||
expected_helpers = [expected_helper] if expected_target.startswith("macos-") else []
|
||||
found_helpers = [Path(helper).name for helper in helpers]
|
||||
if found_helpers != expected_helpers:
|
||||
expected = ", ".join(expected_helpers) or "none"
|
||||
found = ", ".join(found_helpers) or "none"
|
||||
raise RuntimeError(
|
||||
f"{wheel} runtime helper payload mismatch: expected {expected}; found {found}"
|
||||
)
|
||||
for executable in [executables[0], *helpers]:
|
||||
mode = archive.getinfo(executable).external_attr >> 16
|
||||
if mode & stat.S_IXUSR == 0:
|
||||
raise RuntimeError(f"{wheel} runtime executable lost its executable bit: {executable}")
|
||||
actual_target = spawn_helper_binary_target(archive.read(helpers[0])[:20])
|
||||
expected_target = EXECUTABLE_TARGETS[platform[1]]
|
||||
if actual_target != expected_target:
|
||||
raise RuntimeError(
|
||||
f"{wheel} spawn helper binary mismatch: expected {expected_target}, "
|
||||
f"found {actual_target or 'unsupported format or architecture'}"
|
||||
)
|
||||
if helpers:
|
||||
actual_target = spawn_helper_binary_target(archive.read(helpers[0])[:8])
|
||||
if actual_target != expected_target:
|
||||
raise RuntimeError(
|
||||
f"{wheel} spawn helper binary mismatch: expected {expected_target}, "
|
||||
f"found {actual_target or 'unsupported format or architecture'}"
|
||||
)
|
||||
elif runtime_files:
|
||||
raise RuntimeError(f"SDK wheel unexpectedly contains runtime executables: {runtime_files}")
|
||||
if package == "sdk":
|
||||
|
||||
Reference in New Issue
Block a user