mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
build: package prebuilt Codex entrypoints
This commit is contained in:
@@ -17,6 +17,7 @@ const MANAGED_CONFIG_PATH_ENV_VAR: &str = "CODEX_APP_SERVER_MANAGED_CONFIG_PATH"
|
||||
const DISABLE_MANAGED_CONFIG_ENV_VAR: &str = "CODEX_APP_SERVER_DISABLE_MANAGED_CONFIG";
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, version, about = "Codex app-server")]
|
||||
struct AppServerArgs {
|
||||
/// Transport endpoint URL. Supported values: `stdio://` (default),
|
||||
/// `unix://`, `unix://PATH`, `ws://IP:PORT`, `off`.
|
||||
|
||||
@@ -10,7 +10,7 @@ The builder creates a canonical Codex package directory:
|
||||
.
|
||||
├── codex-package.json
|
||||
├── bin
|
||||
│ └── codex[.exe]
|
||||
│ └── <entrypoint>[.exe]
|
||||
├── codex-resources
|
||||
│ ├── bwrap # Linux only
|
||||
│ ├── codex-command-runner.exe # Windows only
|
||||
@@ -28,18 +28,24 @@ artifacts; pass a GNU Linux target explicitly for native glibc local builds. If
|
||||
`--package-dir` is omitted, the builder creates a new temporary directory and
|
||||
prints its path after the package is built.
|
||||
|
||||
The `--variant` flag selects the package entrypoint. Supported variants are
|
||||
`codex` and `codex-app-server`. The `version` field in `codex-package.json` is
|
||||
derived by running the selected entrypoint with `--version`.
|
||||
|
||||
## Source-built artifacts
|
||||
|
||||
Artifacts built from this repository are always built by the package builder in
|
||||
one grouped `cargo build` command per package:
|
||||
one grouped `cargo build` command per package when they are needed:
|
||||
|
||||
- all targets: `codex`
|
||||
- all targets: the selected entrypoint, unless `--entrypoint-bin` is provided
|
||||
- Linux targets: `bwrap`
|
||||
- Windows targets: `codex-command-runner` and `codex-windows-sandbox-setup`
|
||||
|
||||
The default cargo profile is `dev-small` because local iteration should favor
|
||||
fast, small builds. Release jobs should pass `--cargo-profile release` and an
|
||||
explicit target.
|
||||
explicit target. Release jobs that already built and signed/notarized the
|
||||
entrypoint should pass `--entrypoint-bin` so the package contains that exact
|
||||
binary instead of rebuilding it.
|
||||
|
||||
`rg` is not built from this repository, so the builder fetches it from the
|
||||
DotSlash manifest at `codex-cli/bin/rg`. Downloaded archives are cached under
|
||||
|
||||
@@ -6,6 +6,7 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .targets import REPO_ROOT
|
||||
from .targets import PackageVariant
|
||||
from .targets import TargetSpec
|
||||
|
||||
|
||||
@@ -14,7 +15,7 @@ CODEX_RS_ROOT = REPO_ROOT / "codex-rs"
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SourceBuildOutputs:
|
||||
codex_bin: Path
|
||||
entrypoint_bin: Path
|
||||
bwrap_bin: Path | None
|
||||
codex_command_runner_bin: Path | None
|
||||
codex_windows_sandbox_setup_bin: Path | None
|
||||
@@ -22,28 +23,39 @@ class SourceBuildOutputs:
|
||||
|
||||
def build_source_binaries(
|
||||
spec: TargetSpec,
|
||||
variant: PackageVariant,
|
||||
*,
|
||||
cargo: str,
|
||||
profile: str,
|
||||
entrypoint_bin: Path | None,
|
||||
) -> SourceBuildOutputs:
|
||||
binaries = source_binaries_for_target(spec)
|
||||
cmd = [
|
||||
cargo,
|
||||
"build",
|
||||
"--target",
|
||||
spec.target,
|
||||
"--profile",
|
||||
profile,
|
||||
]
|
||||
for binary in binaries:
|
||||
cmd.extend(["--bin", binary])
|
||||
binaries = source_binaries_for_target(
|
||||
spec,
|
||||
variant,
|
||||
build_entrypoint=entrypoint_bin is None,
|
||||
)
|
||||
if binaries:
|
||||
cmd = [
|
||||
cargo,
|
||||
"build",
|
||||
"--target",
|
||||
spec.target,
|
||||
"--profile",
|
||||
profile,
|
||||
]
|
||||
for binary in binaries:
|
||||
cmd.extend(["--bin", binary])
|
||||
|
||||
print("+", " ".join(cmd))
|
||||
subprocess.run(cmd, cwd=CODEX_RS_ROOT, check=True)
|
||||
print("+", " ".join(cmd))
|
||||
subprocess.run(cmd, cwd=CODEX_RS_ROOT, check=True)
|
||||
|
||||
output_dir = cargo_profile_output_dir(spec, profile)
|
||||
outputs = SourceBuildOutputs(
|
||||
codex_bin=output_dir / spec.codex_name,
|
||||
entrypoint_bin=(
|
||||
entrypoint_bin.resolve()
|
||||
if entrypoint_bin is not None
|
||||
else output_dir / variant.entrypoint_name(spec)
|
||||
),
|
||||
bwrap_bin=output_dir / "bwrap" if spec.is_linux else None,
|
||||
codex_command_runner_bin=(
|
||||
output_dir / "codex-command-runner.exe" if spec.is_windows else None
|
||||
@@ -56,8 +68,15 @@ def build_source_binaries(
|
||||
return outputs
|
||||
|
||||
|
||||
def source_binaries_for_target(spec: TargetSpec) -> list[str]:
|
||||
binaries = ["codex"]
|
||||
def source_binaries_for_target(
|
||||
spec: TargetSpec,
|
||||
variant: PackageVariant,
|
||||
*,
|
||||
build_entrypoint: bool,
|
||||
) -> list[str]:
|
||||
binaries = []
|
||||
if build_entrypoint:
|
||||
binaries.append(variant.cargo_bin)
|
||||
if spec.is_linux:
|
||||
binaries.append("bwrap")
|
||||
if spec.is_windows:
|
||||
@@ -97,7 +116,7 @@ def cargo_profile_dirname(profile: str) -> str:
|
||||
|
||||
def validate_source_outputs(outputs: SourceBuildOutputs) -> None:
|
||||
for path in [
|
||||
outputs.codex_bin,
|
||||
outputs.entrypoint_bin,
|
||||
outputs.bwrap_bin,
|
||||
outputs.codex_command_runner_bin,
|
||||
outputs.codex_windows_sandbox_setup_bin,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Command-line interface for building Codex package directories."""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
@@ -10,6 +11,7 @@ from .layout import build_package_dir
|
||||
from .layout import prepare_package_dir
|
||||
from .layout import validate_package_dir
|
||||
from .ripgrep import resolve_rg_bin
|
||||
from .targets import PACKAGE_VARIANTS
|
||||
from .targets import TARGET_SPECS
|
||||
from .targets import PackageInputs
|
||||
from .targets import default_target
|
||||
@@ -29,15 +31,11 @@ def parse_args() -> argparse.Namespace:
|
||||
"for this host platform."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
default="0.0.0-dev",
|
||||
help="Codex version to record in codex-package.json.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--variant",
|
||||
choices=sorted(PACKAGE_VARIANTS),
|
||||
default="codex",
|
||||
help="Package variant to record in codex-package.json.",
|
||||
help="Package variant to build.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--package-dir",
|
||||
@@ -74,6 +72,14 @@ def parse_args() -> argparse.Namespace:
|
||||
"release packages."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--entrypoint-bin",
|
||||
type=Path,
|
||||
help=(
|
||||
"Optional prebuilt entrypoint executable for the selected package "
|
||||
"variant. If omitted, the entrypoint is built with Cargo."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rg-bin",
|
||||
type=Path,
|
||||
@@ -88,6 +94,7 @@ def parse_args() -> argparse.Namespace:
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
spec = TARGET_SPECS[getattr(args, "target", None) or default_target()]
|
||||
variant = PACKAGE_VARIANTS[args.variant]
|
||||
package_dir_arg = getattr(args, "package_dir", None)
|
||||
package_dir = (
|
||||
package_dir_arg.resolve()
|
||||
@@ -97,19 +104,22 @@ def main() -> int:
|
||||
|
||||
source_outputs = build_source_binaries(
|
||||
spec,
|
||||
variant,
|
||||
cargo=args.cargo,
|
||||
profile=args.cargo_profile,
|
||||
entrypoint_bin=args.entrypoint_bin,
|
||||
)
|
||||
version = read_entrypoint_version(source_outputs.entrypoint_bin)
|
||||
inputs = PackageInputs(
|
||||
codex_bin=source_outputs.codex_bin,
|
||||
entrypoint_bin=source_outputs.entrypoint_bin,
|
||||
rg_bin=resolve_rg_bin(spec, args.rg_bin),
|
||||
bwrap_bin=source_outputs.bwrap_bin,
|
||||
codex_command_runner_bin=source_outputs.codex_command_runner_bin,
|
||||
codex_windows_sandbox_setup_bin=source_outputs.codex_windows_sandbox_setup_bin,
|
||||
)
|
||||
prepare_package_dir(package_dir, force=args.force)
|
||||
build_package_dir(package_dir, args.version, args.variant, spec, inputs)
|
||||
validate_package_dir(package_dir, spec)
|
||||
build_package_dir(package_dir, version, variant, spec, inputs)
|
||||
validate_package_dir(package_dir, variant, spec)
|
||||
|
||||
archive_output = args.archive_output
|
||||
if archive_output is not None:
|
||||
@@ -119,3 +129,32 @@ def main() -> int:
|
||||
|
||||
print(f"Built Codex package directory at {package_dir}")
|
||||
return 0
|
||||
|
||||
|
||||
def read_entrypoint_version(entrypoint_bin: Path) -> str:
|
||||
cmd = [str(entrypoint_bin), "--version"]
|
||||
print("+", " ".join(cmd))
|
||||
try:
|
||||
output = subprocess.run(
|
||||
cmd,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout
|
||||
except subprocess.CalledProcessError as exc:
|
||||
stderr = exc.stderr.strip()
|
||||
detail = f": {stderr}" if stderr else ""
|
||||
raise RuntimeError(
|
||||
f"Failed to read package version from {entrypoint_bin}{detail}"
|
||||
) from exc
|
||||
|
||||
version = parse_version_output(output)
|
||||
print(f"Detected package version: {version}")
|
||||
return version
|
||||
|
||||
|
||||
def parse_version_output(output: str) -> str:
|
||||
parts = output.strip().split()
|
||||
if not parts:
|
||||
raise RuntimeError("Entrypoint --version output was empty")
|
||||
return parts[-1]
|
||||
|
||||
@@ -6,6 +6,7 @@ import stat
|
||||
from pathlib import Path
|
||||
|
||||
from .targets import PackageInputs
|
||||
from .targets import PackageVariant
|
||||
from .targets import TargetSpec
|
||||
|
||||
|
||||
@@ -30,7 +31,7 @@ def prepare_package_dir(package_dir: Path, *, force: bool) -> None:
|
||||
def build_package_dir(
|
||||
package_dir: Path,
|
||||
version: str,
|
||||
variant: str,
|
||||
variant: PackageVariant,
|
||||
spec: TargetSpec,
|
||||
inputs: PackageInputs,
|
||||
) -> None:
|
||||
@@ -41,7 +42,12 @@ def build_package_dir(
|
||||
resources_dir.mkdir()
|
||||
path_dir.mkdir()
|
||||
|
||||
copy_executable(inputs.codex_bin, bin_dir / spec.codex_name, is_windows=spec.is_windows)
|
||||
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.bwrap_bin is not None:
|
||||
@@ -65,15 +71,19 @@ def build_package_dir(
|
||||
"layoutVersion": LAYOUT_VERSION,
|
||||
"version": version,
|
||||
"target": spec.target,
|
||||
"variant": variant,
|
||||
"entrypoint": f"bin/{spec.codex_name}",
|
||||
"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, spec: TargetSpec) -> None:
|
||||
def validate_package_dir(
|
||||
package_dir: Path,
|
||||
variant: PackageVariant,
|
||||
spec: TargetSpec,
|
||||
) -> None:
|
||||
required_dirs = [
|
||||
Path("bin"),
|
||||
Path("codex-resources"),
|
||||
@@ -94,7 +104,8 @@ def validate_package_dir(package_dir: Path, spec: TargetSpec) -> None:
|
||||
expected_metadata = {
|
||||
"layoutVersion": LAYOUT_VERSION,
|
||||
"target": spec.target,
|
||||
"entrypoint": f"bin/{spec.codex_name}",
|
||||
"variant": variant.name,
|
||||
"entrypoint": f"bin/{variant.entrypoint_name(spec)}",
|
||||
"resourcesDir": "codex-resources",
|
||||
"pathDir": "codex-path",
|
||||
}
|
||||
@@ -106,7 +117,7 @@ def validate_package_dir(package_dir: Path, spec: TargetSpec) -> None:
|
||||
)
|
||||
|
||||
required_files = [
|
||||
Path("bin") / spec.codex_name,
|
||||
Path("bin") / variant.entrypoint_name(spec),
|
||||
Path("codex-path") / spec.rg_name,
|
||||
]
|
||||
executable_files = list(required_files)
|
||||
|
||||
@@ -21,24 +21,44 @@ class TargetSpec:
|
||||
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 PackageVariant:
|
||||
name: str
|
||||
cargo_bin: str
|
||||
executable_stem: str
|
||||
|
||||
def entrypoint_name(self, spec: TargetSpec) -> str:
|
||||
return f"{self.executable_stem}{spec.exe_suffix}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PackageInputs:
|
||||
codex_bin: Path
|
||||
entrypoint_bin: Path
|
||||
rg_bin: Path
|
||||
bwrap_bin: Path | None
|
||||
codex_command_runner_bin: Path | None
|
||||
codex_windows_sandbox_setup_bin: Path | None
|
||||
|
||||
|
||||
PACKAGE_VARIANTS: dict[str, PackageVariant] = {
|
||||
"codex": PackageVariant(
|
||||
name="codex",
|
||||
cargo_bin="codex",
|
||||
executable_stem="codex",
|
||||
),
|
||||
"codex-app-server": PackageVariant(
|
||||
name="codex-app-server",
|
||||
cargo_bin="codex-app-server",
|
||||
executable_stem="codex-app-server",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
TARGET_SPECS: dict[str, TargetSpec] = {
|
||||
"x86_64-unknown-linux-gnu": TargetSpec(
|
||||
target="x86_64-unknown-linux-gnu",
|
||||
|
||||
Reference in New Issue
Block a user