mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Ship codex-exec-server in CLI and app-server packages (#57590)
## 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
This commit is contained in:
committed by
copyberry
parent
a9ea49e78b
commit
cf2c04b325
@@ -23,7 +23,7 @@ runs:
|
||||
shell: bash
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
BASE_BINARIES: "codex codex-app-server codex-code-mode-host"
|
||||
BASE_BINARIES: "codex codex-app-server codex-code-mode-host codex-exec-server"
|
||||
WINDOWS_ONLY_BINARIES: "codex-command-runner codex-windows-sandbox-setup"
|
||||
BUILD_MODE: ${{ inputs.build-mode }}
|
||||
BUILD_SYSTEM: ${{ inputs.build-system }}
|
||||
@@ -138,6 +138,7 @@ runs:
|
||||
package_args=(
|
||||
--target "$target"
|
||||
--code-mode-host-bin "${release_dir}/codex-code-mode-host${exe_suffix}"
|
||||
--exec-server-bin "${release_dir}/codex-exec-server${exe_suffix}"
|
||||
--cargo-profile release
|
||||
--package-version "$CODEX_PACKAGE_VERSION"
|
||||
--force
|
||||
@@ -183,7 +184,6 @@ runs:
|
||||
mkdir -p "${GITHUB_WORKSPACE}/codex-rs/target"
|
||||
pytest_args=(
|
||||
-v
|
||||
--basetemp "${GITHUB_WORKSPACE}/codex-rs/target/package-smoke-tmp"
|
||||
--package-target "$target"
|
||||
--symbols-archive "${GITHUB_WORKSPACE}/symbols-dist/${target}/codex-symbols-${target}.tar.gz"
|
||||
)
|
||||
@@ -194,13 +194,17 @@ runs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Windows setup helpers can finish background ACL work after pytest exits.
|
||||
# Separate roots keep the next run from deleting their executables.
|
||||
uv run --frozen pytest "${pytest_args[@]}" \
|
||||
--basetemp "${GITHUB_WORKSPACE}/codex-rs/target/package-smoke-gzip-tmp" \
|
||||
--compression gzip \
|
||||
--cli-archive "${archive_dir}/codex-package-${target}.tar.gz" \
|
||||
--app-server-archive "${archive_dir}/codex-app-server-package-${target}.tar.gz" \
|
||||
test_codex_package.py
|
||||
|
||||
uv run --frozen pytest "${pytest_args[@]}" \
|
||||
--basetemp "${GITHUB_WORKSPACE}/codex-rs/target/package-smoke-zstd-tmp" \
|
||||
--compression zstd \
|
||||
--cli-archive "${archive_dir}/codex-package-${target}.tar.zst" \
|
||||
--app-server-archive "${archive_dir}/codex-app-server-package-${target}.tar.zst" \
|
||||
|
||||
@@ -136,6 +136,8 @@ case "$target" in
|
||||
;;
|
||||
esac
|
||||
|
||||
resource_args+=(--exec-server-bin "${entrypoint_dir%/}/codex-exec-server${exe_suffix}")
|
||||
|
||||
code_mode_host_bin="${entrypoint_dir%/}/codex-code-mode-host${exe_suffix}"
|
||||
if [[ "$code_mode_host_bin_provided" == "false" && -f "$code_mode_host_bin" ]]; then
|
||||
resource_args+=(--code-mode-host-bin "$code_mode_host_bin")
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -19,7 +19,7 @@ CODE_SIGNING_ID = "codex"
|
||||
APP = Path("CodexCLI.app")
|
||||
EXECUTABLE = APP / "Contents/MacOS/codex"
|
||||
SIGNING = Path(__file__).resolve().parent
|
||||
HELPERS = ("bin/codex-code-mode-host", "codex-path/rg")
|
||||
HELPERS = ("bin/codex-code-mode-host", "bin/codex-exec-server", "codex-path/rg")
|
||||
LAUNCHER = """#!/bin/sh
|
||||
set -eu
|
||||
entry="$0"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -134,6 +135,12 @@ def main(configuration: bundle.ProfileConfiguration | None = None):
|
||||
check=True,
|
||||
)
|
||||
if relative.startswith("bin/"):
|
||||
# Signing tools may omit the blob when no entitlements are requested.
|
||||
if (
|
||||
actual.stat().st_size == 0
|
||||
and plistlib.loads(entitlements.read_bytes()) == {}
|
||||
):
|
||||
actual.write_bytes(entitlements.read_bytes())
|
||||
expected = reports / f"{name}-expected.plist"
|
||||
subprocess.run(
|
||||
[
|
||||
|
||||
@@ -34,10 +34,15 @@ if name == "codesign":
|
||||
target = Path(args[-1]).name
|
||||
if target == "CodexCLI.app":
|
||||
source = Path(os.environ["CODEX_REPO_ROOT"]) / "signing-verification/codex-provisioned-entitlements.plist"
|
||||
elif target in ("codex", "codex-code-mode-host"):
|
||||
elif target in ("codex", "codex-code-mode-host", "codex-exec-server"):
|
||||
source = Path(os.environ["MOCK_SIGNING_SCRIPTS"]) / (target + ".entitlements.plist")
|
||||
else:
|
||||
sys.exit(0)
|
||||
if target == "codex-exec-server" and os.environ.get("MOCK_EXECUTOR_ENTITLEMENTS"):
|
||||
sys.stdout.buffer.write(plistlib.dumps({"unexpected": True}))
|
||||
sys.exit(0)
|
||||
if not plistlib.loads(source.read_bytes()) and os.environ.get("MOCK_OMIT_EMPTY_ENTITLEMENTS"):
|
||||
sys.exit(0)
|
||||
if os.environ.get("MOCK_WRONG_ENTITLEMENTS"):
|
||||
sys.stdout.buffer.write(plistlib.dumps({"unexpected": True}))
|
||||
else:
|
||||
@@ -145,15 +150,21 @@ class MacosSigningTests(ProvisioningTestCase):
|
||||
)
|
||||
|
||||
def test_standard_and_provisioned_signing_and_verification(self):
|
||||
for provisioned, target in itertools.product(
|
||||
(False, True), ("aarch64-apple-darwin", "x86_64-apple-darwin")
|
||||
for provisioned, target, omit_empty in itertools.product(
|
||||
(False, True),
|
||||
("aarch64-apple-darwin", "x86_64-apple-darwin"),
|
||||
(False, True),
|
||||
):
|
||||
with (
|
||||
self.subTest(provisioned=provisioned, target=target),
|
||||
self.subTest(
|
||||
provisioned=provisioned, target=target, omit_empty=omit_empty
|
||||
),
|
||||
tempfile.TemporaryDirectory() as directory,
|
||||
):
|
||||
root = Path(directory).resolve()
|
||||
package, driver, env = self.fixture(root, provisioned, target)
|
||||
if omit_empty:
|
||||
env["MOCK_OMIT_EMPTY_ENTITLEMENTS"] = "1"
|
||||
result = self.run_driver(driver, "sign", package, env)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
result = self.run_driver(driver, "verify", package, env)
|
||||
@@ -189,13 +200,13 @@ class MacosSigningTests(ProvisioningTestCase):
|
||||
)
|
||||
self.assertEqual(
|
||||
["--entitlements" in call for call in signing],
|
||||
[True, True, False],
|
||||
[True, True, True, False],
|
||||
)
|
||||
self.assertEqual(
|
||||
[call[0] for call in calls if call[0].startswith("notarize")],
|
||||
["notarize_with_akv.py"]
|
||||
if provisioned
|
||||
else ["notarize_macos_binary_with_akv.sh"] * 3,
|
||||
else ["notarize_macos_binary_with_akv.sh"] * 4,
|
||||
)
|
||||
self.assertEqual(
|
||||
[call[1:] for call in calls if call[0] == "lipo"],
|
||||
@@ -318,7 +329,12 @@ class MacosSigningTests(ProvisioningTestCase):
|
||||
|
||||
def test_verification_rejects_wrong_signing_identity_and_entitlements(self):
|
||||
for provisioned, failure in itertools.product(
|
||||
(False, True), ("MOCK_WRONG_SIGNING_IDENTITY", "MOCK_WRONG_ENTITLEMENTS")
|
||||
(False, True),
|
||||
(
|
||||
"MOCK_WRONG_SIGNING_IDENTITY",
|
||||
"MOCK_WRONG_ENTITLEMENTS",
|
||||
"MOCK_EXECUTOR_ENTITLEMENTS",
|
||||
),
|
||||
):
|
||||
with (
|
||||
self.subTest(provisioned=provisioned, failure=failure),
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
WINDOWS_BINARIES: "codex codex-code-mode-host codex-responses-api-proxy codex-windows-sandbox-setup codex-windows-sandbox-service codex-command-runner codex-app-server"
|
||||
WINDOWS_BINARIES: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy codex-windows-sandbox-setup codex-windows-sandbox-service codex-command-runner codex-app-server"
|
||||
|
||||
jobs:
|
||||
build-windows-binaries:
|
||||
@@ -39,14 +39,14 @@ jobs:
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
bundle: primary
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
runs_on:
|
||||
group: ${{ github.event.repository.name }}-runners
|
||||
labels: ${{ github.event.repository.name }}-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
bundle: primary
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
runs_on:
|
||||
group: ${{ github.event.repository.name }}-runners
|
||||
labels: ${{ github.event.repository.name }}-windows-arm64
|
||||
@@ -67,14 +67,14 @@ jobs:
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
bundle: app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
runs_on:
|
||||
group: ${{ github.event.repository.name }}-runners
|
||||
labels: ${{ github.event.repository.name }}-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
bundle: app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
runs_on:
|
||||
group: ${{ github.event.repository.name }}-runners
|
||||
labels: ${{ github.event.repository.name }}-windows-arm64
|
||||
@@ -549,6 +549,26 @@ jobs:
|
||||
--entrypoint-dir "$CARGO_TARGET_DIR/$target/release" \
|
||||
--archive-dir "dist/$target"
|
||||
|
||||
- name: Verify packaged executor signatures and bytes
|
||||
shell: pwsh
|
||||
run: |
|
||||
$target = "${{ matrix.target }}"
|
||||
$source = Join-Path $env:CARGO_TARGET_DIR "$target/release/codex-exec-server.exe"
|
||||
foreach ($stem in @("codex-package", "codex-app-server-package")) {
|
||||
$package = Join-Path $env:RUNNER_TEMP "verify-executor-$stem-$target"
|
||||
New-Item -ItemType Directory -Path $package | Out-Null
|
||||
tar -xzf "dist/$target/$stem-$target.tar.gz" -C $package
|
||||
if ($LASTEXITCODE -ne 0) { throw "Cannot extract executor package" }
|
||||
$binary = Join-Path $package "bin/codex-exec-server.exe"
|
||||
if ((Get-FileHash $binary).Hash -ne (Get-FileHash $source).Hash) {
|
||||
throw "Packaged executor differs from the signed input"
|
||||
}
|
||||
$signature = Get-AuthenticodeSignature $binary
|
||||
if ($signature.Status -ne "Valid" -or $null -eq $signature.TimeStamperCertificate) {
|
||||
throw "Invalid or untimestamped executor signature"
|
||||
}
|
||||
}
|
||||
|
||||
- name: Verify packaged Windows voice closure
|
||||
shell: pwsh
|
||||
run: |
|
||||
|
||||
@@ -96,50 +96,50 @@ jobs:
|
||||
target: aarch64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: aarch64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
build_dmg: "true"
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: aarch64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
build_dmg: "false"
|
||||
- runner: macos-15-xlarge
|
||||
target: x86_64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: x86_64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
build_dmg: "true"
|
||||
- runner: macos-15-xlarge
|
||||
target: x86_64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: x86_64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
build_dmg: "false"
|
||||
# Release artifacts intentionally ship MUSL-linked Linux binaries.
|
||||
- runner: ${{ github.event.repository.name }}-linux-x64-xl
|
||||
target: x86_64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: x86_64-unknown-linux-musl
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy bwrap"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy bwrap"
|
||||
build_dmg: "false"
|
||||
- runner: ${{ github.event.repository.name }}-linux-x64-xl
|
||||
target: x86_64-unknown-linux-musl
|
||||
bundle: app-server
|
||||
artifact_name: x86_64-unknown-linux-musl-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
build_dmg: "false"
|
||||
- runner: ${{ github.event.repository.name }}-linux-arm64
|
||||
target: aarch64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: aarch64-unknown-linux-musl
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy bwrap"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy bwrap"
|
||||
build_dmg: "false"
|
||||
- runner: ${{ github.event.repository.name }}-linux-arm64
|
||||
target: aarch64-unknown-linux-musl
|
||||
bundle: app-server
|
||||
artifact_name: aarch64-unknown-linux-musl-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
build_dmg: "false"
|
||||
|
||||
steps:
|
||||
@@ -398,9 +398,9 @@ jobs:
|
||||
mkdir -p "$dest"
|
||||
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
# Both variants package the host, but only the primary bundle publishes
|
||||
# Both variants package the host and executor; only the primary publishes
|
||||
# standalone binary archives to avoid duplicate release asset names.
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && ( "$binary" == "codex-code-mode-host" || "$binary" == "codex-exec-server" ) ]]; then
|
||||
continue
|
||||
fi
|
||||
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
|
||||
@@ -662,19 +662,19 @@ jobs:
|
||||
- target: aarch64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: aarch64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
- target: aarch64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: aarch64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
- target: x86_64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: x86_64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
- target: x86_64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: x86_64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -907,22 +907,22 @@ jobs:
|
||||
- target: aarch64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: aarch64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
build_dmg: "true"
|
||||
- target: aarch64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: aarch64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
build_dmg: "false"
|
||||
- target: x86_64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: x86_64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
build_dmg: "true"
|
||||
- target: x86_64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: x86_64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
build_dmg: "false"
|
||||
|
||||
steps:
|
||||
@@ -1001,9 +1001,9 @@ jobs:
|
||||
mkdir -p "$dest"
|
||||
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
# Both variants package the host, but only the primary bundle publishes
|
||||
# Both variants package the host and executor; only the primary publishes
|
||||
# standalone binary archives to avoid duplicate release asset names.
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && ( "$binary" == "codex-code-mode-host" || "$binary" == "codex-exec-server" ) ]]; then
|
||||
continue
|
||||
fi
|
||||
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
|
||||
@@ -1300,22 +1300,22 @@ jobs:
|
||||
- target: aarch64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: aarch64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
verify_dmg: "true"
|
||||
- target: aarch64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: aarch64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
verify_dmg: "false"
|
||||
- target: x86_64-apple-darwin
|
||||
bundle: primary
|
||||
artifact_name: x86_64-apple-darwin
|
||||
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
|
||||
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
|
||||
verify_dmg: "true"
|
||||
- target: x86_64-apple-darwin
|
||||
bundle: app-server
|
||||
artifact_name: x86_64-apple-darwin-app-server
|
||||
binaries: "codex-app-server codex-code-mode-host"
|
||||
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
|
||||
verify_dmg: "false"
|
||||
|
||||
steps:
|
||||
@@ -1376,6 +1376,15 @@ jobs:
|
||||
normalized_actual="$(mktemp)"
|
||||
normalized_expected="$(mktemp)"
|
||||
codesign -d --entitlements :- "$path" >"$actual_entitlements"
|
||||
# An omitted entitlement blob is valid only when the expected plist is empty.
|
||||
python3 - "$expected_entitlements" "$actual_entitlements" <<'PY'
|
||||
import plistlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
expected, actual = map(Path, sys.argv[1:])
|
||||
if not actual.stat().st_size and plistlib.loads(expected.read_bytes()) == {}:
|
||||
actual.write_bytes(expected.read_bytes())
|
||||
PY
|
||||
plutil -convert xml1 -o "$normalized_actual" "$actual_entitlements"
|
||||
plutil -convert xml1 -o "$normalized_expected" "$expected_entitlements"
|
||||
diff -u "$normalized_expected" "$normalized_actual"
|
||||
@@ -1386,9 +1395,9 @@ jobs:
|
||||
binary_path="${RUNNER_TEMP}/signed-binaries/${binary}"
|
||||
verify_signed_binary "$binary_path" "$binary"
|
||||
|
||||
# The app-server package contains the host, but its standalone archives
|
||||
# The app-server package contains both helpers, but their standalone archives
|
||||
# are omitted above to avoid duplicate release asset names.
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && ( "$binary" == "codex-code-mode-host" || "$binary" == "codex-exec-server" ) ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -1424,6 +1433,7 @@ jobs:
|
||||
tar -xzf "${packaged_dir}/${package_stem}-${target}.tar.gz" -C "$package_dir"
|
||||
verify_signed_binary "${package_dir}/bin/${package_entrypoint}" "$package_entrypoint"
|
||||
verify_signed_binary "${package_dir}/bin/codex-code-mode-host" "codex-code-mode-host"
|
||||
verify_signed_binary "${package_dir}/bin/codex-exec-server" "codex-exec-server"
|
||||
|
||||
for resource in \
|
||||
"${package_dir}/codex-path/rg"
|
||||
|
||||
@@ -53,6 +53,7 @@ runtime_archive(
|
||||
"//codex-rs/app-server:release_files",
|
||||
"//codex-rs/cli:release_files",
|
||||
"//codex-rs/code-mode-host:release_files",
|
||||
"//codex-rs/exec-server-cli:release_files",
|
||||
] + select({
|
||||
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
|
||||
"@platforms//os:windows": [
|
||||
@@ -70,6 +71,7 @@ runtime_archive(
|
||||
srcs = [
|
||||
"//codex-rs/cli:release_files",
|
||||
"//codex-rs/code-mode-host:release_files",
|
||||
"//codex-rs/exec-server-cli:release_files",
|
||||
"//codex-rs/responses-api-proxy:release_files",
|
||||
] + select({
|
||||
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
|
||||
@@ -84,6 +86,7 @@ runtime_archive(
|
||||
srcs = [
|
||||
"//codex-rs/app-server:release_files",
|
||||
"//codex-rs/code-mode-host:release_files",
|
||||
"//codex-rs/exec-server-cli:release_files",
|
||||
] + select({
|
||||
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
|
||||
"//conditions:default": [],
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
load("//bazel/platforms:release_binaries.bzl", "multiplatform_binaries")
|
||||
load("//bazel/rules:runtime_package.bzl", "release_binary")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "exec-server-cli",
|
||||
@@ -9,3 +11,7 @@ codex_rust_crate(
|
||||
# The standalone integration test launches native OS sandboxes itself.
|
||||
test_tags = ["no-sandbox"],
|
||||
)
|
||||
|
||||
release_binary(binary = ":codex-exec-server")
|
||||
|
||||
multiplatform_binaries(name = "codex-exec-server")
|
||||
|
||||
@@ -17,6 +17,8 @@ use `codex-arg0` for hidden filesystem, process, and platform sandbox dispatch.
|
||||
The standalone executable accepts the same executor arguments and `-c` config
|
||||
overrides, including `--listen stdio`. Build it with
|
||||
`cargo build -p codex-exec-server-cli --bin codex-exec-server`.
|
||||
Canonical packages place it in `bin/` beside the entrypoint, with platform
|
||||
sandbox resources in `codex-resources/`.
|
||||
|
||||
## Command metadata
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ The builder creates a canonical Codex package directory:
|
||||
├── codex-package.json
|
||||
├── bin
|
||||
│ ├── <entrypoint>[.exe]
|
||||
│ └── codex-code-mode-host[.exe]
|
||||
│ ├── codex-code-mode-host[.exe]
|
||||
│ └── codex-exec-server[.exe]
|
||||
├── codex-resources
|
||||
│ ├── bwrap # Linux only
|
||||
│ ├── codex-command-runner.exe # Windows only
|
||||
@@ -30,6 +31,9 @@ The builder creates a canonical Codex package directory:
|
||||
|
||||
The package directory is the primary artifact. Archive formats such as
|
||||
`.tar.gz`, `.tar.zst`, and `.zip` are serializations of that directory.
|
||||
The `execServer` field in `codex-package.json` advertises the packaged executor.
|
||||
Installers use its presence to repair a missing executor while accepting older
|
||||
packages that predate the standalone binary.
|
||||
|
||||
If `--target` is omitted, the builder uses the release target for the current
|
||||
host platform. On Linux, that default is a musl target to match Codex release
|
||||
@@ -50,6 +54,7 @@ override was provided:
|
||||
|
||||
- all targets: the selected entrypoint, unless `--entrypoint-bin` is provided
|
||||
- all targets: `codex-code-mode-host`, unless `--code-mode-host-bin` is provided
|
||||
- all targets: `codex-exec-server`, unless `--exec-server-bin` is provided
|
||||
- Linux targets: `bwrap`, unless `--bwrap-bin` is provided
|
||||
- Windows targets: `codex-command-runner` and `codex-windows-sandbox-setup`,
|
||||
unless the corresponding prebuilt helper flags are provided
|
||||
@@ -60,15 +65,15 @@ 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.
|
||||
|
||||
Release jobs should likewise pass `--code-mode-host-bin` so the package contains
|
||||
the signed host executable beside the signed entrypoint.
|
||||
Release jobs should likewise pass `--code-mode-host-bin` and `--exec-server-bin`
|
||||
so the package preserves the signed host and executor beside the entrypoint.
|
||||
|
||||
On Linux and macOS, the builder strips symbols from the **package copies** of
|
||||
source-built release-profile entrypoint and code-mode host binaries. Cargo outputs
|
||||
source-built release-profile entrypoint, code-mode host, and executor binaries. Cargo outputs
|
||||
retain their symbols for debugging. The default `--strip auto` also preserves
|
||||
development/profiling builds and prebuilt inputs
|
||||
byte-for-byte, including signatures. Use `--strip all` to also strip prebuilt
|
||||
entrypoint/host copies, or `--strip none` to keep symbols in every package copy.
|
||||
first-party executable copies, or `--strip none` to keep symbols in every package copy.
|
||||
Strip before production signing; `--strip all` is not appropriate for inputs
|
||||
whose release signatures must be preserved. Windows MSVC symbols are separate
|
||||
PDB files and are not included in the package.
|
||||
|
||||
@@ -18,6 +18,7 @@ CODEX_RS_ROOT = REPO_ROOT / "codex-rs"
|
||||
class SourceBuildOutputs:
|
||||
entrypoint_bin: Path
|
||||
code_mode_host_bin: Path
|
||||
exec_server_bin: Path
|
||||
bwrap_bin: Path | None
|
||||
codex_command_runner_bin: Path | None
|
||||
codex_windows_sandbox_setup_bin: Path | None
|
||||
@@ -31,6 +32,7 @@ def build_source_binaries(
|
||||
profile: str,
|
||||
entrypoint_bin: Path | None,
|
||||
code_mode_host_bin: Path | None,
|
||||
exec_server_bin: Path | None,
|
||||
bwrap_bin: Path | None,
|
||||
codex_command_runner_bin: Path | None,
|
||||
codex_windows_sandbox_setup_bin: Path | None,
|
||||
@@ -46,6 +48,7 @@ def build_source_binaries(
|
||||
variant,
|
||||
build_entrypoint=entrypoint_bin is None,
|
||||
build_code_mode_host=code_mode_host_bin is None,
|
||||
build_exec_server=exec_server_bin is None,
|
||||
build_bwrap=spec.is_linux and bwrap_bin is None,
|
||||
build_codex_command_runner=spec.is_windows and codex_command_runner_bin is None,
|
||||
build_codex_windows_sandbox_setup=spec.is_windows
|
||||
@@ -64,7 +67,11 @@ def build_source_binaries(
|
||||
cmd.extend(["--bin", binary])
|
||||
|
||||
cargo_env = None
|
||||
if entrypoint_bin is None or code_mode_host_bin is None:
|
||||
if (
|
||||
entrypoint_bin is None
|
||||
or code_mode_host_bin is None
|
||||
or exec_server_bin is None
|
||||
):
|
||||
codex_v8_env = resolve_codex_v8_cargo_env(spec)
|
||||
if codex_v8_env:
|
||||
cargo_env = {**os.environ, **codex_v8_env}
|
||||
@@ -88,6 +95,11 @@ def build_source_binaries(
|
||||
if code_mode_host_bin is not None
|
||||
else output_dir / f"codex-code-mode-host{spec.exe_suffix}"
|
||||
),
|
||||
exec_server_bin=(
|
||||
exec_server_bin.resolve()
|
||||
if exec_server_bin is not None
|
||||
else output_dir / f"codex-exec-server{spec.exe_suffix}"
|
||||
),
|
||||
bwrap_bin=resolve_output_path(
|
||||
bwrap_bin,
|
||||
output_dir / "bwrap" if spec.is_linux else None,
|
||||
@@ -111,6 +123,7 @@ def source_binaries_for_target(
|
||||
*,
|
||||
build_entrypoint: bool,
|
||||
build_code_mode_host: bool,
|
||||
build_exec_server: bool,
|
||||
build_bwrap: bool,
|
||||
build_codex_command_runner: bool,
|
||||
build_codex_windows_sandbox_setup: bool,
|
||||
@@ -120,6 +133,8 @@ def source_binaries_for_target(
|
||||
binaries.append(variant.cargo_bin)
|
||||
if build_code_mode_host:
|
||||
binaries.append("codex-code-mode-host")
|
||||
if build_exec_server:
|
||||
binaries.append("codex-exec-server")
|
||||
if build_bwrap:
|
||||
binaries.append("bwrap")
|
||||
if build_codex_command_runner:
|
||||
@@ -186,6 +201,7 @@ def validate_source_outputs(outputs: SourceBuildOutputs) -> None:
|
||||
for path in [
|
||||
outputs.entrypoint_bin,
|
||||
outputs.code_mode_host_bin,
|
||||
outputs.exec_server_bin,
|
||||
outputs.bwrap_bin,
|
||||
outputs.codex_command_runner_bin,
|
||||
outputs.codex_windows_sandbox_setup_bin,
|
||||
|
||||
@@ -140,6 +140,11 @@ def create_parser() -> argparse.ArgumentParser:
|
||||
"the host is built with Cargo."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exec-server-bin",
|
||||
type=Path,
|
||||
help="Optional prebuilt codex-exec-server executable. If omitted, build it with Cargo.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--bwrap-bin",
|
||||
type=Path,
|
||||
@@ -210,6 +215,11 @@ def assemble_package(args: argparse.Namespace) -> Path:
|
||||
"prebuilt code-mode host executable",
|
||||
"--code-mode-host-bin",
|
||||
),
|
||||
exec_server_bin=resolve_optional_input_path(
|
||||
args.exec_server_bin,
|
||||
"prebuilt executor executable",
|
||||
"--exec-server-bin",
|
||||
),
|
||||
bwrap_bin=resolve_optional_input_path(
|
||||
args.bwrap_bin,
|
||||
"prebuilt Linux bwrap executable",
|
||||
@@ -229,6 +239,7 @@ def assemble_package(args: argparse.Namespace) -> Path:
|
||||
inputs = PackageInputs(
|
||||
entrypoint_bin=source_outputs.entrypoint_bin,
|
||||
code_mode_host_bin=source_outputs.code_mode_host_bin,
|
||||
exec_server_bin=source_outputs.exec_server_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,
|
||||
@@ -239,6 +250,7 @@ def assemble_package(args: argparse.Namespace) -> Path:
|
||||
for filename, prebuilt in (
|
||||
(variant.entrypoint_name(spec), args.entrypoint_bin),
|
||||
(f"codex-code-mode-host{spec.exe_suffix}", args.code_mode_host_bin),
|
||||
(f"codex-exec-server{spec.exe_suffix}", args.exec_server_bin),
|
||||
):
|
||||
if args.strip == "all" or (
|
||||
args.strip == "auto"
|
||||
|
||||
@@ -55,6 +55,11 @@ def build_package_dir(
|
||||
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:
|
||||
@@ -80,6 +85,7 @@ def build_package_dir(
|
||||
"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",
|
||||
}
|
||||
@@ -113,6 +119,7 @@ def validate_package_dir(
|
||||
"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",
|
||||
}
|
||||
@@ -126,6 +133,7 @@ def validate_package_dir(
|
||||
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)
|
||||
|
||||
@@ -208,7 +208,12 @@ def code_mode_host_debug_symbols(
|
||||
tmp_path_factory: pytest.TempPathFactory,
|
||||
) -> Path:
|
||||
target = pytestconfig.getoption("package_target")
|
||||
binaries = {"codex", "codex-app-server", "codex-code-mode-host"}
|
||||
binaries = {
|
||||
"codex",
|
||||
"codex-app-server",
|
||||
"codex-code-mode-host",
|
||||
"codex-exec-server",
|
||||
}
|
||||
if "windows" in target:
|
||||
binaries.update({"codex-command-runner", "codex-windows-sandbox-setup"})
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ ROOT_OF_EXTRACTED_PACKAGE
|
||||
├── bin
|
||||
│ ├── codex[.exe] # CLI package only
|
||||
│ ├── codex-app-server[.exe] # app-server package only
|
||||
│ └── codex-code-mode-host[.exe]
|
||||
│ ├── codex-code-mode-host[.exe]
|
||||
│ └── codex-exec-server[.exe]
|
||||
├── codex-package.json
|
||||
├── codex-path
|
||||
│ └── rg[.exe]
|
||||
@@ -18,11 +19,13 @@ Each package contains one entrypoint, not both codex and codex-app-server.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -67,6 +70,261 @@ def test_cli_version_matches_release_sources(package: SmokePackage) -> None:
|
||||
assert package.run("--version").stdout == f"codex-cli {version}\n"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", ["cli_root", "app_server_root"])
|
||||
def test_packaged_executor_initializes_and_enforces_sandbox(
|
||||
package: SmokePackage, variant: str
|
||||
) -> None:
|
||||
"""Each package re-enters its executor helpers and enforces filesystem/process policy."""
|
||||
root = getattr(package, variant)
|
||||
windows = "windows" in package.target
|
||||
suffix = ".exe" if windows else ""
|
||||
executor = root / "bin" / f"codex-exec-server{suffix}"
|
||||
directory = package.directory / f"{variant}-executor-sandbox"
|
||||
directory.mkdir()
|
||||
workspace = directory / "workspace"
|
||||
workspace.mkdir()
|
||||
home = directory / "home"
|
||||
home.mkdir()
|
||||
blocked = directory / "blocked.txt"
|
||||
blocked.write_text("unchanged")
|
||||
environment = {**package.environment, "CODEX_HOME": str(home)}
|
||||
if windows:
|
||||
# Elevated mode must discover the setup/runner in this package's resources.
|
||||
# Exclude build outputs and host Codex installations from helper fallback.
|
||||
environment["PATH"] = str(Path(os.environ["SystemRoot"]) / "System32")
|
||||
sandbox = {
|
||||
"permissions": {
|
||||
"type": "managed",
|
||||
"file_system": {
|
||||
"type": "restricted",
|
||||
"entries": [
|
||||
{
|
||||
"path": {"type": "special", "value": {"kind": "root"}},
|
||||
"access": "read",
|
||||
},
|
||||
{
|
||||
"path": {"type": "path", "path": workspace.as_uri()},
|
||||
"access": "write",
|
||||
},
|
||||
],
|
||||
},
|
||||
"network": "restricted",
|
||||
},
|
||||
"cwd": workspace.as_uri(),
|
||||
"windowsSandboxLevel": "elevated" if windows else "disabled",
|
||||
}
|
||||
version = (
|
||||
subprocess.check_output(
|
||||
[str(executor), "--version"], env=package.environment, text=True, timeout=45
|
||||
)
|
||||
.strip()
|
||||
.split()[-1]
|
||||
)
|
||||
assert version == package.run("--version").stdout.strip().split()[-1]
|
||||
with (
|
||||
ThreadPoolExecutor(max_workers=1) as reader,
|
||||
subprocess.Popen(
|
||||
[str(executor), "--listen", "stdio"],
|
||||
cwd=package.directory,
|
||||
env=environment,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
) as process,
|
||||
):
|
||||
assert process.stdin is not None and process.stdout is not None
|
||||
try:
|
||||
process.stdin.write(
|
||||
json.dumps(
|
||||
{
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {"clientName": "package-smoke"},
|
||||
}
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
process.stdin.flush()
|
||||
initialized = json.loads(
|
||||
reader.submit(process.stdout.readline).result(timeout=45)
|
||||
)
|
||||
assert initialized["id"] == 1
|
||||
assert initialized["result"]["sessionId"]
|
||||
reported_environment = initialized["result"]["environmentInfo"]
|
||||
metadata = json.loads((root / "codex-package.json").read_text())
|
||||
assert reported_environment["executorVersion"] == metadata["version"]
|
||||
process.stdin.write('{"method":"initialized","params":{}}\n')
|
||||
|
||||
# Restricted writes always launch the filesystem helper, even when allowed.
|
||||
for request_id, path in [(2, workspace / "allowed.txt"), (3, blocked)]:
|
||||
process.stdin.write(
|
||||
json.dumps(
|
||||
{
|
||||
"id": request_id,
|
||||
"method": "fs/writeFile",
|
||||
"params": {
|
||||
"path": path.as_uri(),
|
||||
"dataBase64": "b2s=",
|
||||
"sandbox": sandbox,
|
||||
},
|
||||
}
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
process.stdin.flush()
|
||||
# The first Windows helper can provision sandbox accounts before replying.
|
||||
response = json.loads(
|
||||
reader.submit(process.stdout.readline).result(
|
||||
timeout=180 if windows and request_id == 2 else 45
|
||||
)
|
||||
)
|
||||
if request_id == 2:
|
||||
assert response == {"id": 2, "result": {}}, response
|
||||
assert path.read_text() == "ok"
|
||||
else:
|
||||
assert response["id"] == 3 and "error" in response, response
|
||||
assert blocked.read_text() == "unchanged"
|
||||
|
||||
command = (
|
||||
[
|
||||
str(Path(os.environ["SystemRoot"]) / "System32" / "cmd.exe"),
|
||||
"/D",
|
||||
"/S",
|
||||
"/C",
|
||||
r"echo process>process.txt & echo forbidden>..\blocked.txt & exit /b 0",
|
||||
]
|
||||
if windows
|
||||
else [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"printf process >process.txt; printf forbidden >../blocked.txt; exit 0",
|
||||
]
|
||||
)
|
||||
process.stdin.write(
|
||||
json.dumps(
|
||||
{
|
||||
"id": 4,
|
||||
"method": "process/start",
|
||||
"params": {
|
||||
"processId": "package-sandbox",
|
||||
"argv": command,
|
||||
"cwd": workspace.as_uri(),
|
||||
"env": {
|
||||
key: value
|
||||
for key, value in environment.items()
|
||||
if key.casefold() in {"path", "systemroot"}
|
||||
},
|
||||
"sandbox": sandbox,
|
||||
"tty": False,
|
||||
"pipeStdin": False,
|
||||
"arg0": None,
|
||||
},
|
||||
}
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
process.stdin.flush()
|
||||
started = False
|
||||
exit_code = None
|
||||
notifications = []
|
||||
while not started or exit_code is None:
|
||||
response = json.loads(
|
||||
reader.submit(process.stdout.readline).result(timeout=45)
|
||||
)
|
||||
notifications.append(response)
|
||||
if response.get("id") == 4:
|
||||
expected_type = (
|
||||
"windowsRestrictedToken"
|
||||
if windows
|
||||
else "macosSeatbelt"
|
||||
if "apple-darwin" in package.target
|
||||
else "linuxSeccomp"
|
||||
)
|
||||
assert response["result"]["sandboxType"] == expected_type, response
|
||||
started = True
|
||||
if response.get("method") == "process/exited":
|
||||
exit_code = response["params"]["exitCode"]
|
||||
assert exit_code == 0, notifications
|
||||
assert (workspace / "process.txt").is_file(), notifications
|
||||
assert (workspace / "process.txt").read_text().strip() == "process"
|
||||
assert blocked.read_text() == "unchanged"
|
||||
process.stdin.close()
|
||||
assert process.wait(timeout=45) == 0
|
||||
finally:
|
||||
if process.poll() is None:
|
||||
process.kill()
|
||||
|
||||
|
||||
def test_packaged_executor_matches_subcommand_startup(
|
||||
package: SmokePackage,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Both binaries reject the same invalid executor arguments and config."""
|
||||
home = tmp_path / "home"
|
||||
temporary = tmp_path / "tmp"
|
||||
home.mkdir()
|
||||
temporary.mkdir()
|
||||
(home / "config.toml").write_text("unknown_executor_option = true\n")
|
||||
# Release builds require the helper directory to live outside their temp root.
|
||||
environment = {
|
||||
**package.environment,
|
||||
"CODEX_HOME": str(home),
|
||||
"TMPDIR": str(temporary),
|
||||
"TEMP": str(temporary),
|
||||
"TMP": str(temporary),
|
||||
}
|
||||
suffix = ".exe" if "windows" in package.target else ""
|
||||
executor = package.cli_root / "bin" / f"codex-exec-server{suffix}"
|
||||
for arguments in [
|
||||
["--strict-config", "--listen", "stdio"],
|
||||
["-c", "unknown_executor_option=true", "--strict-config", "--listen", "stdio"],
|
||||
["--listen", "http://127.0.0.1:0"],
|
||||
["--concurrent-requests", "0"],
|
||||
[
|
||||
"--aws-sigv4",
|
||||
"--remote",
|
||||
"https://example.openai.com",
|
||||
"--environment-id",
|
||||
"env-1",
|
||||
],
|
||||
["--remote-transport", "direct"],
|
||||
["forward", "--connect", "ws://127.0.0.1:0"],
|
||||
[
|
||||
"-c",
|
||||
"unknown_executor_option=true",
|
||||
"forward",
|
||||
"-c",
|
||||
"analytics.enabled=false",
|
||||
"--strict-config",
|
||||
"--connect",
|
||||
"ws://127.0.0.1:0",
|
||||
"--remote",
|
||||
"https://example.openai.com",
|
||||
"--environment-id",
|
||||
"env-1",
|
||||
],
|
||||
]:
|
||||
outputs = []
|
||||
for command in ([str(package.cli), "exec-server"], [str(executor)]):
|
||||
result = subprocess.run(
|
||||
[*command, *arguments],
|
||||
cwd=tmp_path,
|
||||
env=environment,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=45,
|
||||
)
|
||||
assert result.returncode != 0, arguments
|
||||
outputs.append(
|
||||
(
|
||||
result.returncode,
|
||||
result.stdout,
|
||||
result.stderr.replace("codex exec-server", "codex-exec-server"),
|
||||
)
|
||||
)
|
||||
assert outputs[0] == outputs[1], arguments
|
||||
|
||||
|
||||
@pytest.mark.parametrize("entrypoint", ["codex", "codex-app-server"])
|
||||
def test_app_server_runs_code_mode_through_python_sdk(
|
||||
package: SmokePackage,
|
||||
|
||||
@@ -45,6 +45,7 @@ class PackageVariant:
|
||||
class PackageInputs:
|
||||
entrypoint_bin: Path
|
||||
code_mode_host_bin: Path
|
||||
exec_server_bin: Path
|
||||
rg_bin: Path
|
||||
bwrap_bin: Path | None
|
||||
codex_command_runner_bin: Path | None
|
||||
|
||||
@@ -21,6 +21,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
|
||||
PACKAGE_VARIANTS["codex"],
|
||||
build_entrypoint=False,
|
||||
build_code_mode_host=False,
|
||||
build_exec_server=False,
|
||||
build_bwrap=False,
|
||||
build_codex_command_runner=False,
|
||||
build_codex_windows_sandbox_setup=False,
|
||||
@@ -37,6 +38,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
|
||||
PACKAGE_VARIANTS["codex"],
|
||||
build_entrypoint=False,
|
||||
build_code_mode_host=False,
|
||||
build_exec_server=False,
|
||||
build_bwrap=False,
|
||||
build_codex_command_runner=False,
|
||||
build_codex_windows_sandbox_setup=False,
|
||||
@@ -53,6 +55,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
|
||||
PACKAGE_VARIANTS["codex"],
|
||||
build_entrypoint=False,
|
||||
build_code_mode_host=False,
|
||||
build_exec_server=False,
|
||||
build_bwrap=False,
|
||||
build_codex_command_runner=False,
|
||||
build_codex_windows_sandbox_setup=False,
|
||||
@@ -67,6 +70,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
|
||||
PACKAGE_VARIANTS["codex"],
|
||||
build_entrypoint=False,
|
||||
build_code_mode_host=False,
|
||||
build_exec_server=False,
|
||||
build_bwrap=False,
|
||||
build_codex_command_runner=True,
|
||||
build_codex_windows_sandbox_setup=True,
|
||||
@@ -81,11 +85,12 @@ class SourceBinariesForTargetTest(unittest.TestCase):
|
||||
PACKAGE_VARIANTS["codex-app-server"],
|
||||
build_entrypoint=False,
|
||||
build_code_mode_host=True,
|
||||
build_exec_server=True,
|
||||
build_bwrap=False,
|
||||
build_codex_command_runner=False,
|
||||
build_codex_windows_sandbox_setup=False,
|
||||
),
|
||||
["codex-code-mode-host"],
|
||||
["codex-code-mode-host", "codex-exec-server"],
|
||||
)
|
||||
|
||||
def test_build_uses_prebuilt_windows_helpers_without_running_cargo(self) -> None:
|
||||
@@ -103,6 +108,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
|
||||
profile="release",
|
||||
entrypoint_bin=entrypoint,
|
||||
code_mode_host_bin=code_mode_host,
|
||||
exec_server_bin=code_mode_host,
|
||||
bwrap_bin=None,
|
||||
codex_command_runner_bin=command_runner,
|
||||
codex_windows_sandbox_setup_bin=sandbox_setup,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import io
|
||||
import tarfile
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -12,6 +14,7 @@ import unittest
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from build_winget_package import prepare_winget_package
|
||||
from codex_package.archive import write_archive, resolve_zstd_command
|
||||
from codex_package.layout import build_package_dir
|
||||
from codex_package.layout import validate_package_dir
|
||||
from codex_package.targets import PACKAGE_VARIANTS
|
||||
@@ -63,6 +66,7 @@ int main(void) {
|
||||
flags = [
|
||||
"--entrypoint-bin",
|
||||
"--code-mode-host-bin",
|
||||
"--exec-server-bin",
|
||||
"--rg-bin",
|
||||
]
|
||||
if "linux" in target:
|
||||
@@ -73,7 +77,7 @@ int main(void) {
|
||||
# Preserved prebuilt inputs must not even need a strip tool.
|
||||
command.extend(("--strip-tool", str(root / "absent-strip")))
|
||||
subprocess.run(command, check=True, capture_output=True)
|
||||
for name in ("codex", "codex-code-mode-host"):
|
||||
for name in ("codex", "codex-code-mode-host", "codex-exec-server"):
|
||||
executable = package / "bin" / name
|
||||
self.assertEqual(
|
||||
subprocess.check_output([str(executable)]),
|
||||
@@ -97,6 +101,7 @@ int main(void) {
|
||||
files = {
|
||||
"bin/codex.exe": b"signed CLI",
|
||||
"bin/codex-code-mode-host.exe": b"signed code mode host",
|
||||
"bin/codex-exec-server.exe": b"signed executor",
|
||||
"codex-resources/codex-command-runner.exe": b"signed runner",
|
||||
"codex-resources/codex-windows-sandbox-setup.exe": b"signed setup",
|
||||
"codex-resources/voice/bin/codex-voice-host.exe": b"signed voice host",
|
||||
@@ -172,6 +177,7 @@ int main(void) {
|
||||
code_mode_host_bin=touch_executable(
|
||||
root / "codex-code-mode-host"
|
||||
),
|
||||
exec_server_bin=rg_bin,
|
||||
rg_bin=rg_bin,
|
||||
bwrap_bin=None,
|
||||
codex_command_runner_bin=None,
|
||||
@@ -180,13 +186,41 @@ int main(void) {
|
||||
|
||||
build_package_dir(package_dir, "1.2.3", variant, spec, inputs)
|
||||
validate_package_dir(package_dir, variant, spec)
|
||||
for suffix in (".tar.gz", ".tar.zst"):
|
||||
archive_path = root / f"package{suffix}"
|
||||
write_archive(package_dir, archive_path, force=False)
|
||||
data = (
|
||||
subprocess.check_output(
|
||||
[
|
||||
*resolve_zstd_command(),
|
||||
"-d",
|
||||
"--stdout",
|
||||
str(archive_path),
|
||||
]
|
||||
)
|
||||
if suffix == ".tar.zst"
|
||||
else archive_path.read_bytes()
|
||||
)
|
||||
with tarfile.open(
|
||||
fileobj=io.BytesIO(data), mode="r:*"
|
||||
) as archive:
|
||||
member = archive.getmember("bin/codex-exec-server")
|
||||
self.assertTrue(member.mode & 0o111)
|
||||
self.assertEqual(
|
||||
archive.extractfile(member).read(),
|
||||
rg_bin.read_bytes(),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
"rg": (package_dir / "codex-path" / "rg").read_bytes(),
|
||||
"executor": (
|
||||
package_dir / "bin/codex-exec-server"
|
||||
).read_bytes(),
|
||||
},
|
||||
{
|
||||
"rg": b"signed ripgrep binary",
|
||||
"executor": b"signed ripgrep binary",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -198,6 +232,7 @@ int main(void) {
|
||||
inputs = PackageInputs(
|
||||
entrypoint_bin=touch_executable(root / "codex-app-server"),
|
||||
code_mode_host_bin=touch_executable(root / "codex-code-mode-host"),
|
||||
exec_server_bin=touch_executable(root / "codex-exec-server"),
|
||||
rg_bin=touch_executable(root / "rg"),
|
||||
bwrap_bin=touch_executable(root / "bwrap"),
|
||||
codex_command_runner_bin=None,
|
||||
|
||||
@@ -772,6 +772,16 @@ function Test-PackageContentsAreComplete {
|
||||
"codex-resources\codex-command-runner.exe",
|
||||
"codex-resources\codex-windows-sandbox-setup.exe"
|
||||
)
|
||||
try {
|
||||
$metadataPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath((Join-Path $PackageDir "codex-package.json"))
|
||||
$metadata = [System.IO.File]::ReadAllText($metadataPath)
|
||||
# Older canonical packages do not advertise or contain the executor.
|
||||
if ([regex]::IsMatch($metadata, '"execServer"\s*:')) {
|
||||
$expectedFiles += "bin\codex-exec-server.exe"
|
||||
}
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
foreach ($name in $expectedFiles) {
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $PackageDir $name) -PathType Leaf)) {
|
||||
return $false
|
||||
|
||||
@@ -1125,6 +1125,9 @@ install_package_release() {
|
||||
if [ -f "$stage_release/codex-resources/bwrap" ]; then
|
||||
chmod 0755 "$stage_release/codex-resources/bwrap"
|
||||
fi
|
||||
if [ -f "$stage_release/bin/codex-exec-server" ]; then
|
||||
chmod 0755 "$stage_release/bin/codex-exec-server"
|
||||
fi
|
||||
ln -sf "bin/codex" "$stage_release/codex"
|
||||
|
||||
if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then
|
||||
@@ -1178,6 +1181,10 @@ release_dir_is_complete() {
|
||||
[ -x "$release_dir/codex" ] &&
|
||||
[ -x "$release_dir/codex-path/rg" ] ||
|
||||
return 1
|
||||
# Older canonical packages do not advertise or contain the executor.
|
||||
if grep -Eq '"execServer"[[:space:]]*:' "$release_dir/codex-package.json"; then
|
||||
[ -x "$release_dir/bin/codex-exec-server" ] || return 1
|
||||
fi
|
||||
;;
|
||||
legacy-platform-npm)
|
||||
[ -x "$release_dir/codex" ] &&
|
||||
|
||||
@@ -995,6 +995,7 @@ def run_installer_in(
|
||||
root: Path,
|
||||
release: str,
|
||||
*,
|
||||
installer_path: Path = INSTALL_SCRIPT,
|
||||
metadata_failure: bool = False,
|
||||
metadata_json: str | None = None,
|
||||
releases_metadata_json: str | None = None,
|
||||
@@ -1186,7 +1187,7 @@ def run_installer_in(
|
||||
"TRUE" if use_mirror else "false"
|
||||
)
|
||||
result = subprocess.run(
|
||||
["/bin/sh", str(INSTALL_SCRIPT)],
|
||||
["/bin/sh", str(installer_path)],
|
||||
capture_output=True,
|
||||
check=False,
|
||||
env=env,
|
||||
@@ -1213,14 +1214,17 @@ def create_package_release(
|
||||
metadata_version: str = VERSION,
|
||||
binary_version: str = VERSION,
|
||||
target: str = "aarch64-apple-darwin",
|
||||
include_exec_server: bool = False,
|
||||
) -> tuple[Path, Path, str]:
|
||||
package_dir = root / "package"
|
||||
(package_dir / "bin").mkdir(parents=True)
|
||||
(package_dir / "codex-path").mkdir()
|
||||
metadata = {"version": binary_version, "target": target, "entrypoint": "bin/codex"}
|
||||
if include_exec_server:
|
||||
metadata["execServer"] = "bin/codex-exec-server"
|
||||
write_executable(package_dir / "bin/codex-exec-server", "#!/bin/sh\nexit 0\n")
|
||||
(package_dir / "codex-package.json").write_text(
|
||||
json.dumps(
|
||||
{"version": binary_version, "target": target, "entrypoint": "bin/codex"}
|
||||
),
|
||||
json.dumps(metadata),
|
||||
encoding="utf-8",
|
||||
)
|
||||
write_executable(
|
||||
|
||||
Reference in New Issue
Block a user