feat: publish standalone installer archives

This commit is contained in:
Edward Frazer
2026-04-29 10:45:47 -07:00
parent 445629815c
commit 6912b101f0
3 changed files with 191 additions and 1 deletions
+29 -1
View File
@@ -540,13 +540,41 @@ jobs:
--release-version "$RELEASE_VERSION" \
--package codex \
--package codex-responses-api-proxy \
--package codex-sdk
--package codex-sdk \
--standalone-output-dir dist/installer
- name: Stage installer scripts
run: |
cp scripts/install/install.sh dist/install.sh
cp scripts/install/install.ps1 dist/install.ps1
- name: Stage installer checksums
env:
RELEASE_VERSION: ${{ steps.release_name.outputs.name }}
run: |
set -euo pipefail
checksums="dist/codex-installer_SHA256SUMS"
found=false
: > "$checksums"
for archive in dist/installer/codex-standalone-*-"${RELEASE_VERSION}".tar.gz; do
if [[ ! -e "$archive" ]]; then
continue
fi
found=true
digest="$(sha256sum "$archive" | awk '{print $1}')"
printf '%s %s\n' "$digest" "$(basename "$archive")" >> "$checksums"
done
if [[ "$found" != true ]]; then
echo "No Codex standalone installer archives found for ${RELEASE_VERSION}."
exit 1
fi
sort -k2,2 "$checksums" -o "$checksums"
- name: Create GitHub Release
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
with:
+45
View File
@@ -16,6 +16,9 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
BUILD_SCRIPT = REPO_ROOT / "codex-cli" / "scripts" / "build_npm_package.py"
INSTALL_NATIVE_DEPS = REPO_ROOT / "codex-cli" / "scripts" / "install_native_deps.py"
STAGE_STANDALONE_INSTALLER_ARCHIVES = (
REPO_ROOT / "scripts" / "stage_standalone_installer_archives.py"
)
WORKFLOW_NAME = ".github/workflows/rust-release.yml"
GITHUB_REPO = "openai/codex"
@@ -53,6 +56,12 @@ def parse_args() -> argparse.Namespace:
default=None,
help="Directory where npm tarballs should be written (default: dist/npm).",
)
parser.add_argument(
"--standalone-output-dir",
type=Path,
default=None,
help="Directory where standalone installer archives should be written.",
)
parser.add_argument(
"--keep-staging-dirs",
action="store_true",
@@ -130,6 +139,24 @@ def run_command(cmd: list[str]) -> None:
subprocess.run(cmd, cwd=REPO_ROOT, check=True)
def stage_standalone_installer_archives(
release_version: str,
vendor_src: Path,
output_dir: Path,
) -> None:
run_command(
[
str(STAGE_STANDALONE_INSTALLER_ARCHIVES),
"--release-version",
release_version,
"--vendor-src",
str(vendor_src),
"--output-dir",
str(output_dir),
]
)
def tarball_name_for_package(package: str, version: str) -> str:
if package in CODEX_PLATFORM_PACKAGES:
platform = package.removeprefix("codex-")
@@ -192,6 +219,24 @@ def main() -> int:
shutil.rmtree(staging_dir, ignore_errors=True)
final_messages.append(f"Staged {package} at {pack_output}")
if args.standalone_output_dir is not None:
if vendor_src is None:
raise RuntimeError(
"--standalone-output-dir requires staged native binaries, but no selected "
"package installed native components."
)
standalone_output_dir = args.standalone_output_dir
standalone_output_dir.mkdir(parents=True, exist_ok=True)
stage_standalone_installer_archives(
args.release_version,
vendor_src,
standalone_output_dir,
)
final_messages.append(
f"Staged standalone installer archives in {standalone_output_dir}"
)
finally:
if vendor_temp_root is not None and not args.keep_staging_dirs:
shutil.rmtree(vendor_temp_root, ignore_errors=True)
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Stage standalone installer archives for Codex releases."""
from __future__ import annotations
import argparse
import importlib.util
import shutil
import tarfile
import tempfile
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
BUILD_SCRIPT = REPO_ROOT / "codex-cli" / "scripts" / "build_npm_package.py"
_SPEC = importlib.util.spec_from_file_location("codex_build_npm_package", BUILD_SCRIPT)
if _SPEC is None or _SPEC.loader is None:
raise RuntimeError(f"Unable to load module from {BUILD_SCRIPT}")
_BUILD_MODULE = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(_BUILD_MODULE)
CODEX_PLATFORM_PACKAGES = getattr(_BUILD_MODULE, "CODEX_PLATFORM_PACKAGES", {})
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--release-version",
required=True,
help="Version to stage (e.g. 0.1.0 or 0.1.0-alpha.1).",
)
parser.add_argument(
"--vendor-src",
type=Path,
required=True,
help="Directory containing native binaries under vendor/<target>.",
)
parser.add_argument(
"--output-dir",
type=Path,
default=REPO_ROOT / "dist" / "installer",
help="Directory where standalone archives should be written.",
)
return parser.parse_args()
def archive_name(platform_tag: str, version: str) -> str:
return f"codex-standalone-{platform_tag}-{version}.tar.gz"
def copy_executable(source: Path, destination: Path) -> None:
if not source.exists():
raise RuntimeError(f"Missing standalone installer archive input: {source}")
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
destination.chmod(0o755)
def stage_target(vendor_src: Path, staging_dir: Path, target: str, is_windows: bool) -> None:
target_root = vendor_src / target
codex_root = target_root / "codex"
path_root = target_root / "path"
resources_dir = staging_dir / "codex-resources"
resources_dir.mkdir(parents=True, exist_ok=True)
if is_windows:
copy_executable(codex_root / "codex.exe", staging_dir / "codex.exe")
copy_executable(
codex_root / "codex-command-runner.exe",
resources_dir / "codex-command-runner.exe",
)
copy_executable(
codex_root / "codex-windows-sandbox-setup.exe",
resources_dir / "codex-windows-sandbox-setup.exe",
)
copy_executable(path_root / "rg.exe", resources_dir / "rg.exe")
return
copy_executable(codex_root / "codex", staging_dir / "codex")
copy_executable(path_root / "rg", resources_dir / "rg")
def write_archive(staging_dir: Path, output_path: Path) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
with tarfile.open(output_path, "w:gz") as archive:
for path in sorted(staging_dir.rglob("*")):
archive.add(path, arcname=path.relative_to(staging_dir))
def main() -> int:
args = parse_args()
vendor_src = args.vendor_src.resolve()
output_dir = args.output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
for package in sorted(CODEX_PLATFORM_PACKAGES):
package_config = CODEX_PLATFORM_PACKAGES[package]
platform_tag = package_config["npm_tag"]
target = package_config["target_triple"]
is_windows = package_config["os"] == "win32"
output_path = output_dir / archive_name(platform_tag, args.release_version)
with tempfile.TemporaryDirectory(
prefix=f"codex-standalone-{platform_tag}-"
) as staging_dir_str:
staging_dir = Path(staging_dir_str)
stage_target(vendor_src, staging_dir, target, is_windows)
write_archive(staging_dir, output_path)
print(f"Staged standalone installer archive at {output_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())