mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
## What changed - Build and bundle the standalone executor in `bin/` for both package variants, and advertise its path through `execServer` in `codex-package.json`. - Include the executor in Cargo and Bazel release packaging, macOS signing and notarization, and Windows signing verification. Preserve prebuilt signed binaries during packaging. - Make installers detect a missing advertised executor while continuing to accept older packages without it. ## Testing Add coverage for executor archive contents and executable permissions, signed binary preservation, and macOS entitlement verification. Add package smoke tests for initialization, version reporting, filesystem and process sandbox enforcement, and startup error parity with `codex exec-server`. GitOrigin-RevId: 5c0027fc19f16cf9c2a5bb52dbd065a9776bc2af
181 lines
5.6 KiB
Python
181 lines
5.6 KiB
Python
"""Canonical Codex package directory layout."""
|
|
|
|
import json
|
|
import shutil
|
|
import stat
|
|
from pathlib import Path
|
|
|
|
from .targets import PackageInputs
|
|
from .targets import PackageVariant
|
|
from .targets import TargetSpec
|
|
|
|
|
|
LAYOUT_VERSION = 1
|
|
|
|
|
|
def prepare_package_dir(package_dir: Path, *, force: bool) -> None:
|
|
if package_dir.exists():
|
|
if not package_dir.is_dir():
|
|
raise RuntimeError(
|
|
f"Package output exists and is not a directory: {package_dir}"
|
|
)
|
|
if any(package_dir.iterdir()):
|
|
if not force:
|
|
raise RuntimeError(
|
|
f"Package output directory is not empty: {package_dir}. "
|
|
"Pass --force to replace it."
|
|
)
|
|
shutil.rmtree(package_dir)
|
|
|
|
package_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def build_package_dir(
|
|
package_dir: Path,
|
|
version: str,
|
|
variant: PackageVariant,
|
|
spec: TargetSpec,
|
|
inputs: PackageInputs,
|
|
) -> None:
|
|
bin_dir = package_dir / "bin"
|
|
resources_dir = package_dir / "codex-resources"
|
|
path_dir = package_dir / "codex-path"
|
|
bin_dir.mkdir()
|
|
resources_dir.mkdir()
|
|
path_dir.mkdir()
|
|
|
|
entrypoint_name = variant.entrypoint_name(spec)
|
|
copy_executable(
|
|
inputs.entrypoint_bin,
|
|
bin_dir / entrypoint_name,
|
|
is_windows=spec.is_windows,
|
|
)
|
|
copy_executable(
|
|
inputs.code_mode_host_bin,
|
|
bin_dir / f"codex-code-mode-host{spec.exe_suffix}",
|
|
is_windows=spec.is_windows,
|
|
)
|
|
copy_executable(
|
|
inputs.exec_server_bin,
|
|
bin_dir / f"codex-exec-server{spec.exe_suffix}",
|
|
is_windows=spec.is_windows,
|
|
)
|
|
copy_executable(inputs.rg_bin, path_dir / spec.rg_name, is_windows=spec.is_windows)
|
|
|
|
if inputs.bwrap_bin is not None:
|
|
copy_executable(inputs.bwrap_bin, resources_dir / "bwrap", is_windows=False)
|
|
|
|
if inputs.codex_command_runner_bin is not None:
|
|
copy_executable(
|
|
inputs.codex_command_runner_bin,
|
|
resources_dir / "codex-command-runner.exe",
|
|
is_windows=True,
|
|
)
|
|
|
|
if inputs.codex_windows_sandbox_setup_bin is not None:
|
|
copy_executable(
|
|
inputs.codex_windows_sandbox_setup_bin,
|
|
resources_dir / "codex-windows-sandbox-setup.exe",
|
|
is_windows=True,
|
|
)
|
|
|
|
metadata = {
|
|
"layoutVersion": LAYOUT_VERSION,
|
|
"version": version,
|
|
"target": spec.target,
|
|
"variant": variant.name,
|
|
"entrypoint": f"bin/{entrypoint_name}",
|
|
"execServer": f"bin/codex-exec-server{spec.exe_suffix}",
|
|
"resourcesDir": "codex-resources",
|
|
"pathDir": "codex-path",
|
|
}
|
|
write_json(package_dir / "codex-package.json", metadata)
|
|
|
|
|
|
def validate_package_dir(
|
|
package_dir: Path,
|
|
variant: PackageVariant,
|
|
spec: TargetSpec,
|
|
) -> None:
|
|
required_dirs = [
|
|
Path("bin"),
|
|
Path("codex-resources"),
|
|
Path("codex-path"),
|
|
]
|
|
for relative_dir in required_dirs:
|
|
path = package_dir / relative_dir
|
|
if not path.is_dir():
|
|
raise RuntimeError(f"Missing package directory: {relative_dir}")
|
|
|
|
metadata_path = package_dir / "codex-package.json"
|
|
if not metadata_path.is_file():
|
|
raise RuntimeError("Missing package metadata: codex-package.json")
|
|
|
|
with open(metadata_path, encoding="utf-8") as fh:
|
|
metadata = json.load(fh)
|
|
|
|
expected_metadata = {
|
|
"layoutVersion": LAYOUT_VERSION,
|
|
"target": spec.target,
|
|
"variant": variant.name,
|
|
"entrypoint": f"bin/{variant.entrypoint_name(spec)}",
|
|
"execServer": f"bin/codex-exec-server{spec.exe_suffix}",
|
|
"resourcesDir": "codex-resources",
|
|
"pathDir": "codex-path",
|
|
}
|
|
for key, expected in expected_metadata.items():
|
|
actual = metadata.get(key)
|
|
if actual != expected:
|
|
raise RuntimeError(
|
|
f"Invalid package metadata field {key!r}: expected {expected!r}, got {actual!r}"
|
|
)
|
|
|
|
required_files = [
|
|
Path("bin") / variant.entrypoint_name(spec),
|
|
Path("bin") / f"codex-code-mode-host{spec.exe_suffix}",
|
|
Path("bin") / f"codex-exec-server{spec.exe_suffix}",
|
|
Path("codex-path") / spec.rg_name,
|
|
]
|
|
executable_files = list(required_files)
|
|
|
|
if spec.is_linux:
|
|
required_files.append(Path("codex-resources") / "bwrap")
|
|
executable_files.append(Path("codex-resources") / "bwrap")
|
|
|
|
if spec.is_windows:
|
|
required_files.extend(
|
|
[
|
|
Path("codex-resources") / "codex-command-runner.exe",
|
|
Path("codex-resources") / "codex-windows-sandbox-setup.exe",
|
|
]
|
|
)
|
|
|
|
for relative_file in required_files:
|
|
path = package_dir / relative_file
|
|
if not path.is_file():
|
|
raise RuntimeError(f"Missing package file: {relative_file}")
|
|
|
|
if not spec.is_windows:
|
|
for relative_file in executable_files:
|
|
path = package_dir / relative_file
|
|
if not is_executable(path):
|
|
raise RuntimeError(f"Package file is not executable: {relative_file}")
|
|
|
|
|
|
def copy_executable(src: Path, dest: Path, *, is_windows: bool) -> None:
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(src, dest)
|
|
if not is_windows:
|
|
mode = dest.stat().st_mode
|
|
dest.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
|
|
|
|
def write_json(path: Path, value: object) -> None:
|
|
with open(path, "w", encoding="utf-8") as out:
|
|
json.dump(value, out, indent=2)
|
|
out.write("\n")
|
|
|
|
|
|
def is_executable(path: Path) -> bool:
|
|
return bool(path.stat().st_mode & stat.S_IXUSR)
|