Files
codex/scripts/codex_package/layout.py
Michael Bolin c11e772789 package: include zsh fork in Codex package
Summary:
- add a checked-in codex-zsh DotSlash manifest for supported Unix targets
- fetch the matching zsh fork artifact during package builds and install it at codex-resources/zsh/bin/zsh when available
- expose the bundled zsh fork through install-context and use it as the runtime zsh path for packaged installs
- remove the user/profile zsh_path config override now that the fork is package-managed
- update app-server zsh-fork integration tests to spawn from a temporary package layout instead of configuring zsh_path directly
- use copyfile for package executable staging so local package smoke tests do not preserve platform-specific source metadata

Test Plan:
- just fmt
- just write-config-schema
- just fix -p codex-config -p codex-core -p codex-exec
- just fix -p codex-app-server
- python3 -m py_compile scripts/codex_package/cli.py scripts/codex_package/layout.py scripts/codex_package/zsh.py
- python3 -m unittest discover scripts/codex_package
- cargo test -p codex-core default_zsh_path_sets_runtime_zsh_path
- cargo test -p codex-core session_new_fails_when_zsh_fork_enabled_without_packaged_zsh
- cargo test -p codex-core restricted_read_implicitly_allows_helper_executables
- cargo test -p codex-config
- cargo test -p codex-app-server turn_start_shell_zsh_fork_executes_command_v2
- cargo test -p codex-app-server turn_start_shell_zsh_fork
2026-05-22 15:46:31 -07:00

180 lines
5.4 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
from .zsh import ZSH_RESOURCE_PATH
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.rg_bin, path_dir / spec.rg_name, is_windows=spec.is_windows)
if inputs.zsh_bin is not None:
copy_executable(
inputs.zsh_bin,
resources_dir / ZSH_RESOURCE_PATH,
is_windows=False,
)
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}",
"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,
*,
include_zsh: bool,
) -> 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)}",
"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("codex-path") / spec.rg_name,
]
executable_files = list(required_files)
if include_zsh:
zsh_path = Path("codex-resources") / ZSH_RESOURCE_PATH
required_files.append(zsh_path)
executable_files.append(zsh_path)
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)