fix: relocate long-path runtime console launchers
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e387-534d-70e3-ad53-4ea05676db8c
This commit is contained in:
parent
505e82db74
commit
81b5fcff8e
3 changed files with 113 additions and 10 deletions
|
|
@ -11,6 +11,7 @@ import hashlib
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -43,6 +44,49 @@ def install_claude(output: Path, source: Path, sha256: str, version: str) -> dic
|
|||
"expected_version": version, "source": str(source)}
|
||||
|
||||
|
||||
def relocate_entrypoints(output: Path) -> list[str]:
|
||||
"""Rewrite direct shebangs and uv/distlib long-path shell launchers.
|
||||
|
||||
Long build paths (or spaces) produce a three-line shell/Python trampoline.
|
||||
Its interpreter must also refer to the fixed in-sandbox runtime mount.
|
||||
Recognize generated shapes, never arbitrary shell source or binary data.
|
||||
"""
|
||||
relocated = []
|
||||
python_names = [p.name for p in (output / "bin").iterdir()
|
||||
if re.fullmatch(r"python(?:[0-9]+(?:\.[0-9]+)*)?", p.name)]
|
||||
interpreters = [str(output / "bin" / name) for name in python_names]
|
||||
direct = {("#!" + name).encode() for name in interpreters}
|
||||
trampolines = {
|
||||
("'''exec' " + quoted + ' "$0" "$@"').encode()
|
||||
for name in interpreters
|
||||
for quoted in (shlex.quote(name), "'" + name.replace("'", "'\\''") + "'")
|
||||
}
|
||||
for path in sorted((output / "bin").iterdir()):
|
||||
if path.is_symlink() or not path.is_file():
|
||||
continue
|
||||
with path.open("rb") as stream:
|
||||
header = stream.read(8192)
|
||||
lines = header.splitlines()
|
||||
skip = 0
|
||||
if lines and lines[0] in direct:
|
||||
skip = 1
|
||||
elif len(lines) >= 3 and lines[0] == b"#!/bin/sh":
|
||||
if lines[1] in trampolines and lines[2] == b"' '''":
|
||||
skip = 3
|
||||
elif lines[1].startswith(b"'''exec'") and str(output).encode() in lines[1]:
|
||||
raise ValueError("unsupported build-host Python trampoline")
|
||||
if skip:
|
||||
body = path.read_bytes().split(b"\n", skip)[skip]
|
||||
path.write_bytes(f"#!{RUNTIME_MOUNT}/bin/python3\n".encode() + body)
|
||||
relocated.append(path.name)
|
||||
for required in ("rein-aharness", "glas-harness", "sandboxer"):
|
||||
path = output / "bin" / required
|
||||
expected = f"#!{RUNTIME_MOUNT}/bin/python3".encode()
|
||||
if path.exists() and path.read_bytes().split(b"\n", 1)[0] != expected:
|
||||
raise ValueError(f"unrelocated governed entrypoint: {required}")
|
||||
return relocated
|
||||
|
||||
|
||||
def verify_source_files(site: Path, mappings: list[tuple[Path, str, str]]) -> dict:
|
||||
"""Refuse stale/missing/extra package contents even when versions match."""
|
||||
expected = {}
|
||||
|
|
@ -103,15 +147,7 @@ def build(output: Path, rein_source: Path, llm_source: Path,
|
|||
else:
|
||||
checked(["uv", "pip", "install", "--python", str(output / "bin/python3"),
|
||||
str(rein_source), str(llm_source)])
|
||||
# Console entrypoints must reference the in-sandbox mount, not the build host.
|
||||
for path in (output / "bin").iterdir():
|
||||
if not path.is_file() or path.is_symlink():
|
||||
continue
|
||||
with path.open("rb") as stream:
|
||||
first_line = stream.readline(4096)
|
||||
if first_line.startswith(f"#!{output}/bin/python".encode()):
|
||||
body = path.read_bytes().partition(b"\n")[2]
|
||||
path.write_bytes(f"#!{RUNTIME_MOUNT}/bin/python3\n".encode() + body)
|
||||
entrypoints = relocate_entrypoints(output)
|
||||
metadata = json.loads(checked([
|
||||
str(output / "bin/python3"), "-c",
|
||||
"import importlib.metadata,json,platform; "
|
||||
|
|
@ -120,6 +156,7 @@ def build(output: Path, rein_source: Path, llm_source: Path,
|
|||
]))
|
||||
if claude_binary is not None:
|
||||
metadata["claude"] = install_claude(output, claude_binary, claude_sha256, claude_version)
|
||||
metadata["relocated_entrypoints"] = entrypoints
|
||||
metadata["source_revisions"] = revisions
|
||||
if owner_runtime:
|
||||
lock = json.loads((rein_source / "deploy/runtime-contract-lock.json").read_text())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue