fix(build): stage platform-specific PTY artifacts

This commit is contained in:
Tianyi Cui
2026-07-29 22:44:19 +08:00
parent 7fdde06cf9
commit c0bd88430a
14 changed files with 141 additions and 105 deletions
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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/` 的文件树。往分发物里加插件,就是在那里加一行依赖再重新构建。
+12 -17
View File
@@ -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
+25 -13
View File
@@ -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",