mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
build: add Codex package builder (#23513)
## Why Codex CLI packaging is currently split across npm staging, standalone installers, and release bundle creation, which makes it hard to define and validate a single valid package directory. This adds the first standalone package builder so later release paths can converge on the same canonical layout. ## What changed - Added `scripts/build_codex_package.py` as the stable executable wrapper around `scripts/codex_package`. - Added modules for CLI parsing, target metadata, grouped cargo builds, package layout validation, and archive writing. - The builder creates a package directory with `codex-package.json`, `bin/`, `codex-resources/`, and `codex-path`, and can serialize it as `.tar.gz`, `.tar.zst`, or `.zip`. - Source-built artifacts are built by one grouped `cargo build`: `codex` for all targets, `bwrap` for Linux, and the Windows sandbox helpers for Windows. `rg` remains an input because it is vendored from upstream rather than built from this repo. - Added `scripts/codex_package/README.md` to document the package layout, source-built artifacts, and cargo profile behavior. ## Verification - Ran wrapper/module syntax compilation. - Ran `scripts/build_codex_package.py --help` from `/private/tmp`. - Ran fake-cargo package/archive builds for macOS, Linux, and Windows target layouts, including an assertion that generated tar archives contain no duplicate member names. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23513). * #23526 * __->__ #23513
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""Supported package targets and default binary discovery."""
|
||||
|
||||
import stat
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parents[1]
|
||||
REPO_ROOT = SCRIPT_DIR.parent
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TargetSpec:
|
||||
target: str
|
||||
is_windows: bool
|
||||
is_linux: bool
|
||||
|
||||
@property
|
||||
def exe_suffix(self) -> str:
|
||||
return ".exe" if self.is_windows else ""
|
||||
|
||||
@property
|
||||
def codex_name(self) -> str:
|
||||
return f"codex{self.exe_suffix}"
|
||||
|
||||
@property
|
||||
def rg_name(self) -> str:
|
||||
return f"rg{self.exe_suffix}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PackageInputs:
|
||||
codex_bin: Path
|
||||
rg_bin: Path
|
||||
bwrap_bin: Path | None
|
||||
codex_command_runner_bin: Path | None
|
||||
codex_windows_sandbox_setup_bin: Path | None
|
||||
|
||||
|
||||
TARGET_SPECS: dict[str, TargetSpec] = {
|
||||
"x86_64-unknown-linux-musl": TargetSpec(
|
||||
target="x86_64-unknown-linux-musl",
|
||||
is_windows=False,
|
||||
is_linux=True,
|
||||
),
|
||||
"aarch64-unknown-linux-musl": TargetSpec(
|
||||
target="aarch64-unknown-linux-musl",
|
||||
is_windows=False,
|
||||
is_linux=True,
|
||||
),
|
||||
"x86_64-apple-darwin": TargetSpec(
|
||||
target="x86_64-apple-darwin",
|
||||
is_windows=False,
|
||||
is_linux=False,
|
||||
),
|
||||
"aarch64-apple-darwin": TargetSpec(
|
||||
target="aarch64-apple-darwin",
|
||||
is_windows=False,
|
||||
is_linux=False,
|
||||
),
|
||||
"x86_64-pc-windows-msvc": TargetSpec(
|
||||
target="x86_64-pc-windows-msvc",
|
||||
is_windows=True,
|
||||
is_linux=False,
|
||||
),
|
||||
"aarch64-pc-windows-msvc": TargetSpec(
|
||||
target="aarch64-pc-windows-msvc",
|
||||
is_windows=True,
|
||||
is_linux=False,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def resolve_rg_bin(spec: TargetSpec, rg_bin: Path | None) -> Path:
|
||||
return resolve_input_path(
|
||||
rg_bin,
|
||||
default_rg_candidates(spec),
|
||||
"ripgrep executable",
|
||||
"--rg-bin",
|
||||
)
|
||||
|
||||
|
||||
def default_rg_candidates(spec: TargetSpec) -> list[Path]:
|
||||
return [
|
||||
REPO_ROOT / "codex-cli" / "vendor" / spec.target / "path" / spec.rg_name,
|
||||
]
|
||||
|
||||
|
||||
def resolve_input_path(
|
||||
explicit_path: Path | None,
|
||||
default_candidates: list[Path],
|
||||
description: str,
|
||||
flag_name: str,
|
||||
) -> Path:
|
||||
if explicit_path is not None:
|
||||
path = explicit_path.resolve()
|
||||
if not path.is_file():
|
||||
raise RuntimeError(f"{description} does not exist: {path}")
|
||||
if not is_executable(path):
|
||||
raise RuntimeError(f"{description} is not executable: {path}")
|
||||
return path
|
||||
|
||||
for candidate in default_candidates:
|
||||
if candidate.is_file():
|
||||
return candidate.resolve()
|
||||
|
||||
candidates = "\n".join(f" - {candidate}" for candidate in default_candidates)
|
||||
raise RuntimeError(
|
||||
f"Could not find {description}. Pass {flag_name}, or create one of:\n{candidates}"
|
||||
)
|
||||
|
||||
|
||||
def is_executable(path: Path) -> bool:
|
||||
return bool(path.stat().st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH))
|
||||
Reference in New Issue
Block a user