fix(build): validate packaged spawn helpers
This commit is contained in:
+2
-2
@@ -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: 26949e7435bcbf05132662c308f33f322920c7eb
|
||||
2026-07-29-persistent-bash-str-replace-editor.zh.md: 6d5eadbfa68a59157e8f4bc148d03144bc665cb2
|
||||
2026-07-29-persistent-bash-str-replace-editor.md: b1be9cc40e11b07b722877e666de0e0328636a05
|
||||
2026-07-29-persistent-bash-str-replace-editor.zh.md: 0bf100514e971dfa759f44dcca15a7ad6a2fdd8a
|
||||
@@ -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 (or when `DSH_NODE_PTY_SPAWN_HELPER` explicitly selects one), preserving upstream lookup in ordinary Node runs; the executable and runtime-wheel builders 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`, 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.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+1
-1
@@ -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` 补丁只在该伴随文件存在时解析它(也可由 `DSH_NODE_PTY_SPAWN_HELPER` 显式指定),普通 Node 运行仍保留上游查找方式;若 helper 缺失、架构不匹配或不可执行,可执行文件与 runtime wheel 构建会在发布前失败。
|
||||
两个插件都进入 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 缺失、架构不匹配或不可执行,构建会在发布前失败。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ diff --git a/lib/unixTerminal.js b/lib/unixTerminal.js
|
||||
index 1ec12f796a822c78fba9ad7f6448c3987e325c23..5cd6b7d635f4752be5a6c5ff9cf9edf988cf94c5 100644
|
||||
--- a/lib/unixTerminal.js
|
||||
+++ b/lib/unixTerminal.js
|
||||
@@ -26,10 +26,22 @@ var terminal_1 = require("./terminal");
|
||||
@@ -26,10 +26,23 @@ var terminal_1 = require("./terminal");
|
||||
var utils_1 = require("./utils");
|
||||
var native = utils_1.loadNativeModule('pty');
|
||||
var pty = native.module;
|
||||
@@ -10,6 +10,7 @@ index 1ec12f796a822c78fba9ad7f6448c3987e325c23..5cd6b7d635f4752be5a6c5ff9cf9edf9
|
||||
-helperPath = path.resolve(__dirname, helperPath);
|
||||
-helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
||||
-helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
||||
+// A current external embedded-runtime consumer supplies a non-sibling helper.
|
||||
+var helperPath = process.env.DSH_NODE_PTY_SPAWN_HELPER;
|
||||
+if (helperPath) {
|
||||
+ helperPath = path.resolve(helperPath);
|
||||
@@ -33,7 +34,7 @@ diff --git a/src/unixTerminal.ts b/src/unixTerminal.ts
|
||||
index 98733dc0cd752b554bd94e45904ca341ad141bba..fa234291206617ae5a6d8605abf9771220392d17 100644
|
||||
--- a/src/unixTerminal.ts
|
||||
+++ b/src/unixTerminal.ts
|
||||
@@ -14,10 +14,20 @@ import { assign, loadNativeModule } from './utils';
|
||||
@@ -14,10 +14,21 @@ import { assign, loadNativeModule } from './utils';
|
||||
|
||||
const native = loadNativeModule('pty');
|
||||
const pty: IUnixNative = native.module;
|
||||
@@ -41,6 +42,7 @@ index 98733dc0cd752b554bd94e45904ca341ad141bba..fa234291206617ae5a6d8605abf97712
|
||||
-helperPath = path.resolve(__dirname, helperPath);
|
||||
-helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
||||
-helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
||||
+// A current external embedded-runtime consumer supplies a non-sibling helper.
|
||||
+let helperPath = process.env.DSH_NODE_PTY_SPAWN_HELPER;
|
||||
+if (helperPath) {
|
||||
+ helperPath = path.resolve(helperPath);
|
||||
|
||||
Generated
+4
-4
@@ -6,7 +6,7 @@ settings:
|
||||
|
||||
patchedDependencies:
|
||||
'@earendil-works/pi-tui@0.80.7': 6c30c5386c0159131e1361023cddf31377f5728962524841964373312c1ed946
|
||||
node-pty@1.1.0: 4a1568bc9ef77084629054d0736430818818155abcd0dce581ef8c782e974c15
|
||||
node-pty@1.1.0: 7a0c04f1f49d798a9ffe2f7f414c01064a44ca2489772d0c3e1235ab336755e6
|
||||
|
||||
importers:
|
||||
|
||||
@@ -656,7 +656,7 @@ importers:
|
||||
devDependencies:
|
||||
node-pty:
|
||||
specifier: 1.1.0
|
||||
version: 1.1.0(patch_hash=4a1568bc9ef77084629054d0736430818818155abcd0dce581ef8c782e974c15)
|
||||
version: 1.1.0(patch_hash=7a0c04f1f49d798a9ffe2f7f414c01064a44ca2489772d0c3e1235ab336755e6)
|
||||
|
||||
packages/acp/acp:
|
||||
dependencies:
|
||||
@@ -3402,7 +3402,7 @@ importers:
|
||||
dependencies:
|
||||
node-pty:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(patch_hash=4a1568bc9ef77084629054d0736430818818155abcd0dce581ef8c782e974c15)
|
||||
version: 1.1.0(patch_hash=7a0c04f1f49d798a9ffe2f7f414c01064a44ca2489772d0c3e1235ab336755e6)
|
||||
schemastery:
|
||||
specifier: ^3.18.0
|
||||
version: 3.18.0
|
||||
@@ -15287,7 +15287,7 @@ snapshots:
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
node-pty@1.1.0(patch_hash=4a1568bc9ef77084629054d0736430818818155abcd0dce581ef8c782e974c15):
|
||||
node-pty@1.1.0(patch_hash=7a0c04f1f49d798a9ffe2f7f414c01064a44ca2489772d0c3e1235ab336755e6):
|
||||
dependencies:
|
||||
node-addon-api: 7.1.1
|
||||
|
||||
|
||||
@@ -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: ee3791eddf26b526316d4f3952793a03cc48841e
|
||||
README.zh.md: 59d40ee56688cb377902ff126b7fa77606c7ad8b
|
||||
README.md: 0869b4a9dce0e261b168f21c90a80faf81ea4a64
|
||||
README.zh.md: 3f2342c4c66e24b8213c78d4e7530c3360497022
|
||||
@@ -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 and validated as one runtime product. 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>` 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.
|
||||
- **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;构建与校验会把两者视作同一个运行时产物。目标机器无需安装 Node。这是唯一随 wheel 包分发的载体;本包不发布 sdist。
|
||||
- **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。
|
||||
- **`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/` 的文件树。往分发物里加插件,就是在那里加一行依赖再重新构建。
|
||||
|
||||
@@ -16,6 +16,34 @@ _PLATFORMS = {
|
||||
_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:
|
||||
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))
|
||||
if actual_target != expected_target:
|
||||
raise RuntimeError(
|
||||
f"runtime spawn helper binary mismatch: expected {expected_target}, "
|
||||
f"found {actual_target or 'unsupported format or architecture'} at {path}"
|
||||
)
|
||||
|
||||
|
||||
def _host_platform_tag() -> str:
|
||||
machine = platform.machine().lower()
|
||||
arch = "arm64" if machine in {"arm64", "aarch64"} else "x64" if machine in {"x86_64", "amd64"} else machine
|
||||
@@ -39,13 +67,13 @@ class RuntimeBuildHook(BuildHookInterface):
|
||||
)
|
||||
|
||||
platform_tag = os.environ.get("DSH_RUNTIME_PLATFORM_TAG") or _host_platform_tag()
|
||||
matches = [value for value in _PLATFORMS.values() if value[0] == platform_tag]
|
||||
matches = [(key, value) for key, value in _PLATFORMS.items() if value[0] == platform_tag]
|
||||
if len(matches) != 1:
|
||||
supported = ", ".join(value[0] for value in _PLATFORMS.values())
|
||||
raise RuntimeError(
|
||||
f"unsupported DSH_RUNTIME_PLATFORM_TAG {platform_tag!r}; expected one of {supported}"
|
||||
)
|
||||
expected_executable = matches[0][1]
|
||||
expected_target, (_, expected_executable) = matches[0]
|
||||
runtime_dir = Path(self.root) / "src" / "deepseek_harness_runtime" / "runtime"
|
||||
runtime_files = sorted(runtime_dir.glob("dsh-jsonrpc-agent-pkg-*") if runtime_dir.is_dir() else [])
|
||||
executables = [path for path in runtime_files if not path.name.endswith(_SPAWN_HELPER_SUFFIX)]
|
||||
@@ -64,6 +92,7 @@ class RuntimeBuildHook(BuildHookInterface):
|
||||
for executable in [executables[0], helpers[0]]:
|
||||
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)
|
||||
|
||||
build_data["pure_python"] = False
|
||||
build_data["infer_tag"] = False
|
||||
|
||||
@@ -16,6 +16,18 @@ SCRIPT = ROOT / "scripts" / "build-python-release.py"
|
||||
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")
|
||||
return bytes(header)
|
||||
|
||||
|
||||
def test_repository_version_matches_root_package_json() -> None:
|
||||
expected = json.loads((ROOT / "package.json").read_text())["version"]
|
||||
|
||||
@@ -45,7 +57,7 @@ def test_stage_runtime_copies_executable_and_spawn_helper(tmp_path: Path) -> Non
|
||||
executable.write_bytes(b"runtime")
|
||||
executable.chmod(0o755)
|
||||
spawn_helper = Path(f"{executable}-spawn-helper")
|
||||
spawn_helper.write_bytes(b"helper")
|
||||
spawn_helper.write_bytes(helper_header("macos-arm64"))
|
||||
spawn_helper.chmod(0o751)
|
||||
destination = tmp_path / "staging"
|
||||
|
||||
@@ -59,7 +71,7 @@ def test_stage_runtime_copies_executable_and_spawn_helper(tmp_path: Path) -> Non
|
||||
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
|
||||
assert (runtime_dir / executable.name).read_bytes() == b"runtime"
|
||||
copied_helper = runtime_dir / spawn_helper.name
|
||||
assert copied_helper.read_bytes() == b"helper"
|
||||
assert copied_helper.read_bytes() == helper_header("macos-arm64")
|
||||
assert copied_helper.stat().st_mode & stat.S_IXUSR
|
||||
|
||||
|
||||
@@ -75,3 +87,42 @@ def test_stage_runtime_rejects_missing_spawn_helper(tmp_path: Path) -> None:
|
||||
executable,
|
||||
executable.name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", ["linux-x64", "linux-arm64", "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.write_bytes(b"runtime")
|
||||
executable.chmod(0o755)
|
||||
spawn_helper = Path(f"{executable}-spawn-helper")
|
||||
spawn_helper.write_bytes(helper_header("linux-arm64"))
|
||||
spawn_helper.chmod(0o755)
|
||||
|
||||
with pytest.raises(ValueError, match="expected linux-x64, found linux-arm64"):
|
||||
build_python_release.stage_runtime(
|
||||
tmp_path / "staging",
|
||||
"1.2.3",
|
||||
executable,
|
||||
executable.name,
|
||||
)
|
||||
|
||||
|
||||
def test_stage_runtime_rejects_non_binary_spawn_helper(tmp_path: Path) -> None:
|
||||
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(b"helper")
|
||||
spawn_helper.chmod(0o755)
|
||||
|
||||
with pytest.raises(ValueError, match="unsupported format or architecture"):
|
||||
build_python_release.stage_runtime(
|
||||
tmp_path / "staging",
|
||||
"1.2.3",
|
||||
executable,
|
||||
executable.name,
|
||||
)
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { spawn } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, statSync } from 'node:fs'
|
||||
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 { parseArgs } from 'node:util'
|
||||
@@ -58,6 +58,24 @@ interface RuntimeProduct {
|
||||
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'
|
||||
}
|
||||
if (header.length >= 8 && header.readUInt32LE(0) === 0xfeedfacf) {
|
||||
const cpuType = header.readUInt32LE(4)
|
||||
if (cpuType === 0x01000007) return 'macos-x64'
|
||||
if (cpuType === 0x0100000c) return 'macos-arm64'
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isPlatform(value: string): value is Platform {
|
||||
return (PLATFORMS as readonly string[]).includes(value)
|
||||
}
|
||||
@@ -347,7 +365,17 @@ class SingleExeBuild {
|
||||
+ `checked ${candidates.join(', ')}. Build each runtime on its target platform and architecture.`,
|
||||
)
|
||||
}
|
||||
if (statSync(helper).mode & 0o111) return helper
|
||||
if (statSync(helper).mode & 0o111) {
|
||||
const expected = `${target.platform}-${target.arch}`
|
||||
const actual = spawnHelperBinaryTarget(helper)
|
||||
if (actual !== expected) {
|
||||
throw new Error(
|
||||
`build-exe-for-python-sdk: node-pty spawn-helper binary mismatch: expected ${expected}, `
|
||||
+ `found ${actual ?? 'unsupported format or architecture'} at ${helper}`,
|
||||
)
|
||||
}
|
||||
return helper
|
||||
}
|
||||
throw new Error(`build-exe-for-python-sdk: node-pty spawn-helper is not executable: ${helper}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,35 @@ PLATFORMS = {
|
||||
"macos-arm64": ("macosx_11_0_arm64", "dsh-jsonrpc-agent-pkg-macos-arm64"),
|
||||
}
|
||||
SPAWN_HELPER_SUFFIX = "-spawn-helper"
|
||||
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:
|
||||
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))
|
||||
if actual_target != expected_target:
|
||||
raise ValueError(
|
||||
f"runtime spawn helper binary mismatch: expected {expected_target}, "
|
||||
f"found {actual_target or 'unsupported format or architecture'} at {path}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -142,6 +171,7 @@ def stage_runtime(destination: Path, version: str, executable: Path, executable_
|
||||
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])
|
||||
copy_package(ROOT / "python" / "sdk-runtime", destination)
|
||||
rewrite_version(destination / "pyproject.toml", version)
|
||||
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
|
||||
@@ -186,6 +216,13 @@ def verify_wheel(
|
||||
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'}"
|
||||
)
|
||||
elif runtime_files:
|
||||
raise RuntimeError(f"SDK wheel unexpectedly contains runtime executables: {runtime_files}")
|
||||
if package == "sdk":
|
||||
|
||||
Reference in New Issue
Block a user