feat(python-runtime): package the Windows x64 dsh executable

Add node24-win-x64 as the only supported Windows runtime target and publish it as a py3-none-win_amd64 wheel containing the conventional dsh and ripgrep .exe payload names. Keep Windows ARM64 rejected explicitly so Python cannot claim a carrier that CI and release automation do not build.

Teach the pkg builder to require a native x64 Windows host, validate both node-pty ConPTY addons, copy @vscode's win32 ripgrep executable, and recognize pkg's .exe output. Extend runtime resolution, wheel staging, payload validation, and the preset closure check so the Windows-specific PowerShell plugins and sidecars fail loud when omitted.

The sidecar resolver now maps a packaged main.exe to main-rg.exe; focused TypeScript and Python tests cover that name, the win_amd64 manifest, x64-only host selection, complete wheel payload, ConPTY inventory, and platform-conditioned plugin closure.
This commit is contained in:
Tianyi Cui
2026-08-24 19:09:40 +08:00
parent f76a225a7d
commit ca0b21661e
18 changed files with 380 additions and 53 deletions
@@ -20,7 +20,7 @@
*/
import { existsSync } from 'node:fs'
import { isAbsolute, relative, sep } from 'node:path'
import { isAbsolute, join, parse, relative, sep } from 'node:path'
import type { Context } from '@deepseek-ai/cordis'
import { HarnessError } from '@deepseek-ai/dsh-llm'
import { ItemRetainer, TextRetainer } from '@deepseek-ai/dsh-output-retention'
@@ -170,7 +170,10 @@ let rgPathPromise: Promise<string> | undefined
*/
export function resolveRgPath(): Promise<string> {
rgPathPromise ??= Promise.resolve().then(async () => {
const executableSidecar = `${process.execPath}-rg`
const executable = parse(process.execPath)
const executableSidecar = process.platform === 'win32'
? join(executable.dir, `${executable.name}-rg.exe`)
: `${process.execPath}-rg`
if ('pkg' in process && existsSync(executableSidecar)) return executableSidecar
return (await import('@vscode/ripgrep')).rgPath
})
@@ -1,9 +1,12 @@
import { join, parse } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const { dependencyRgPath, existsSync } = vi.hoisted(() => ({
dependencyRgPath: '/node_modules/@vscode/ripgrep/bin/rg',
existsSync: vi.fn(),
}))
const originalPlatform = process.platform
const originalExecPath = process.execPath
vi.mock('node:fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs')>()
@@ -16,17 +19,35 @@ beforeEach(() => {
vi.resetModules()
existsSync.mockReset()
Reflect.deleteProperty(process, 'pkg')
Reflect.defineProperty(process, 'platform', { configurable: true, enumerable: true, value: originalPlatform })
process.execPath = originalExecPath
})
afterEach(() => {
Reflect.deleteProperty(process, 'pkg')
Reflect.defineProperty(process, 'platform', { configurable: true, enumerable: true, value: originalPlatform })
process.execPath = originalExecPath
})
describe('ripgrep resolution', () => {
it('uses the native sidecar beside the current executable', async () => {
Reflect.defineProperty(process, 'pkg', { configurable: true, value: {} })
Reflect.defineProperty(process, 'platform', { configurable: true, enumerable: true, value: 'linux' })
process.execPath = '/runtime/dsh'
existsSync.mockReturnValue(true)
const sidecar = `${process.execPath}-rg`
const sidecar = '/runtime/dsh-rg'
const { resolveRgPath } = await import('@deepseek-ai/dsh-tool-fs-search')
await expect(resolveRgPath()).resolves.toBe(sidecar)
expect(existsSync).toHaveBeenCalledWith(sidecar)
})
it('uses a conventional executable name for the Windows ripgrep sidecar', async () => {
Reflect.defineProperty(process, 'pkg', { configurable: true, value: {} })
Reflect.defineProperty(process, 'platform', { configurable: true, enumerable: true, value: 'win32' })
process.execPath = 'C:\\runtime\\deepseek-harness-sdk-runtime-win-x64.exe'
existsSync.mockReturnValue(true)
const sidecar = 'C:\\runtime\\deepseek-harness-sdk-runtime-win-x64-rg.exe'
const { resolveRgPath } = await import('@deepseek-ai/dsh-tool-fs-search')
await expect(resolveRgPath()).resolves.toBe(sidecar)
@@ -47,6 +68,10 @@ describe('ripgrep resolution', () => {
const { resolveRgPath } = await import('@deepseek-ai/dsh-tool-fs-search')
await expect(resolveRgPath()).resolves.toBe(dependencyRgPath)
expect(existsSync).toHaveBeenCalledWith(`${process.execPath}-rg`)
const executable = parse(process.execPath)
const sidecar = process.platform === 'win32'
? join(executable.dir, `${executable.name}-rg.exe`)
: `${process.execPath}-rg`
expect(existsSync).toHaveBeenCalledWith(sidecar)
})
})
+6
View File
@@ -9972,6 +9972,12 @@ importers:
'@deepseek-ai/dsh-tool-jobs':
specifier: workspace:^
version: link:../../packages/jobs/tool-jobs
'@deepseek-ai/dsh-tool-pwsh':
specifier: workspace:^
version: link:../../packages/shell/tool-pwsh
'@deepseek-ai/dsh-tool-pwsh-persistent':
specifier: workspace:^
version: link:../../packages/shell/tool-pwsh-persistent
'@deepseek-ai/dsh-tool-ralph':
specifier: workspace:^
version: link:../../packages/workflow/tool-ralph
+16 -3
View File
@@ -39,7 +39,15 @@ 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
system = platform.system().lower()
key = f"macos-{arch}" if system == "darwin" else f"linux-{arch}" if system == "linux" else system
key = (
f"macos-{arch}"
if system == "darwin"
else f"linux-{arch}"
if system == "linux"
else f"win-{arch}"
if system == "windows"
else system
)
try:
return _PLATFORMS[key][0]
except KeyError as exc:
@@ -69,16 +77,21 @@ class RuntimeBuildHook(BuildHookInterface):
runtime_files = sorted(
runtime_dir.glob("deepseek-harness-sdk-runtime-*") if runtime_dir.is_dir() else []
)
expected_files = [expected_executable, f"{expected_executable}-rg"]
expected_files = (
[expected_executable, f"{expected_executable.removesuffix('.exe')}-rg.exe"]
if expected_executable.endswith(".exe")
else [expected_executable, f"{expected_executable}-rg"]
)
if "-macos-" in expected_executable:
expected_files.append(f"{expected_executable}-spawn-helper")
expected_files.sort()
found_files = [path.name for path in runtime_files]
if found_files != expected_files:
raise RuntimeError(
f"runtime wheel {platform_tag} payload must be {expected_files}; found {found_files}"
)
for executable in runtime_files:
if executable.stat().st_mode & stat.S_IXUSR == 0:
if platform_tag != "win_amd64" and executable.stat().st_mode & stat.S_IXUSR == 0:
raise RuntimeError(f"runtime executable is not executable: {executable}")
build_data["pure_python"] = False
build_data["infer_tag"] = False
+2
View File
@@ -59,6 +59,7 @@
"@deepseek-ai/dsh-plan-mode": "workspace:^",
"@deepseek-ai/dsh-persona": "workspace:^",
"@deepseek-ai/dsh-pwsh-local": "workspace:^",
"@deepseek-ai/dsh-tool-pwsh-persistent": "workspace:^",
"@deepseek-ai/dsh-terminal": "workspace:^",
"@deepseek-ai/dsh-terminal-bash": "workspace:^",
"@deepseek-ai/dsh-repeat-tool-reminder": "workspace:^",
@@ -103,6 +104,7 @@
"@deepseek-ai/dsh-tool-fs": "workspace:^",
"@deepseek-ai/dsh-tool-fs-search": "workspace:^",
"@deepseek-ai/dsh-tool-goal": "workspace:^",
"@deepseek-ai/dsh-tool-pwsh": "workspace:^",
"@deepseek-ai/dsh-tool-ralph": "workspace:^",
"@deepseek-ai/dsh-tool-skill": "workspace:^",
"@deepseek-ai/dsh-tool-str-replace-editor": "workspace:^",
+4
View File
@@ -10,5 +10,9 @@
"macos-arm64": {
"tag": "macosx_14_0_arm64",
"executable": "deepseek-harness-sdk-runtime-macos-arm64"
},
"win-x64": {
"tag": "win_amd64",
"executable": "deepseek-harness-sdk-runtime-win-x64.exe"
}
}
@@ -4,9 +4,10 @@ Two runtime carriers coexist under ``runtime/``, both injected by the repo's
``scripts/build-exe-for-python-sdk.ts`` build (neither is checked into git):
- **exe (production)**: single-file Node executables named
``deepseek-harness-sdk-runtime-<platform>-<arch>`` (platform in {linux, macos}, arch in
{x64, arm64}) with a sibling ``-rg`` executable; macOS also uses a sibling
``-spawn-helper``. The target machine needs no Node installation.
``deepseek-harness-sdk-runtime-<platform>-<arch>`` for Linux/macOS and an
``.exe`` counterpart for Windows. Each has a sibling ripgrep executable;
macOS also uses a sibling ``-spawn-helper``. The target machine needs no
Node installation.
- **node (dev-only)**: the full deploy closure under ``runtime/node/``
(``package.json`` + ``node_modules/``), executed as ``node
runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js`` on a
@@ -30,7 +31,7 @@ PACKAGE_METADATA_FILENAME = "deepseek-harness-runtime.json"
RUNTIME_MODE_ENV_VAR = "DSH_RUNTIME_MODE"
_PLATFORM_TAGS = {"linux": "linux", "darwin": "macos"}
_PLATFORM_TAGS = {"linux": "linux", "darwin": "macos", "win32": "win"}
_ARCH_TAGS = {"x86_64": "x64", "amd64": "x64", "arm64": "arm64", "aarch64": "arm64"}
_EXE_ACQUISITION_HINT = (
@@ -62,13 +63,18 @@ def bundled_runtime_path() -> Path:
touching callers).
"""
tag = _current_platform_tag()
path = bundled_package_dir() / "runtime" / f"deepseek-harness-sdk-runtime-{tag}"
extension = ".exe" if tag.startswith("win-") else ""
path = bundled_package_dir() / "runtime" / f"deepseek-harness-sdk-runtime-{tag}{extension}"
if not path.is_file():
raise FileNotFoundError(
f"deepseek-harness-runtime-bin is missing the runtime executable at {path}. "
+ _EXE_ACQUISITION_HINT
)
ripgrep = Path(f"{path}-rg")
ripgrep = (
path.with_name(f"{path.stem}-rg.exe")
if tag.startswith("win-")
else Path(f"{path}-rg")
)
if not ripgrep.is_file():
raise FileNotFoundError(
f"deepseek-harness-runtime-bin is missing the ripgrep sidecar at {ripgrep}. "
@@ -110,11 +116,11 @@ def resolve_bundled_launch_args(mode: str | None = None) -> tuple[str, ...]:
def _current_platform_tag() -> str:
plat = _PLATFORM_TAGS.get(sys.platform)
arch = _ARCH_TAGS.get(platform.machine().lower())
if plat is None or arch is None:
if plat is None or arch is None or (plat == "win" and arch != "x64"):
raise FileNotFoundError(
"no bundled DeepSeek Harness SDK runtime exists for this platform "
f"(sys.platform={sys.platform!r}, machine={platform.machine()!r}); supported: "
"linux/macos on x64/arm64. " + _EXE_ACQUISITION_HINT
"Linux x64/arm64, macOS arm64, and Windows x64. " + _EXE_ACQUISITION_HINT
)
return f"{plat}-{arch}"
+14 -5
View File
@@ -93,11 +93,14 @@ class HarnessClient:
self._start_stderr_thread()
def close(self) -> None:
"""Close the runtime after a bounded opportunity to flush durable state."""
proc = self._proc
if proc is None:
return
shutdown_completed = False
try:
self.request("shutdown", None, response_model=_ShutdownResponse, timeout_seconds=self.config.shutdown_timeout_seconds)
shutdown_completed = True
except Exception as exc:
self._stderr_lines.append(f"shutdown request failed: {exc}")
if proc.stdin:
@@ -105,16 +108,22 @@ class HarnessClient:
proc.stdin.close()
except Exception as exc:
self._stderr_lines.append(f"stdin close failed: {exc}")
if shutdown_completed:
try:
proc.wait(timeout=self.config.shutdown_timeout_seconds)
except subprocess.TimeoutExpired:
pass
if proc.poll() is None:
try:
proc.terminate()
except ProcessLookupError:
pass
try:
proc.wait(timeout=self.config.shutdown_timeout_seconds)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
if proc.poll() is None:
try:
proc.wait(timeout=self.config.shutdown_timeout_seconds)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
self._proc = None
self._fail_waiters(self._runtime_closed_error("DeepSeek Harness runtime closed"))
if self._reader_thread and self._reader_thread.is_alive():
+37
View File
@@ -783,6 +783,43 @@ for line in sys.stdin:
assert client._proc is None
def test_client_close_allows_eof_quiescence_after_shutdown_response(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
marker = tmp_path / "quiesced.txt"
script.write_text(
"""
import json
import os
from pathlib import Path
import sys
import time
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
elif msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
time.sleep(0.05)
Path(os.environ["QUIESCED_MARKER"]).write_text("quiesced")
""".strip()
)
client = HarnessClient(
HarnessConfig(
_launch_args=(sys.executable, str(script)),
env={"QUIESCED_MARKER": str(marker)},
shutdown_timeout_seconds=1,
)
)
client.start()
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
client.close()
assert marker.read_text() == "quiesced"
def test_initialize_failure_reaps_started_runtime(tmp_path: Path) -> None:
script = tmp_path / "rejecting_runtime.py"
script.write_text(
+17 -2
View File
@@ -62,6 +62,14 @@ def test_macos_wheel_tag_does_not_claim_unsupported_node_platforms() -> None:
assert build_python_release.PLATFORMS["macos-arm64"][1] == "deepseek-harness-sdk-runtime-macos-arm64"
def test_windows_wheel_tag_and_payload_are_x64_only() -> None:
assert build_python_release.PLATFORMS["win-x64"] == (
"win_amd64",
"deepseek-harness-sdk-runtime-win-x64.exe",
)
assert not any(name.startswith("win-") and name != "win-x64" for name in build_python_release.PLATFORMS)
def test_platform_manifest_rejects_incomplete_entries(tmp_path: Path) -> None:
manifest = tmp_path / "platforms.json"
manifest.write_text('{"macos-arm64":{"tag":"macosx_14_0_arm64"}}\n')
@@ -85,7 +93,10 @@ def test_stage_sdk_keeps_distribution_module_and_runtime_pin_distinct(tmp_path:
assert (destination / "src" / "deepseek_harness" / "__init__.py").is_file()
@pytest.mark.parametrize(("target", "with_helper"), [("linux-x64", False), ("macos-arm64", True)])
@pytest.mark.parametrize(
("target", "with_helper"),
[("linux-x64", False), ("macos-arm64", True), ("win-x64.exe", False)],
)
def test_stage_runtime_copies_platform_payload(
tmp_path: Path, target: str, with_helper: bool
) -> None:
@@ -93,7 +104,11 @@ def test_stage_runtime_copies_platform_payload(
executable.write_bytes(b"runtime")
executable.chmod(0o755)
expected = {executable.name: b"runtime"}
ripgrep = Path(f"{executable}-rg")
ripgrep = (
executable.with_name(f"{executable.stem}-rg.exe")
if executable.suffix == ".exe"
else Path(f"{executable}-rg")
)
ripgrep.write_bytes(b"ripgrep")
ripgrep.chmod(0o755)
expected[ripgrep.name] = b"ripgrep"
@@ -55,6 +55,30 @@ def test_runtime_requires_spawn_helper_only_on_macos(
assert runtime.bundled_runtime_path() == linux
def test_windows_runtime_uses_exe_payload_and_exe_sidecar(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
runtime_dir = tmp_path / "runtime"
runtime_dir.mkdir()
executable = runtime_dir / "deepseek-harness-sdk-runtime-win-x64.exe"
executable.touch()
(runtime_dir / "deepseek-harness-sdk-runtime-win-x64-rg.exe").touch()
monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path)
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "win-x64")
assert runtime.bundled_runtime_path() == executable
def test_current_platform_supports_windows_x64_only(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(runtime.sys, "platform", "win32")
monkeypatch.setattr(runtime.platform, "machine", lambda: "AMD64")
assert runtime._current_platform_tag() == "win-x64"
monkeypatch.setattr(runtime.platform, "machine", lambda: "ARM64")
with pytest.raises(FileNotFoundError, match="Windows x64"):
runtime._current_platform_tag()
def test_runtime_requires_ripgrep_sidecar(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
@@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { resolveLinuxNodePtyAddon } from './build-exe-for-python-sdk-native-pty.ts'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './build-exe-for-python-sdk-native-pty.ts'
const roots: string[] = []
@@ -35,6 +35,24 @@ describe('resolveLinuxNodePtyAddon', () => {
})
})
describe('resolveWindowsNodePtyAddons', () => {
it('requires both ConPTY addons from the x64 prebuild', () => {
const root = temporaryPackage()
const conpty = createAddon(root, 'prebuilds', 'win32-x64', 'conpty.node')
const consoleList = createAddon(root, 'prebuilds', 'win32-x64', 'conpty_console_list.node')
expect(resolveWindowsNodePtyAddons(root, 'x64')).toEqual([conpty, consoleList])
})
it('names every missing Windows addon', () => {
const root = temporaryPackage()
expect(() => resolveWindowsNodePtyAddons(root, 'x64')).toThrow(
`Windows node-pty addons are missing: ${join(root, 'prebuilds', 'win32-x64', 'conpty.node')}, ${join(root, 'prebuilds', 'win32-x64', 'conpty_console_list.node')}`,
)
})
})
function temporaryPackage(): string {
const root = mkdtempSync(join(tmpdir(), 'dsh-node-pty-addon-'))
roots.push(root)
@@ -21,3 +21,25 @@ export function resolveLinuxNodePtyAddon(
`build-exe-for-python-sdk: node-pty addon is absent from both ${built} and ${prebuilt}.`,
)
}
/**
* Require both node-pty addons used by the Windows ConPTY backend.
* @param packageDirectory - staged node-pty package directory.
* @param arch - Windows target architecture.
* @returns the existing addon paths in load order.
*/
export function resolveWindowsNodePtyAddons(
packageDirectory: string,
arch: 'x64',
): string[] {
const directory = join(packageDirectory, 'prebuilds', `win32-${arch}`)
const addons = [
join(directory, 'conpty.node'),
join(directory, 'conpty_console_list.node'),
]
const missing = addons.filter(path => !existsSync(path))
if (missing.length > 0) {
throw new Error(`build-exe-for-python-sdk: Windows node-pty addons are missing: ${missing.join(', ')}.`)
}
return addons
}
+81
View File
@@ -0,0 +1,81 @@
import { spawnSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
const root = resolve(import.meta.dirname, '..')
const script = resolve(root, 'scripts/build-exe-for-python-sdk.ts')
const temporaryDirectories: string[] = []
afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
rmSync(directory, { recursive: true, force: true })
}
})
function run(env: NodeJS.ProcessEnv, ...args: string[]) {
return spawnSync(process.execPath, ['--import', 'tsx/esm', script, ...args], {
cwd: root,
encoding: 'utf8',
env: isolatedPnpmEnvironment(env),
})
}
describe('Python runtime executable builder CLI', () => {
it('runs pnpm through its JavaScript entrypoint without a command shell', () => {
const result = run(
{ npm_execpath: 'C:\\tools\\pnpm.cjs' },
'--skip-build',
'--dry-run',
'--targets=node24-macos-arm64',
)
expect(result.status).toBe(0)
expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs run verify-runtime-closure`)
expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs --filter dsh-python-runtime-closure deploy`)
expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs dlx @yao-pkg/pkg@6.21.0`)
expect(result.stdout).not.toMatch(/pnpm\.cmd/i)
})
it('resolves the pnpm package behind a Windows command shim', () => {
const setup = mkdtempSync(join(tmpdir(), 'dsh-pnpm-home-'))
temporaryDirectories.push(setup)
const home = join(setup, 'node_modules', '.bin')
const entrypoint = join(setup, 'node_modules', 'pnpm', 'bin', 'pnpm.mjs')
mkdirSync(home, { recursive: true })
mkdirSync(dirname(entrypoint), { recursive: true })
writeFileSync(entrypoint, '')
const result = run(
{ npm_execpath: 'C:\\tools\\pnpm.cmd', PNPM_HOME: home },
'--skip-build',
'--dry-run',
'--targets=node24-macos-arm64',
)
expect(result.status).toBe(0)
expect(result.stdout).toContain(`${process.execPath} ${entrypoint} run verify-runtime-closure`)
expect(result.stdout).not.toMatch(/pnpm\.cmd/i)
})
it('rejects a Windows arm64 product before any build step', () => {
const result = run(
{ npm_execpath: 'C:\\tools\\pnpm.cjs' },
'--skip-build',
'--dry-run',
'--targets=node24-win-arm64',
)
expect(result.status).not.toBe(0)
expect(result.stderr).toContain('Windows supports x64 only')
expect(result.stdout).toBe('')
})
})
function isolatedPnpmEnvironment(overrides: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const environment = Object.fromEntries(
Object.entries(process.env).filter(([key]) => !['npm_execpath', 'pnpm_home'].includes(key.toLowerCase())),
)
return { ...environment, ...overrides }
}
+72 -20
View File
@@ -9,9 +9,9 @@
import { spawn } from 'node:child_process'
import { existsSync, statSync } from 'node:fs'
import { chmod, copyFile, cp, lstat, mkdir, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'
import { basename, dirname, join, resolve, sep } from 'node:path'
import { basename, dirname, extname, join, resolve, sep } from 'node:path'
import { parseArgs } from 'node:util'
import { resolveLinuxNodePtyAddon } from './build-exe-for-python-sdk-native-pty.ts'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './build-exe-for-python-sdk-native-pty.ts'
const root = resolve(import.meta.dirname, '..')
@@ -63,7 +63,7 @@ const ASSET_GLOBS = [
'node_modules/@deepseek-ai/dsh-skill-badge/assets/**/*',
]
const PLATFORMS = ['linux', 'macos'] as const
const PLATFORMS = ['linux', 'macos', 'win'] as const
const ARCHES = ['x64', 'arm64'] as const
type Platform = (typeof PLATFORMS)[number]
type Arch = (typeof ARCHES)[number]
@@ -83,10 +83,7 @@ class Target {
private constructor(
/** pkg Node range (`node<major>`). */
readonly nodeRange: string,
/**
* pkg platform tag. Windows is a documented non-goal
* (.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md).
*/
/** pkg platform tag. */
readonly platform: Platform,
/** pkg CPU tag. */
readonly arch: Arch,
@@ -117,6 +114,9 @@ class Target {
if (!isArch(arch)) {
throw new Error(`build-exe-for-python-sdk: target ${JSON.stringify(spec)}: arch must be one of ${ARCHES.join(', ')}, got ${JSON.stringify(arch)}.`)
}
if (platform === 'win' && arch !== 'x64') {
throw new Error(`build-exe-for-python-sdk: target ${JSON.stringify(spec)}: Windows supports x64 only.`)
}
return new Target(nodeRange, platform, arch)
}
@@ -125,7 +125,13 @@ class Target {
* @returns the host target; throws on an unsupported host platform or arch.
*/
static host(): Target {
const platform = process.platform === 'darwin' ? 'macos' : process.platform === 'linux' ? 'linux' : undefined
const platform = process.platform === 'darwin'
? 'macos'
: process.platform === 'linux'
? 'linux'
: process.platform === 'win32'
? 'win'
: undefined
if (platform === undefined) {
throw new Error(`build-exe-for-python-sdk: unsupported host platform ${process.platform}; pass --targets explicitly.`)
}
@@ -133,6 +139,9 @@ class Target {
if (arch === undefined) {
throw new Error(`build-exe-for-python-sdk: unsupported host arch ${process.arch}; pass --targets explicitly.`)
}
if (platform === 'win' && arch !== 'x64') {
throw new Error('build-exe-for-python-sdk: Windows supports x64 only; use an x64 Node process.')
}
return new Target(DEFAULT_NODE_RANGE, platform, arch)
}
}
@@ -200,7 +209,7 @@ class BuildCli {
return [
'Usage: pnpm exec tsx scripts/build-exe-for-python-sdk.ts [flags]',
'',
' --targets=<t1,t2,...> pkg targets, e.g. node24-linux-x64,node24-linux-arm64,node24-macos-arm64.',
' --targets=<t1,t2,...> pkg targets, e.g. node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64.',
' Default: the host platform only (on node24).',
' --skip-build skip `pnpm run build` (lib/ artifacts must already exist).',
' --dry-run print every command and config patch without executing.',
@@ -212,8 +221,27 @@ class BuildCli {
}
}
function pnpmBin(): string {
return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
function pnpmInvocation(args: string[]): [command: string, args: string[]] {
const entrypoint = process.env.npm_execpath?.trim()
if (entrypoint !== undefined && entrypoint !== '') {
const extension = extname(entrypoint).toLowerCase()
if (extension === '.js' || extension === '.cjs' || extension === '.mjs') {
return [process.execPath, [entrypoint, ...args]]
}
if (extension !== '.cmd') return [entrypoint, args]
}
const home = process.env.PNPM_HOME?.trim()
if (home !== undefined && home !== '') {
const packageBin = resolve(home, '..', 'pnpm', 'bin')
for (const filename of ['pnpm.mjs', 'pnpm.cjs']) {
const candidate = resolve(packageBin, filename)
if (existsSync(candidate)) return [process.execPath, [candidate, ...args]]
}
}
if (process.platform === 'win32') {
throw new Error('build-exe-for-python-sdk: pnpm must expose a JavaScript entrypoint through npm_execpath or PNPM_HOME on Windows.')
}
return ['pnpm', args]
}
/**
@@ -241,7 +269,7 @@ class SingleExeBuild {
/** Verify the closure before compiling or packaging. */
async verifyClosure(): Promise<void> {
await this.run('runtime dependency closure', pnpmBin(), ['run', 'verify-runtime-closure'])
await this.runPnpm('runtime dependency closure', ['run', 'verify-runtime-closure'])
}
/** Build all package artifacts unless `--skip-build` was passed. */
@@ -250,7 +278,7 @@ class SingleExeBuild {
console.log('build-exe-for-python-sdk: skipping pnpm run build (--skip-build)')
return
}
await this.run('build', pnpmBin(), ['run', 'build'])
await this.runPnpm('build', ['run', 'build'])
}
/** Clear and deploy the runtime closure into the node carrier. */
@@ -260,7 +288,7 @@ class SingleExeBuild {
}
if (this.cli.dryRun) console.log(`build-exe-for-python-sdk: [dry-run] rm -rf ${this.staging}`)
else await rm(this.staging, { recursive: true, force: true })
await this.run('deploy', pnpmBin(), [
await this.runPnpm('deploy', [
'--filter',
DEPLOY_ROOT_PACKAGE,
'deploy',
@@ -393,10 +421,11 @@ class SingleExeBuild {
* @returns the executable and ripgrep sidecar paths, plus the macOS spawn helper path when required.
*/
async pack(target: Target): Promise<string[]> {
const product = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
const productBase = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
const product = target.platform === 'win' ? `${productBase}.exe` : productBase
await this.prepareNativePty(target)
if (!this.cli.dryRun) await mkdir(this.outDir, { recursive: true })
await this.run(`pkg ${target.spec}`, pnpmBin(), [
await this.runPnpm(`pkg ${target.spec}`, [
'dlx',
PKG_SPEC,
this.staging,
@@ -424,16 +453,19 @@ class SingleExeBuild {
/** Copy the target ripgrep binary beside the executable so Node can spawn it outside pkg's virtual filesystem. */
private async copyRipgrepSidecar(target: Target, product: string): Promise<string> {
const platform = target.platform === 'macos' ? 'darwin' : target.platform
const platform = target.platform === 'macos' ? 'darwin' : target.platform === 'win' ? 'win32' : target.platform
const executable = target.platform === 'win' ? 'rg.exe' : 'rg'
const source = join(
this.staging,
'node_modules',
'@vscode',
`ripgrep-${platform}-${target.arch}`,
'bin',
'rg',
executable,
)
const destination = `${product}-rg`
const destination = target.platform === 'win'
? `${product.slice(0, -'.exe'.length)}-rg.exe`
: `${product}-rg`
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] cp ${source} ${destination}`)
return destination
@@ -455,7 +487,6 @@ class SingleExeBuild {
const stagedBuild = join(this.staging, 'node_modules', 'node-pty', '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 })
if (target.platform !== 'linux') return
const packageDirectory = join(
root,
'packages',
@@ -464,6 +495,21 @@ class SingleExeBuild {
'node_modules',
'node-pty',
)
if (target.platform === 'win') {
if (target.arch !== 'x64') {
throw new Error('build-exe-for-python-sdk: Windows supports x64 only.')
}
const host = Target.host()
if (target.platform !== host.platform || target.arch !== host.arch) {
throw new Error(
'build-exe-for-python-sdk: build the Windows runtime under x64 Node on its target host; '
+ `target ${target.platform}-${target.arch} does not match host ${host.platform}-${host.arch}.`,
)
}
resolveWindowsNodePtyAddons(join(this.staging, 'node_modules', 'node-pty'), target.arch)
return
}
if (target.platform !== 'linux') return
const destination = join(stagedBuild, 'Release', 'pty.node')
const source = resolveLinuxNodePtyAddon(packageDirectory, target.arch)
if (this.cli.dryRun) {
@@ -553,6 +599,12 @@ class SingleExeBuild {
})
})
}
/** Run pnpm through its JavaScript entrypoint when the caller supplies one. */
private async runPnpm(label: string, args: string[]): Promise<void> {
const [command, invocationArgs] = pnpmInvocation(args)
await this.run(label, command, invocationArgs)
}
}
async function main(): Promise<void> {
+11 -7
View File
@@ -47,9 +47,12 @@ def load_platforms(path: Path = PLATFORM_MANIFEST) -> dict[str, tuple[str, str]]
PLATFORMS = load_platforms()
def runtime_suffixes(executable_name: str) -> tuple[str, ...]:
suffixes = ("", "-rg")
return (*suffixes, "-spawn-helper") if "-macos-" in executable_name else suffixes
def runtime_filenames(executable_name: str) -> tuple[str, ...]:
"""Return the exact platform payload names for one runtime executable."""
if executable_name.endswith(".exe"):
return (executable_name, f"{executable_name.removesuffix('.exe')}-rg.exe")
names = (executable_name, f"{executable_name}-rg")
return (*names, f"{executable_name}-spawn-helper") if "-macos-" in executable_name else names
def main() -> None:
@@ -210,8 +213,9 @@ def stage_runtime(destination: Path, version: str, executable: Path, executable_
rewrite_version(destination / "pyproject.toml", version)
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)
for suffix in runtime_suffixes(executable_name):
shutil.copy2(Path(f"{executable}{suffix}"), runtime_dir / f"{executable_name}{suffix}")
source_directory = executable.parent
for filename in runtime_filenames(executable_name):
shutil.copy2(source_directory / filename, runtime_dir / filename)
def verify_wheel(
@@ -250,13 +254,13 @@ def verify_wheel(
]
if package == "runtime":
assert platform is not None
expected_files = [f"{platform[1]}{suffix}" for suffix in runtime_suffixes(platform[1])]
expected_files = sorted(runtime_filenames(platform[1]))
found_files = sorted(Path(name).name for name in runtime_files)
if found_files != expected_files:
raise RuntimeError(f"{wheel} runtime payload must be {expected_files}, found {found_files}")
for runtime_file in runtime_files:
mode = archive.getinfo(runtime_file).external_attr >> 16
if mode & stat.S_IXUSR == 0:
if platform[0] != "win_amd64" and mode & stat.S_IXUSR == 0:
raise RuntimeError(f"{wheel} runtime executable lost its executable bit: {runtime_file}")
elif runtime_files:
raise RuntimeError(f"SDK wheel unexpectedly contains runtime executables: {runtime_files}")
+8 -3
View File
@@ -21,6 +21,7 @@ const platforms = {
'linux-x64': { tag: 'manylinux_2_28_x86_64', executable: 'runtime-linux-x64' },
'linux-arm64': { tag: 'manylinux_2_28_aarch64', executable: 'runtime-linux-arm64' },
'macos-arm64': { tag: 'macosx_14_0_arm64', executable: 'runtime-macos-arm64' },
'win-x64': { tag: 'win_amd64', executable: 'runtime-win-x64.exe' },
}
function workspace(root: string, name: string, manifest: Record<string, unknown>): void {
@@ -35,7 +36,7 @@ afterEach(() => {
})
describe('verifyRuntimeClosure', () => {
it('requires only plugins active for a Linux or macOS target', async () => {
it('requires only plugins active for each published target', async () => {
const root = fixture({
'python/sdk-runtime/package.json': { name: 'runtime', dependencies: { '@scope/shared': 'workspace:^' } },
'python/sdk-runtime/platforms.json': platforms,
@@ -52,6 +53,9 @@ describe('verifyRuntimeClosure', () => {
- id: macos
name: '@scope/macos'
disabled: !!js process.platform !== 'darwin'
- id: windows
name: '@scope/windows'
disabled: !!js process.platform !== 'win32'
`,
})
@@ -61,6 +65,7 @@ describe('verifyRuntimeClosure', () => {
expect(result.failures).toEqual([
'standard preset -> @scope/linux (linux-arm64, linux-x64)',
'standard preset -> @scope/macos (macos-arm64)',
'standard preset -> @scope/windows (win-x64)',
])
})
@@ -78,7 +83,7 @@ describe('verifyRuntimeClosure', () => {
const result = await verifyRuntimeClosure(root)
expect(result.failures).toEqual([
'standard preset -> @scope/conditional (linux-arm64, linux-x64, macos-arm64)',
'standard preset -> @scope/conditional (linux-arm64, linux-x64, macos-arm64, win-x64)',
])
})
@@ -112,7 +117,7 @@ describe('verifyRuntimeClosure', () => {
const result = await verifyRuntimeClosure(root)
expect(result.failures).toEqual([
'standard preset -> @scope/plugin [runtime dependency is "1.2.3"; expected workspace:] (linux-arm64, linux-x64, macos-arm64)',
'standard preset -> @scope/plugin [runtime dependency is "1.2.3"; expected workspace:] (linux-arm64, linux-x64, macos-arm64, win-x64)',
])
})
+1
View File
@@ -181,6 +181,7 @@ function disabledOnPlatform(value: unknown, processPlatform: string): boolean {
function processPlatformForTarget(target: string): string {
if (target.startsWith('linux-')) return 'linux'
if (target.startsWith('macos-')) return 'darwin'
if (target.startsWith('win-')) return 'win32'
throw new Error(`verify-runtime-closure: unsupported runtime target ${JSON.stringify(target)}`)
}