mirror of
https://github.com/openai/codex.git
synced 2026-09-11 20:36:49 +00:00
## Why The helper's native bindings require shared libraries before entering `main`, including for lifecycle calls. Helper-only packages cannot satisfy that requirement. ## What changed Make `--runtime` mandatory in `third_party/voice/assemble_package.py` and require the `runtime` argument in `assemble()`. Always validate and include the prepared runtime, and update the voice host documentation to describe the requirement. ## Testing Update assembly tests to supply prepared runtimes and verify that package manifests include runtime library hashes and `runtime.json` alongside the helper. GitOrigin-RevId: ab5abf80ae935b4ace833fa59b74f670ba62c632
141 lines
5.2 KiB
Python
141 lines
5.2 KiB
Python
"""Add a helper and its prepared runtime to a fresh private Codex package."""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import sys
|
|
|
|
# Import only this script's siblings, including under PYTHONSAFEPATH.
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from package_runtime import runtime_files
|
|
from runtime import digest
|
|
|
|
|
|
def assemble(
|
|
package: Path,
|
|
helper: Path,
|
|
voice_target: str,
|
|
commit: str,
|
|
output: Path,
|
|
*,
|
|
runtime: Path,
|
|
):
|
|
package, helper = package.resolve(strict=True), helper.resolve(strict=True)
|
|
output = output.absolute()
|
|
if (
|
|
output.exists()
|
|
or output.is_symlink()
|
|
or output.resolve().is_relative_to(package)
|
|
):
|
|
raise ValueError("output must be fresh and outside the input package")
|
|
if not re.fullmatch(r"[0-9a-f]{40}", commit):
|
|
raise ValueError(
|
|
"a full build commit is required; dev builds are not distributable"
|
|
)
|
|
metadata = json.loads((package / "codex-package.json").read_text())
|
|
app_target = metadata["target"]
|
|
targets = {
|
|
f"{arch}-{suffix}": f"{arch}-{suffix.replace('musl', 'gnu')}"
|
|
for arch in ("aarch64", "x86_64")
|
|
for suffix in (
|
|
"apple-darwin",
|
|
"unknown-linux-gnu",
|
|
"unknown-linux-musl",
|
|
"pc-windows-msvc",
|
|
)
|
|
}
|
|
if targets.get(app_target) != voice_target:
|
|
raise ValueError("incompatible app and helper targets")
|
|
suffix = ".exe" if app_target.endswith("windows-msvc") else ""
|
|
entrypoint = f"bin/codex{suffix}"
|
|
expected = {
|
|
"layoutVersion": 1,
|
|
"variant": "codex",
|
|
"entrypoint": entrypoint,
|
|
"resourcesDir": "codex-resources",
|
|
"pathDir": "codex-path",
|
|
}
|
|
if any(metadata.get(key) != value for key, value in expected.items()):
|
|
raise ValueError("input is not a canonical Codex package")
|
|
if not metadata["version"].endswith(f"+{commit}"):
|
|
raise ValueError("package version does not match the declared build")
|
|
if (package / "codex-resources/voice").exists():
|
|
raise ValueError("input already contains voice resources")
|
|
for path in package.rglob("*"):
|
|
if path.is_symlink() or not (path.is_file() or path.is_dir()):
|
|
raise ValueError("package inputs must be regular files or directories")
|
|
if not helper.is_file() or not (package / entrypoint).is_file():
|
|
raise ValueError("helper and app entrypoint must be regular files")
|
|
if not suffix and not helper.stat().st_mode & 0o111:
|
|
raise ValueError("helper is not executable")
|
|
runtime = runtime.resolve(strict=True)
|
|
if any(parent.samefile(package) for parent in (runtime, *runtime.parents)):
|
|
raise ValueError("runtime must be outside the input package")
|
|
if any(
|
|
parent.exists() and parent.samefile(runtime)
|
|
for parent in output.resolve().parents
|
|
):
|
|
raise ValueError("output must be outside the runtime input")
|
|
inputs = runtime_files(runtime, voice_target)
|
|
output.mkdir() # Exclusive creation: never clean or overwrite a pre-existing output.
|
|
try:
|
|
shutil.copytree(package, output, dirs_exist_ok=True)
|
|
relative_helper = f"codex-resources/voice/bin/codex-voice-host{suffix}"
|
|
destination = output / relative_helper
|
|
destination.parent.mkdir(parents=True)
|
|
shutil.copy2(helper, destination)
|
|
for relative, expected_digest in inputs.items():
|
|
copied = output / "codex-resources/voice" / relative
|
|
copied.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(runtime / relative, copied)
|
|
if digest(copied) != expected_digest:
|
|
raise ValueError("runtime file changed during copying")
|
|
digests = {}
|
|
for relative in (entrypoint, relative_helper):
|
|
with (output / relative).open("rb") as source:
|
|
digests[relative] = hashlib.file_digest(source, "sha256").hexdigest()
|
|
digests.update(
|
|
{f"codex-resources/voice/{name}": value for name, value in inputs.items()}
|
|
)
|
|
manifest = {
|
|
"schemaVersion": 1,
|
|
"buildCommit": commit,
|
|
"appTarget": app_target,
|
|
"voiceTarget": voice_target,
|
|
"appVersion": metadata["version"],
|
|
"sha256": digests,
|
|
}
|
|
(destination.parent.parent / "manifest.json").write_text(
|
|
json.dumps(manifest, indent=2) + "\n", encoding="utf-8"
|
|
)
|
|
except BaseException:
|
|
shutil.rmtree(output)
|
|
raise
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--package", type=Path, required=True)
|
|
parser.add_argument("--helper", type=Path, required=True)
|
|
parser.add_argument("--voice-target", required=True)
|
|
parser.add_argument("--build-commit", required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
parser.add_argument(
|
|
"--runtime",
|
|
type=Path,
|
|
required=True,
|
|
help="prepared development runtime required by the helper",
|
|
)
|
|
args = parser.parse_args()
|
|
assemble(
|
|
args.package,
|
|
args.helper,
|
|
args.voice_target,
|
|
args.build_commit,
|
|
args.output,
|
|
runtime=args.runtime,
|
|
)
|