mirror of
https://github.com/openai/codex.git
synced 2026-05-22 20:14:17 +00:00
## Why Release packaging should be a staging step once release binaries have already been built and signed. The Windows release job was downloading and signing `codex-command-runner.exe` and `codex-windows-sandbox-setup.exe`, but `scripts/build_codex_package.py` still rebuilt those helpers while creating the package archives. That makes the package step slower and, more importantly, risks putting helper binaries in the archive that were produced after the signing step. Linux had the same shape for package resources: `bwrap` could be rebuilt by the package builder instead of being passed in as a prebuilt release artifact. This builds on #23752, which fixes `.tar.zst` creation when Windows runners rely on the repository DotSlash `zstd` wrapper. ## What changed - Add explicit prebuilt resource inputs to the Codex package builder: - `--bwrap-bin` - `--codex-command-runner-bin` - `--codex-windows-sandbox-setup-bin` - Make `.github/scripts/build-codex-package-archive.sh` pass resource binaries from the release output directory when they are already present. - Build Linux `bwrap` for app-server release jobs too, so app-server package creation does not invoke Cargo just to supply the package resource. - Keep macOS package creation as a no-Cargo path when `--entrypoint-bin` is provided, since macOS packages have no resource binaries. - Add unit coverage showing prebuilt macOS, Linux, and Windows package inputs result in no source-built binaries. ## Verification - `python3 -m unittest discover -s scripts/codex_package -p 'test_*.py'` - `python3 -m py_compile scripts/codex_package/*.py` - `bash -n .github/scripts/build-codex-package-archive.sh` - Dry-ran Linux and Windows package builds with fake prebuilt resources and a nonexistent Cargo path to verify the package builder did not invoke Cargo. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23759). * #23760 * __->__ #23759
1572 lines
61 KiB
YAML
1572 lines
61 KiB
YAML
# Release workflow for codex-rs.
|
|
# To release, follow a workflow like:
|
|
# ```
|
|
# git tag -a rust-v0.1.0 -m "Release 0.1.0"
|
|
# git push origin rust-v0.1.0
|
|
# ```
|
|
#
|
|
# To use external macOS signing, manually dispatch `release_mode=build_unsigned`,
|
|
# sign the unsigned macOS artifacts in a secure enclave, upload the signed handoff
|
|
# archive as a GitHub Release asset, then manually dispatch
|
|
# `release_mode=promote_signed` with `unsigned_run_id` and `signed_macos_asset`.
|
|
# The signed handoff archive should contain target or artifact directories such
|
|
# as `aarch64-apple-darwin/` with signed binaries.
|
|
|
|
name: rust-release
|
|
on:
|
|
push:
|
|
tags:
|
|
- "rust-v*.*.*"
|
|
workflow_dispatch:
|
|
inputs:
|
|
release_mode:
|
|
description: "build_unsigned creates unsigned macOS handoff artifacts; promote_signed finishes a release from signed macOS handoff artifacts."
|
|
required: false
|
|
type: choice
|
|
default: build_unsigned
|
|
options:
|
|
- build_unsigned
|
|
- promote_signed
|
|
sign_macos:
|
|
description: "Deprecated compatibility input; use release_mode instead."
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
unsigned_run_id:
|
|
description: "For promote_signed: workflow run id from the build_unsigned run."
|
|
required: false
|
|
type: string
|
|
signed_macos_asset:
|
|
description: "For promote_signed: exact GitHub Release asset name containing signed macOS handoff artifacts."
|
|
required: false
|
|
type: string
|
|
signed_macos_sha256:
|
|
description: "For promote_signed: optional SHA-256 of signed_macos_asset."
|
|
required: false
|
|
type: string
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
tag-check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
|
- name: Validate tag matches Cargo.toml version
|
|
shell: bash
|
|
env:
|
|
RELEASE_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.release_mode || 'signed' }}
|
|
REQUESTED_SIGN_MACOS: ${{ inputs.sign_macos }}
|
|
SIGNED_MACOS_ASSET: ${{ inputs.signed_macos_asset }}
|
|
UNSIGNED_RUN_ID: ${{ inputs.unsigned_run_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
echo "::group::Tag validation"
|
|
|
|
case "${RELEASE_MODE}" in
|
|
signed)
|
|
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
|
echo "❌ Manual rust-release runs must use release_mode=build_unsigned or release_mode=promote_signed"
|
|
exit 1
|
|
fi
|
|
;;
|
|
build_unsigned)
|
|
if [[ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" ]]; then
|
|
echo "❌ release_mode=build_unsigned is only valid for manual runs"
|
|
exit 1
|
|
fi
|
|
;;
|
|
promote_signed)
|
|
if [[ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" ]]; then
|
|
echo "❌ release_mode=promote_signed is only valid for manual runs"
|
|
exit 1
|
|
fi
|
|
if [[ ! "${UNSIGNED_RUN_ID}" =~ ^[0-9]+$ ]]; then
|
|
echo "❌ release_mode=promote_signed requires unsigned_run_id to be a workflow run id"
|
|
exit 1
|
|
fi
|
|
if [[ -z "${SIGNED_MACOS_ASSET}" ]]; then
|
|
echo "❌ release_mode=promote_signed requires signed_macos_asset"
|
|
exit 1
|
|
fi
|
|
if [[ "${SIGNED_MACOS_ASSET}" == */* || "${SIGNED_MACOS_ASSET}" == *"*"* || "${SIGNED_MACOS_ASSET}" == *"?"* || "${SIGNED_MACOS_ASSET}" == *"["* ]]; then
|
|
echo "❌ signed_macos_asset must be an exact release asset name, not a path or glob"
|
|
exit 1
|
|
fi
|
|
if [[ "${UNSIGNED_RUN_ID}" == "${GITHUB_RUN_ID}" ]]; then
|
|
echo "❌ unsigned_run_id must refer to the earlier build_unsigned run, not this run"
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "❌ Unknown release_mode '${RELEASE_MODE}'"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${REQUESTED_SIGN_MACOS}" == "true" ]]; then
|
|
echo "::warning title=Deprecated sign_macos input ignored::Use release_mode=build_unsigned or release_mode=promote_signed instead."
|
|
fi
|
|
|
|
# 1. Must be a tag and match the regex
|
|
[[ "${GITHUB_REF_TYPE}" == "tag" ]] \
|
|
|| { echo "❌ Not a tag push"; exit 1; }
|
|
[[ "${GITHUB_REF_NAME}" =~ ^rust-v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta)(\.[0-9]+)?)?$ ]] \
|
|
|| { echo "❌ Tag '${GITHUB_REF_NAME}' doesn't match expected format"; exit 1; }
|
|
|
|
# 2. Extract versions
|
|
tag_ver="${GITHUB_REF_NAME#rust-v}"
|
|
cargo_ver="$(grep -m1 '^version' codex-rs/Cargo.toml \
|
|
| sed -E 's/version *= *"([^"]+)".*/\1/')"
|
|
|
|
# 3. Compare
|
|
[[ "${tag_ver}" == "${cargo_ver}" ]] \
|
|
|| { echo "❌ Tag ${tag_ver} ≠ Cargo.toml ${cargo_ver}"; exit 1; }
|
|
|
|
echo "✅ Tag and Cargo.toml agree (${tag_ver})"
|
|
echo "::endgroup::"
|
|
|
|
build:
|
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
|
|
needs: tag-check
|
|
name: Build - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
|
|
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
|
# Release builds can take a long time, so leave some headroom to avoid
|
|
# having to restart the full workflow due to a timeout.
|
|
timeout-minutes: 90
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
defaults:
|
|
run:
|
|
working-directory: codex-rs
|
|
env:
|
|
# 2026-03-04: temporarily change releases to use thin LTO because
|
|
# Ubuntu ARM is timing out at 60 minutes.
|
|
CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'thin' }}
|
|
SIGN_MACOS: ${{ github.event_name != 'workflow_dispatch' }}
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- runner: macos-15-xlarge
|
|
target: aarch64-apple-darwin
|
|
bundle: primary
|
|
artifact_name: aarch64-apple-darwin
|
|
binaries: "codex 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"
|
|
build_dmg: "false"
|
|
- runner: macos-15-xlarge
|
|
target: x86_64-apple-darwin
|
|
bundle: primary
|
|
artifact_name: x86_64-apple-darwin
|
|
binaries: "codex 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"
|
|
build_dmg: "false"
|
|
# Release artifacts intentionally ship MUSL-linked Linux binaries.
|
|
- runner: codex-linux-x64-xl
|
|
target: x86_64-unknown-linux-musl
|
|
bundle: primary
|
|
artifact_name: x86_64-unknown-linux-musl
|
|
binaries: "codex codex-responses-api-proxy bwrap"
|
|
build_dmg: "false"
|
|
- runner: codex-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"
|
|
build_dmg: "false"
|
|
- runner: codex-linux-arm64
|
|
target: aarch64-unknown-linux-musl
|
|
bundle: primary
|
|
artifact_name: aarch64-unknown-linux-musl
|
|
binaries: "codex codex-responses-api-proxy bwrap"
|
|
build_dmg: "false"
|
|
- runner: codex-linux-arm64
|
|
target: aarch64-unknown-linux-musl
|
|
bundle: app-server
|
|
artifact_name: aarch64-unknown-linux-musl-app-server
|
|
binaries: "codex-app-server"
|
|
build_dmg: "false"
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
- name: Print runner specs (Linux)
|
|
if: ${{ runner.os == 'Linux' }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
cpu_model="$(lscpu | awk -F: '/Model name/ {gsub(/^[ \t]+/, "", $2); print $2; exit}')"
|
|
total_ram="$(awk '/MemTotal/ {printf "%.1f GiB\n", $2 / 1024 / 1024}' /proc/meminfo)"
|
|
echo "Runner: ${RUNNER_NAME:-unknown}"
|
|
echo "OS: $(uname -a)"
|
|
echo "CPU model: ${cpu_model}"
|
|
echo "Logical CPUs: $(nproc)"
|
|
echo "Total RAM: ${total_ram}"
|
|
echo "Disk usage:"
|
|
df -h .
|
|
- name: Print runner specs (macOS)
|
|
if: ${{ runner.os == 'macOS' }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
total_ram="$(sysctl -n hw.memsize | awk '{printf "%.1f GiB\n", $1 / 1024 / 1024 / 1024}')"
|
|
echo "Runner: ${RUNNER_NAME:-unknown}"
|
|
echo "OS: $(sw_vers -productName) $(sw_vers -productVersion)"
|
|
echo "Hardware model: $(sysctl -n hw.model)"
|
|
echo "CPU architecture: $(uname -m)"
|
|
echo "Logical CPUs: $(sysctl -n hw.logicalcpu)"
|
|
echo "Physical CPUs: $(sysctl -n hw.physicalcpu)"
|
|
echo "Total RAM: ${total_ram}"
|
|
echo "Disk usage:"
|
|
df -h .
|
|
- name: Install Linux bwrap build dependencies
|
|
if: ${{ runner.os == 'Linux' }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
sudo apt-get update -y
|
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
|
- name: Install UBSan runtime (musl)
|
|
if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
sudo apt-get update -y
|
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1
|
|
fi
|
|
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
|
name: Use hermetic Cargo home (musl)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
cargo_home="${GITHUB_WORKSPACE}/.cargo-home"
|
|
mkdir -p "${cargo_home}/bin"
|
|
echo "CARGO_HOME=${cargo_home}" >> "$GITHUB_ENV"
|
|
echo "${cargo_home}/bin" >> "$GITHUB_PATH"
|
|
: > "${cargo_home}/config.toml"
|
|
|
|
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
|
name: Install Zig
|
|
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
|
|
with:
|
|
version: 0.14.0
|
|
use-cache: false
|
|
|
|
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
|
name: Install musl build tools
|
|
env:
|
|
TARGET: ${{ matrix.target }}
|
|
run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh"
|
|
|
|
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
|
name: Configure rustc UBSan wrapper (musl host)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
ubsan=""
|
|
if command -v ldconfig >/dev/null 2>&1; then
|
|
ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')"
|
|
fi
|
|
wrapper_root="${RUNNER_TEMP:-/tmp}"
|
|
wrapper="${wrapper_root}/rustc-ubsan-wrapper"
|
|
cat > "${wrapper}" <<EOF
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
if [[ -n "${ubsan}" ]]; then
|
|
export LD_PRELOAD="${ubsan}\${LD_PRELOAD:+:\${LD_PRELOAD}}"
|
|
fi
|
|
exec "\$1" "\${@:2}"
|
|
EOF
|
|
chmod +x "${wrapper}"
|
|
echo "RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV"
|
|
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
|
|
|
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
|
name: Clear sanitizer flags (musl)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
# Avoid problematic aws-lc jitter entropy code path on musl builders.
|
|
echo "AWS_LC_SYS_NO_JITTER_ENTROPY=1" >> "$GITHUB_ENV"
|
|
target_no_jitter="AWS_LC_SYS_NO_JITTER_ENTROPY_${{ matrix.target }}"
|
|
target_no_jitter="${target_no_jitter//-/_}"
|
|
echo "${target_no_jitter}=1" >> "$GITHUB_ENV"
|
|
|
|
# Clear global Rust flags so host/proc-macro builds don't pull in UBSan.
|
|
echo "RUSTFLAGS=" >> "$GITHUB_ENV"
|
|
echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV"
|
|
echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV"
|
|
# Override any runner-level Cargo config rustflags as well.
|
|
echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV"
|
|
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
|
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
|
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
|
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
|
|
|
sanitize_flags() {
|
|
local input="$1"
|
|
input="${input//-fsanitize=undefined/}"
|
|
input="${input//-fno-sanitize-recover=undefined/}"
|
|
input="${input//-fno-sanitize-trap=undefined/}"
|
|
echo "$input"
|
|
}
|
|
|
|
cflags="$(sanitize_flags "${CFLAGS-}")"
|
|
cxxflags="$(sanitize_flags "${CXXFLAGS-}")"
|
|
echo "CFLAGS=${cflags}" >> "$GITHUB_ENV"
|
|
echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV"
|
|
|
|
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
|
|
name: Configure musl rusty_v8 artifact overrides and verify checksums
|
|
uses: ./.github/actions/setup-rusty-v8-musl
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
|
|
- if: ${{ contains(matrix.target, 'linux') }}
|
|
name: Build bwrap and export digest
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
target="${{ matrix.target }}"
|
|
cargo build --target "$target" --release --timings --bin bwrap
|
|
|
|
bwrap_path="target/${target}/release/bwrap"
|
|
if [[ ! -f "$bwrap_path" ]]; then
|
|
echo "bwrap binary ${bwrap_path} not found"
|
|
exit 1
|
|
fi
|
|
|
|
digest="$(sha256sum "$bwrap_path" | awk '{print $1}')"
|
|
echo "CODEX_BWRAP_SHA256=${digest}" >> "$GITHUB_ENV"
|
|
echo "Built bwrap ${bwrap_path} with sha256:${digest}"
|
|
|
|
- name: Cargo build
|
|
shell: bash
|
|
run: |
|
|
build_args=()
|
|
for binary in ${{ matrix.binaries }}; do
|
|
build_args+=(--bin "$binary")
|
|
done
|
|
echo "CARGO_PROFILE_RELEASE_LTO: ${CARGO_PROFILE_RELEASE_LTO}"
|
|
cargo build --target ${{ matrix.target }} --release --timings "${build_args[@]}"
|
|
|
|
- name: Upload Cargo timings
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: cargo-timings-rust-release-${{ matrix.target }}-${{ matrix.bundle }}
|
|
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
|
if-no-files-found: warn
|
|
|
|
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS != 'true' }}
|
|
name: Stage unsigned macOS artifacts
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
target="${{ matrix.target }}"
|
|
release_dir="target/${target}/release"
|
|
dest="unsigned-dist/${target}"
|
|
mkdir -p "$dest"
|
|
|
|
for binary in ${{ matrix.binaries }}; do
|
|
binary_path="${release_dir}/${binary}"
|
|
unsigned_name="${binary}-${target}-unsigned"
|
|
unsigned_path="${dest}/${unsigned_name}"
|
|
if [[ ! -f "${binary_path}" ]]; then
|
|
echo "Binary ${binary_path} not found"
|
|
exit 1
|
|
fi
|
|
|
|
cp "${binary_path}" "${unsigned_path}"
|
|
tar -C "$dest" -czf "${unsigned_path}.tar.gz" "${unsigned_name}"
|
|
zstd -T0 -19 --rm "${unsigned_path}"
|
|
done
|
|
|
|
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS != 'true' }}
|
|
name: Upload unsigned macOS artifacts
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: ${{ matrix.artifact_name }}-unsigned
|
|
path: codex-rs/unsigned-dist/${{ matrix.target }}/*
|
|
if-no-files-found: error
|
|
|
|
- if: ${{ contains(matrix.target, 'linux') }}
|
|
name: Cosign Linux artifacts
|
|
uses: ./.github/actions/linux-code-sign
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release
|
|
binaries: ${{ matrix.binaries }}
|
|
|
|
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS == 'true' }}
|
|
name: MacOS code signing (binaries)
|
|
uses: ./.github/actions/macos-code-sign
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
binaries: ${{ matrix.binaries }}
|
|
sign-binaries: "true"
|
|
sign-dmg: "false"
|
|
apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
|
apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
|
|
apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
|
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
|
|
|
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' && env.SIGN_MACOS == 'true' }}
|
|
name: Build macOS dmg
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
target="${{ matrix.target }}"
|
|
release_dir="target/${target}/release"
|
|
dmg_root="${RUNNER_TEMP}/codex-dmg-root"
|
|
volname="Codex (${target})"
|
|
dmg_path="${release_dir}/codex-${target}.dmg"
|
|
|
|
# The previous "MacOS code signing (binaries)" step signs + notarizes the
|
|
# built artifacts in `${release_dir}`. This step packages *those same*
|
|
# signed binaries into a dmg.
|
|
rm -rf "$dmg_root"
|
|
mkdir -p "$dmg_root"
|
|
|
|
for binary in ${{ matrix.binaries }}; do
|
|
binary_path="${release_dir}/${binary}"
|
|
if [[ ! -f "${binary_path}" ]]; then
|
|
echo "Binary ${binary_path} not found"
|
|
exit 1
|
|
fi
|
|
ditto "${binary_path}" "${dmg_root}/${binary}"
|
|
done
|
|
|
|
rm -f "$dmg_path"
|
|
hdiutil create \
|
|
-volname "$volname" \
|
|
-srcfolder "$dmg_root" \
|
|
-format UDZO \
|
|
-ov \
|
|
"$dmg_path"
|
|
|
|
if [[ ! -f "$dmg_path" ]]; then
|
|
echo "dmg $dmg_path not found after build"
|
|
exit 1
|
|
fi
|
|
|
|
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' && env.SIGN_MACOS == 'true' }}
|
|
name: MacOS code signing (dmg)
|
|
uses: ./.github/actions/macos-code-sign
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
sign-binaries: "false"
|
|
sign-dmg: "true"
|
|
apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
|
apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
|
|
apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
|
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
|
|
|
- name: Stage artifacts
|
|
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
|
shell: bash
|
|
run: |
|
|
dest="dist/${{ matrix.target }}"
|
|
mkdir -p "$dest"
|
|
|
|
for binary in ${{ matrix.binaries }}; do
|
|
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
|
|
if [[ "${{ matrix.target }}" == *linux* ]]; then
|
|
cp "target/${{ matrix.target }}/release/${binary}.sigstore" \
|
|
"$dest/${binary}-${{ matrix.target }}.sigstore"
|
|
fi
|
|
done
|
|
|
|
if [[ "${{ matrix.target }}" == *linux* && "${{ matrix.bundle }}" == "primary" ]]; then
|
|
bundle_root="${RUNNER_TEMP}/codex-${{ matrix.target }}-bundle"
|
|
rm -rf "$bundle_root"
|
|
mkdir -p "$bundle_root/codex-resources"
|
|
cp "$dest/codex-${{ matrix.target }}" "$bundle_root/codex"
|
|
cp "$dest/bwrap-${{ matrix.target }}" "$bundle_root/codex-resources/bwrap"
|
|
chmod 0755 "$bundle_root/codex" "$bundle_root/codex-resources/bwrap"
|
|
tar -C "$bundle_root" -cf - codex codex-resources/bwrap |
|
|
zstd -T0 -19 -o "$dest/codex-${{ matrix.target }}-bundle.tar.zst"
|
|
fi
|
|
|
|
if [[ "${{ matrix.build_dmg }}" == "true" ]]; then
|
|
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
|
|
fi
|
|
|
|
- name: Build Codex package archive
|
|
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
|
shell: bash
|
|
env:
|
|
TARGET: ${{ matrix.target }}
|
|
BUNDLE: ${{ matrix.bundle }}
|
|
run: |
|
|
set -euo pipefail
|
|
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
|
--target "$TARGET" \
|
|
--bundle "$BUNDLE" \
|
|
--entrypoint-dir "target/${TARGET}/release" \
|
|
--archive-dir "dist/${TARGET}"
|
|
|
|
- name: Build Python runtime wheel
|
|
if: ${{ matrix.bundle == 'primary' && (runner.os != 'macOS' || env.SIGN_MACOS == 'true') }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
case "${{ matrix.target }}" in
|
|
aarch64-apple-darwin)
|
|
platform_tag="macosx_11_0_arm64"
|
|
;;
|
|
x86_64-apple-darwin)
|
|
platform_tag="macosx_10_9_x86_64"
|
|
;;
|
|
aarch64-unknown-linux-musl)
|
|
platform_tag="manylinux_2_17_aarch64"
|
|
;;
|
|
x86_64-unknown-linux-musl)
|
|
platform_tag="manylinux_2_17_x86_64"
|
|
;;
|
|
*)
|
|
echo "No Python runtime wheel platform tag for ${{ matrix.target }}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
python3 -m venv "${RUNNER_TEMP}/python-runtime-build-venv"
|
|
# Do not install into the runner's system Python; macOS runners mark
|
|
# the Homebrew Python as externally managed under PEP 668.
|
|
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m pip install build
|
|
|
|
stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}"
|
|
wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}"
|
|
stage_runtime_args=(
|
|
"${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py"
|
|
stage-runtime
|
|
"$stage_dir"
|
|
"${GITHUB_WORKSPACE}/codex-rs/target/${{ matrix.target }}/release/codex"
|
|
--codex-version "${GITHUB_REF_NAME}"
|
|
--platform-tag "$platform_tag"
|
|
)
|
|
if [[ "${{ matrix.target }}" == *linux* ]]; then
|
|
# Keep bwrap in the runtime wheel so Linux sandbox fallback behavior
|
|
# matches the standalone release bundle on hosts without system bwrap.
|
|
stage_runtime_args+=(
|
|
--resource-binary
|
|
"${GITHUB_WORKSPACE}/codex-rs/target/${{ matrix.target }}/release/bwrap"
|
|
)
|
|
fi
|
|
python3 "${stage_runtime_args[@]}"
|
|
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
|
|
|
|
- name: Upload Python runtime wheel
|
|
if: ${{ matrix.bundle == 'primary' && (runner.os != 'macOS' || env.SIGN_MACOS == 'true') }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: python-runtime-wheel-${{ matrix.target }}
|
|
path: python-runtime-dist/${{ matrix.target }}/*.whl
|
|
if-no-files-found: error
|
|
|
|
- name: Compress artifacts
|
|
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
|
shell: bash
|
|
run: |
|
|
# Path that contains the uncompressed binaries for the current
|
|
# ${{ matrix.target }}
|
|
dest="dist/${{ matrix.target }}"
|
|
|
|
# For compatibility with environments that lack the `zstd` tool we
|
|
# additionally create a `.tar.gz` alongside every binary we publish.
|
|
# The end result is:
|
|
# codex-<target>.zst (existing)
|
|
# codex-<target>.tar.gz (new)
|
|
|
|
# 1. Produce a .tar.gz for every file in the directory *before* we
|
|
# run `zstd --rm`, because that flag deletes the original files.
|
|
for f in "$dest"/*; do
|
|
base="$(basename "$f")"
|
|
# Skip files that are already archives (shouldn't happen, but be
|
|
# safe).
|
|
if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Don't try to compress signature bundles.
|
|
if [[ "$base" == *.sigstore ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Create per-binary tar.gz
|
|
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
|
|
|
|
# Also create .zst and remove the uncompressed binaries to keep
|
|
# non-Windows artifact directories small.
|
|
zstd -T0 -19 --rm "$dest/$base"
|
|
done
|
|
|
|
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
|
with:
|
|
name: ${{ matrix.artifact_name }}
|
|
# Upload the per-binary .zst files, .tar.gz equivalents, and any
|
|
# prebuilt archives staged above.
|
|
path: |
|
|
codex-rs/dist/${{ matrix.target }}/*
|
|
|
|
stage-signed-macos:
|
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.release_mode == 'promote_signed' }}
|
|
needs: tag-check
|
|
name: Stage signed macOS handoff - ${{ matrix.target }} - ${{ matrix.bundle }}
|
|
runs-on: macos-15-xlarge
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
defaults:
|
|
run:
|
|
working-directory: codex-rs
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- target: aarch64-apple-darwin
|
|
bundle: primary
|
|
artifact_name: aarch64-apple-darwin
|
|
binaries: "codex codex-responses-api-proxy"
|
|
build_dmg: "false"
|
|
- target: aarch64-apple-darwin
|
|
bundle: app-server
|
|
artifact_name: aarch64-apple-darwin-app-server
|
|
binaries: "codex-app-server"
|
|
build_dmg: "false"
|
|
- target: x86_64-apple-darwin
|
|
bundle: primary
|
|
artifact_name: x86_64-apple-darwin
|
|
binaries: "codex codex-responses-api-proxy"
|
|
build_dmg: "false"
|
|
- target: x86_64-apple-darwin
|
|
bundle: app-server
|
|
artifact_name: x86_64-apple-darwin-app-server
|
|
binaries: "codex-app-server"
|
|
build_dmg: "false"
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Download signed macOS handoff
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
SIGNED_MACOS_ASSET: ${{ inputs.signed_macos_asset }}
|
|
SIGNED_MACOS_SHA256: ${{ inputs.signed_macos_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
download_dir="${RUNNER_TEMP}/signed-macos-download"
|
|
handoff_dir="${RUNNER_TEMP}/signed-macos-handoff"
|
|
rm -rf "$download_dir" "$handoff_dir"
|
|
mkdir -p "$download_dir" "$handoff_dir"
|
|
|
|
gh release download "$GITHUB_REF_NAME" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--pattern "$SIGNED_MACOS_ASSET" \
|
|
--dir "$download_dir"
|
|
|
|
asset_count="$(find "$download_dir" -maxdepth 1 -type f | wc -l | tr -d '[:space:]')"
|
|
if [[ "$asset_count" != "1" ]]; then
|
|
echo "Expected exactly one signed macOS handoff asset named ${SIGNED_MACOS_ASSET}; found ${asset_count}"
|
|
find "$download_dir" -maxdepth 1 -type f -print
|
|
exit 1
|
|
fi
|
|
|
|
asset_path="$(find "$download_dir" -maxdepth 1 -type f -print -quit)"
|
|
if [[ -n "${SIGNED_MACOS_SHA256}" ]]; then
|
|
expected_sha="$(printf '%s' "$SIGNED_MACOS_SHA256" | tr '[:upper:]' '[:lower:]')"
|
|
actual_sha="$(shasum -a 256 "$asset_path" | awk '{print $1}')"
|
|
if [[ "$actual_sha" != "$expected_sha" ]]; then
|
|
echo "signed_macos_sha256 mismatch for ${SIGNED_MACOS_ASSET}"
|
|
echo "expected: ${expected_sha}"
|
|
echo "actual: ${actual_sha}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
asset_name="$(basename "$asset_path")"
|
|
case "$asset_name" in
|
|
*.tar.zst)
|
|
zstd -dc "$asset_path" | tar -C "$handoff_dir" -xf -
|
|
;;
|
|
*.tar.gz|*.tgz)
|
|
tar -C "$handoff_dir" -xzf "$asset_path"
|
|
;;
|
|
*.zip)
|
|
ditto -x -k "$asset_path" "$handoff_dir"
|
|
;;
|
|
*)
|
|
echo "Unsupported signed macOS handoff archive format: ${asset_name}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "SIGNED_MACOS_HANDOFF_DIR=$handoff_dir" >> "$GITHUB_ENV"
|
|
|
|
- name: Stage signed macOS artifacts
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
target="${{ matrix.target }}"
|
|
artifact_name="${{ matrix.artifact_name }}"
|
|
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/${artifact_name}"
|
|
if [[ ! -d "$source_dir" && -d "${SIGNED_MACOS_HANDOFF_DIR}/dist/${artifact_name}" ]]; then
|
|
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/dist/${artifact_name}"
|
|
fi
|
|
if [[ ! -d "$source_dir" && -d "${SIGNED_MACOS_HANDOFF_DIR}/${target}" ]]; then
|
|
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/${target}"
|
|
fi
|
|
if [[ ! -d "$source_dir" && -d "${SIGNED_MACOS_HANDOFF_DIR}/dist/${target}" ]]; then
|
|
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/dist/${target}"
|
|
fi
|
|
if [[ ! -d "$source_dir" ]]; then
|
|
echo "Signed macOS handoff is missing ${artifact_name}/"
|
|
echo "Expected either:"
|
|
echo " ${SIGNED_MACOS_HANDOFF_DIR}/${artifact_name}"
|
|
echo " ${SIGNED_MACOS_HANDOFF_DIR}/dist/${artifact_name}"
|
|
echo " ${SIGNED_MACOS_HANDOFF_DIR}/${target}"
|
|
echo " ${SIGNED_MACOS_HANDOFF_DIR}/dist/${target}"
|
|
find "$SIGNED_MACOS_HANDOFF_DIR" -maxdepth 3 -type f -print
|
|
exit 1
|
|
fi
|
|
|
|
dest="dist/${target}"
|
|
mkdir -p "$dest"
|
|
|
|
for binary in ${{ matrix.binaries }}; do
|
|
source_path="${source_dir}/${binary}"
|
|
if [[ ! -f "$source_path" ]]; then
|
|
source_path="${source_dir}/${binary}-${target}"
|
|
fi
|
|
if [[ ! -f "$source_path" ]]; then
|
|
echo "Signed macOS handoff is missing ${binary} for ${artifact_name}"
|
|
exit 1
|
|
fi
|
|
|
|
release_path="${dest}/${binary}-${target}"
|
|
ditto "$source_path" "$release_path"
|
|
chmod 0755 "$release_path"
|
|
codesign --verify --strict --verbose=2 "$release_path"
|
|
done
|
|
|
|
# DMG staging is disabled for signed promotion because we no longer
|
|
# distribute DMGs from this release path. Keep the branch here so the
|
|
# handoff can opt back in by flipping matrix.build_dmg if needed.
|
|
if [[ "${{ matrix.build_dmg }}" == "true" ]]; then
|
|
dmg_name="codex-${target}.dmg"
|
|
dmg_source="${source_dir}/${dmg_name}"
|
|
if [[ ! -f "$dmg_source" ]]; then
|
|
echo "Signed macOS handoff is missing ${dmg_name} for ${artifact_name}"
|
|
exit 1
|
|
fi
|
|
|
|
codesign --verify --strict --verbose=2 "$dmg_source"
|
|
xcrun stapler validate "$dmg_source"
|
|
cp "$dmg_source" "$dest/$dmg_name"
|
|
fi
|
|
|
|
- name: Build Python runtime wheel
|
|
if: ${{ matrix.bundle == 'primary' }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
case "${{ matrix.target }}" in
|
|
aarch64-apple-darwin)
|
|
platform_tag="macosx_11_0_arm64"
|
|
;;
|
|
x86_64-apple-darwin)
|
|
platform_tag="macosx_10_9_x86_64"
|
|
;;
|
|
*)
|
|
echo "No Python runtime wheel platform tag for ${{ matrix.target }}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
python3 -m venv "${RUNNER_TEMP}/python-runtime-build-venv"
|
|
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m pip install build
|
|
|
|
stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}"
|
|
wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}"
|
|
python3 \
|
|
"${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \
|
|
stage-runtime \
|
|
"$stage_dir" \
|
|
"${GITHUB_WORKSPACE}/codex-rs/dist/${{ matrix.target }}/codex-${{ matrix.target }}" \
|
|
--codex-version "${GITHUB_REF_NAME}" \
|
|
--platform-tag "$platform_tag"
|
|
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
|
|
|
|
- name: Build Codex package archive
|
|
shell: bash
|
|
env:
|
|
TARGET: ${{ matrix.target }}
|
|
BUNDLE: ${{ matrix.bundle }}
|
|
run: |
|
|
set -euo pipefail
|
|
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
|
--target "$TARGET" \
|
|
--bundle "$BUNDLE" \
|
|
--entrypoint-dir "dist/${TARGET}" \
|
|
--archive-dir "dist/${TARGET}" \
|
|
--target-suffixed-entrypoint
|
|
|
|
- name: Upload Python runtime wheel
|
|
if: ${{ matrix.bundle == 'primary' }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: python-runtime-wheel-${{ matrix.target }}
|
|
path: python-runtime-dist/${{ matrix.target }}/*.whl
|
|
if-no-files-found: error
|
|
|
|
- name: Compress artifacts
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
dest="dist/${{ matrix.target }}"
|
|
for f in "$dest"/*; do
|
|
base="$(basename "$f")"
|
|
if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
|
|
continue
|
|
fi
|
|
|
|
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
|
|
zstd -T0 -19 --rm "$dest/$base"
|
|
done
|
|
|
|
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: ${{ matrix.artifact_name }}
|
|
path: |
|
|
codex-rs/dist/${{ matrix.target }}/*
|
|
|
|
build-windows:
|
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
|
|
needs: tag-check
|
|
uses: ./.github/workflows/rust-release-windows.yml
|
|
with:
|
|
release-lto: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }}
|
|
secrets: inherit
|
|
|
|
argument-comment-lint-release-assets:
|
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
|
|
name: argument-comment-lint release assets
|
|
needs: tag-check
|
|
uses: ./.github/workflows/rust-release-argument-comment-lint.yml
|
|
with:
|
|
publish: true
|
|
|
|
zsh-release-assets:
|
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
|
|
name: zsh release assets
|
|
needs: tag-check
|
|
uses: ./.github/workflows/rust-release-zsh.yml
|
|
|
|
release:
|
|
needs:
|
|
- tag-check
|
|
- build
|
|
- stage-signed-macos
|
|
- build-windows
|
|
- argument-comment-lint-release-assets
|
|
- zsh-release-assets
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
needs.tag-check.result == 'success' &&
|
|
(
|
|
(
|
|
github.event_name == 'workflow_dispatch' &&
|
|
inputs.release_mode == 'promote_signed' &&
|
|
needs.stage-signed-macos.result == 'success' &&
|
|
needs.build.result == 'skipped' &&
|
|
needs.build-windows.result == 'skipped' &&
|
|
needs.argument-comment-lint-release-assets.result == 'skipped' &&
|
|
needs.zsh-release-assets.result == 'skipped'
|
|
) ||
|
|
(
|
|
(github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed') &&
|
|
needs.build.result == 'success' &&
|
|
needs.stage-signed-macos.result == 'skipped' &&
|
|
needs.build-windows.result == 'success' &&
|
|
needs.argument-comment-lint-release-assets.result == 'success' &&
|
|
needs.zsh-release-assets.result == 'success'
|
|
)
|
|
)
|
|
}}
|
|
name: release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
actions: read
|
|
env:
|
|
RELEASE_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.release_mode || 'signed' }}
|
|
SIGN_MACOS: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode == 'promote_signed' }}
|
|
SIGNED_MACOS_ASSET: ${{ inputs.signed_macos_asset }}
|
|
UNSIGNED_RUN_ID: ${{ inputs.unsigned_run_id }}
|
|
outputs:
|
|
version: ${{ steps.release_name.outputs.name }}
|
|
tag: ${{ github.ref_name }}
|
|
sign_macos: ${{ steps.release_mode.outputs.sign_macos }}
|
|
should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }}
|
|
npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }}
|
|
should_publish_python_runtime: ${{ steps.python_runtime_publish_settings.outputs.should_publish }}
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Define release mode
|
|
id: release_mode
|
|
run: |
|
|
echo "release_mode=${RELEASE_MODE}" >> "$GITHUB_OUTPUT"
|
|
echo "sign_macos=${SIGN_MACOS}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Generate release notes from tag commit message
|
|
id: release_notes
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# On tag pushes, GITHUB_SHA may be a tag object for annotated tags;
|
|
# peel it to the underlying commit.
|
|
commit="$(git rev-parse "${GITHUB_SHA}^{commit}")"
|
|
notes_path="${RUNNER_TEMP}/release-notes.md"
|
|
|
|
# Use the commit message for the commit the tag points at (not the
|
|
# annotated tag message).
|
|
git log -1 --format=%B "${commit}" > "${notes_path}"
|
|
# Ensure trailing newline so GitHub's markdown renderer doesn't
|
|
# occasionally run the last line into subsequent content.
|
|
echo >> "${notes_path}"
|
|
|
|
echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
path: dist
|
|
|
|
- name: Validate unsigned build run
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
run_summary="$(gh run view "$UNSIGNED_RUN_ID" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--json conclusion,event,headBranch,headSha,status,workflowName,url \
|
|
--jq '[.workflowName, .event, .headBranch, .headSha, .status, .conclusion, .url] | @tsv')"
|
|
IFS=$'\t' read -r workflow_name event head_branch head_sha status conclusion run_url <<< "$run_summary"
|
|
expected_head_sha="$(git rev-parse "${GITHUB_SHA}^{commit}")"
|
|
|
|
if [[ "$workflow_name" != "$GITHUB_WORKFLOW" ]]; then
|
|
echo "unsigned_run_id ${UNSIGNED_RUN_ID} is for workflow '${workflow_name}', expected '${GITHUB_WORKFLOW}'"
|
|
echo "Run URL: ${run_url}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$event" != "workflow_dispatch" ]]; then
|
|
echo "unsigned_run_id ${UNSIGNED_RUN_ID} was triggered by '${event}', expected 'workflow_dispatch'"
|
|
echo "Run URL: ${run_url}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$head_branch" != "$GITHUB_REF_NAME" ]]; then
|
|
echo "unsigned_run_id ${UNSIGNED_RUN_ID} used ref '${head_branch}', expected '${GITHUB_REF_NAME}'"
|
|
echo "Run URL: ${run_url}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$head_sha" != "$expected_head_sha" ]]; then
|
|
echo "unsigned_run_id ${UNSIGNED_RUN_ID} used head SHA '${head_sha}', expected '${expected_head_sha}'"
|
|
echo "Run URL: ${run_url}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$status" != "completed" || "$conclusion" != "success" ]]; then
|
|
echo "unsigned_run_id ${UNSIGNED_RUN_ID} is ${status}/${conclusion}, expected completed/success"
|
|
echo "Run URL: ${run_url}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Download artifacts from unsigned build run
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh run download "$UNSIGNED_RUN_ID" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--dir dist
|
|
|
|
- name: Remove unsigned macOS staging artifacts
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
run: |
|
|
set -euo pipefail
|
|
find dist -mindepth 1 -maxdepth 1 -type d \
|
|
-name '*-apple-darwin*-unsigned' \
|
|
-exec rm -rf {} +
|
|
|
|
- name: Re-upload promoted Linux x64 artifacts
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: x86_64-unknown-linux-musl
|
|
path: dist/x86_64-unknown-linux-musl/*
|
|
if-no-files-found: error
|
|
|
|
- name: Re-upload promoted Linux arm64 artifacts
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: aarch64-unknown-linux-musl
|
|
path: dist/aarch64-unknown-linux-musl/*
|
|
if-no-files-found: error
|
|
|
|
- name: Re-upload promoted Windows x64 artifacts
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: x86_64-pc-windows-msvc
|
|
path: dist/x86_64-pc-windows-msvc/*
|
|
if-no-files-found: error
|
|
|
|
- name: Re-upload promoted Windows arm64 artifacts
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: aarch64-pc-windows-msvc
|
|
path: dist/aarch64-pc-windows-msvc/*
|
|
if-no-files-found: error
|
|
|
|
- name: List
|
|
run: ls -R dist/
|
|
|
|
- name: Prune artifacts excluded from unsigned macOS release
|
|
if: ${{ env.SIGN_MACOS == 'false' }}
|
|
run: |
|
|
find dist -mindepth 1 -maxdepth 1 -type d \
|
|
! -name '*-apple-darwin*-unsigned' \
|
|
! -name 'aarch64-unknown-linux-musl' \
|
|
! -name 'aarch64-unknown-linux-musl-app-server' \
|
|
! -name 'x86_64-unknown-linux-musl' \
|
|
! -name 'x86_64-unknown-linux-musl-app-server' \
|
|
! -name 'aarch64-pc-windows-msvc' \
|
|
! -name 'x86_64-pc-windows-msvc' \
|
|
-exec rm -rf {} +
|
|
|
|
if ! find dist -type f -name '*-apple-darwin*-unsigned*' | grep -q .; then
|
|
echo "No unsigned macOS artifacts found in downloaded workflow artifacts."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Delete entries from dist/ that should not go in the release
|
|
run: |
|
|
rm -rf dist/windows-binaries*
|
|
# cargo-timing.html appears under multiple target-specific directories.
|
|
# If included in files: dist/**, release upload races on duplicate
|
|
# asset names and can fail with 404s.
|
|
find dist -type f -name 'cargo-timing.html' -delete
|
|
find dist -type d -empty -delete
|
|
|
|
ls -R dist/
|
|
|
|
- name: Add Codex package checksum manifest
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
manifest="dist/codex-package_SHA256SUMS"
|
|
tmp_manifest="$(mktemp)"
|
|
find dist -type f \
|
|
\( -name 'codex-package-*.tar.gz' -o -name 'codex-app-server-package-*.tar.gz' \) \
|
|
-print |
|
|
sort |
|
|
while IFS= read -r archive; do
|
|
sha256sum "$archive" |
|
|
awk -v name="$(basename "$archive")" '{ print $1 " " name }'
|
|
done > "$tmp_manifest"
|
|
|
|
if [[ ! -s "$tmp_manifest" ]]; then
|
|
echo "No Codex package archives found for checksum manifest"
|
|
exit 1
|
|
fi
|
|
|
|
mv "$tmp_manifest" "$manifest"
|
|
cat "$manifest"
|
|
|
|
- name: Add config schema release asset
|
|
run: |
|
|
cp codex-rs/core/config.schema.json dist/config-schema.json
|
|
|
|
- name: Define release name
|
|
id: release_name
|
|
run: |
|
|
# Extract the version from the tag name, which is in the format
|
|
# "rust-v0.1.0".
|
|
version="${GITHUB_REF_NAME#rust-v}"
|
|
echo "name=${version}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Determine npm publish settings
|
|
id: npm_publish_settings
|
|
env:
|
|
VERSION: ${{ steps.release_name.outputs.name }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${VERSION}"
|
|
|
|
if [[ "${SIGN_MACOS}" != "true" ]]; then
|
|
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
echo "npm_tag=" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
echo "npm_tag=" >> "$GITHUB_OUTPUT"
|
|
elif [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then
|
|
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
echo "npm_tag=alpha" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
echo "npm_tag=" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Determine Python runtime publish settings
|
|
id: python_runtime_publish_settings
|
|
env:
|
|
VERSION: ${{ steps.release_name.outputs.name }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${VERSION}"
|
|
|
|
if [[ "${SIGN_MACOS}" != "true" ]]; then
|
|
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
elif [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then
|
|
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Setup pnpm
|
|
if: ${{ env.SIGN_MACOS == 'true' }}
|
|
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
|
with:
|
|
run_install: false
|
|
|
|
- name: Setup Node.js for npm packaging
|
|
if: ${{ env.SIGN_MACOS == 'true' }}
|
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Install dependencies
|
|
if: ${{ env.SIGN_MACOS == 'true' }}
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Stage npm packages
|
|
if: ${{ env.SIGN_MACOS == 'true' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_VERSION: ${{ steps.release_name.outputs.name }}
|
|
run: |
|
|
workflow_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
|
./scripts/stage_npm_packages.py \
|
|
--release-version "$RELEASE_VERSION" \
|
|
--workflow-url "$workflow_url" \
|
|
--package codex \
|
|
--package codex-responses-api-proxy \
|
|
--package codex-sdk
|
|
|
|
- name: Stage installer scripts
|
|
if: ${{ env.SIGN_MACOS == 'true' }}
|
|
run: |
|
|
cp scripts/install/install.sh dist/install.sh
|
|
cp scripts/install/install.ps1 dist/install.ps1
|
|
|
|
- name: Create GitHub Release
|
|
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
|
|
with:
|
|
name: ${{ steps.release_name.outputs.name }}
|
|
tag_name: ${{ github.ref_name }}
|
|
body_path: ${{ steps.release_notes.outputs.path }}
|
|
files: dist/**
|
|
overwrite_files: true
|
|
make_latest: ${{ env.SIGN_MACOS == 'true' && !contains(steps.release_name.outputs.name, '-') }}
|
|
# Mark as prerelease only when the version has a suffix after x.y.z
|
|
# (e.g. -alpha, -beta). Otherwise publish a normal release.
|
|
prerelease: ${{ contains(steps.release_name.outputs.name, '-') }}
|
|
|
|
- name: Clean up signed promotion handoff assets
|
|
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
release_id="$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${GITHUB_REF_NAME}" --jq '.id')"
|
|
gh api --paginate "repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets" \
|
|
--jq '.[] | [.id, .name] | @tsv' |
|
|
while IFS=$'\t' read -r asset_id asset_name; do
|
|
if [[ -z "$asset_id" || -z "$asset_name" ]]; then
|
|
continue
|
|
fi
|
|
|
|
delete_asset=false
|
|
if [[ "$asset_name" == *unsigned* || "$asset_name" == "$SIGNED_MACOS_ASSET" ]]; then
|
|
delete_asset=true
|
|
fi
|
|
|
|
if [[ "$delete_asset" == "true" ]]; then
|
|
echo "Deleting release asset ${asset_name}"
|
|
gh api -X DELETE "repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}"
|
|
fi
|
|
done
|
|
|
|
- if: ${{ env.SIGN_MACOS == 'true' }}
|
|
uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
tag: ${{ github.ref_name }}
|
|
config: .github/dotslash-config.json
|
|
|
|
- if: ${{ env.SIGN_MACOS == 'true' }}
|
|
uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
tag: ${{ github.ref_name }}
|
|
config: .github/dotslash-zsh-config.json
|
|
|
|
- if: ${{ env.SIGN_MACOS == 'true' }}
|
|
uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
tag: ${{ github.ref_name }}
|
|
config: .github/dotslash-argument-comment-lint-config.json
|
|
|
|
- name: Trigger developers.openai.com deploy
|
|
# Only trigger the deploy if the release is not a pre-release.
|
|
# The deploy is used to update the developers.openai.com website with the new config schema json file.
|
|
if: ${{ env.SIGN_MACOS == 'true' && !contains(steps.release_name.outputs.name, '-') }}
|
|
continue-on-error: true
|
|
env:
|
|
DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL: ${{ secrets.DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL }}
|
|
run: |
|
|
if ! curl -sS -f -o /dev/null -X POST "$DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL"; then
|
|
echo "::warning title=developers.openai.com deploy hook failed::Vercel deploy hook POST failed for ${GITHUB_REF_NAME}"
|
|
exit 1
|
|
fi
|
|
|
|
# Publish to npm using OIDC authentication.
|
|
# July 31, 2025: https://github.blog/changelog/2025-07-31-npm-trusted-publishing-with-oidc-is-generally-available/
|
|
# npm docs: https://docs.npmjs.com/trusted-publishers
|
|
publish-npm:
|
|
# Publish to npm for stable releases and alpha pre-releases with numeric suffixes.
|
|
# promote_signed intentionally skips build jobs that are ancestors of release;
|
|
# include the !cancelled() status function so Actions does not apply its implicit
|
|
# success() check to the whole dependency chain before evaluating release outputs.
|
|
if: >-
|
|
${{
|
|
!cancelled() &&
|
|
needs.release.result == 'success' &&
|
|
needs.release.outputs.should_publish_npm == 'true'
|
|
}}
|
|
name: publish-npm
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
id-token: write # Required for OIDC
|
|
contents: read
|
|
|
|
steps:
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
|
with:
|
|
# Node 24 bundles npm >= 11.5.1, which trusted publishing requires.
|
|
node-version: 24
|
|
registry-url: "https://registry.npmjs.org"
|
|
scope: "@openai"
|
|
|
|
- name: Download npm tarballs from release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
RELEASE_TAG: ${{ needs.release.outputs.tag }}
|
|
RELEASE_VERSION: ${{ needs.release.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="$RELEASE_VERSION"
|
|
tag="$RELEASE_TAG"
|
|
mkdir -p dist/npm
|
|
patterns=(
|
|
"codex-npm-${version}.tgz"
|
|
"codex-npm-linux-*-${version}.tgz"
|
|
"codex-npm-darwin-*-${version}.tgz"
|
|
"codex-npm-win32-*-${version}.tgz"
|
|
"codex-responses-api-proxy-npm-${version}.tgz"
|
|
"codex-sdk-npm-${version}.tgz"
|
|
)
|
|
for pattern in "${patterns[@]}"; do
|
|
gh release download "$tag" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--pattern "$pattern" \
|
|
--dir dist/npm
|
|
done
|
|
|
|
# No NODE_AUTH_TOKEN needed because we use OIDC.
|
|
- name: Publish to npm
|
|
env:
|
|
VERSION: ${{ needs.release.outputs.version }}
|
|
NPM_TAG: ${{ needs.release.outputs.npm_tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
prefix=""
|
|
if [[ -n "${NPM_TAG}" ]]; then
|
|
prefix="${NPM_TAG}-"
|
|
fi
|
|
|
|
root_tarball="dist/npm/codex-npm-${VERSION}.tgz"
|
|
sdk_tarball="dist/npm/codex-sdk-npm-${VERSION}.tgz"
|
|
# Keep this list in sync with CODEX_PLATFORM_PACKAGES in
|
|
# codex-cli/scripts/build_npm_package.py. The root wrapper advances
|
|
# @openai/codex@latest as soon as it publishes, so every platform
|
|
# package it aliases must already exist in the registry first.
|
|
platform_tarballs=(
|
|
"dist/npm/codex-npm-linux-x64-${VERSION}.tgz"
|
|
"dist/npm/codex-npm-linux-arm64-${VERSION}.tgz"
|
|
"dist/npm/codex-npm-darwin-x64-${VERSION}.tgz"
|
|
"dist/npm/codex-npm-darwin-arm64-${VERSION}.tgz"
|
|
"dist/npm/codex-npm-win32-x64-${VERSION}.tgz"
|
|
"dist/npm/codex-npm-win32-arm64-${VERSION}.tgz"
|
|
)
|
|
|
|
for required_tarball in "${platform_tarballs[@]}" "${root_tarball}"; do
|
|
if [[ ! -f "${required_tarball}" ]]; then
|
|
echo "Missing npm tarball: ${required_tarball}"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
shopt -s nullglob
|
|
other_tarballs=()
|
|
for tarball in dist/npm/*-"${VERSION}".tgz; do
|
|
if [[ "${tarball}" == "${root_tarball}" || "${tarball}" == "${sdk_tarball}" ]]; then
|
|
continue
|
|
fi
|
|
|
|
is_platform_tarball=false
|
|
for platform_tarball in "${platform_tarballs[@]}"; do
|
|
if [[ "${tarball}" == "${platform_tarball}" ]]; then
|
|
is_platform_tarball=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "${is_platform_tarball}" == true ]]; then
|
|
continue
|
|
fi
|
|
|
|
other_tarballs+=("${tarball}")
|
|
done
|
|
|
|
# Publish the platform packages before the root CLI wrapper. The root
|
|
# wrapper advances @openai/codex@latest, so it should only publish
|
|
# after the optional dependency versions it references exist.
|
|
tarballs=(
|
|
"${platform_tarballs[@]}"
|
|
"${other_tarballs[@]}"
|
|
"${root_tarball}"
|
|
)
|
|
if [[ -f "${sdk_tarball}" ]]; then
|
|
tarballs+=("${sdk_tarball}")
|
|
fi
|
|
|
|
for tarball in "${tarballs[@]}"; do
|
|
filename="$(basename "${tarball}")"
|
|
tag=""
|
|
|
|
case "${filename}" in
|
|
codex-npm-linux-*-"${VERSION}".tgz|codex-npm-darwin-*-"${VERSION}".tgz|codex-npm-win32-*-"${VERSION}".tgz)
|
|
platform="${filename#codex-npm-}"
|
|
platform="${platform%-${VERSION}.tgz}"
|
|
tag="${prefix}${platform}"
|
|
;;
|
|
codex-npm-"${VERSION}".tgz|codex-responses-api-proxy-npm-"${VERSION}".tgz|codex-sdk-npm-"${VERSION}".tgz)
|
|
tag="${NPM_TAG}"
|
|
;;
|
|
*)
|
|
echo "Unexpected npm tarball: ${filename}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
publish_cmd=(npm publish "${GITHUB_WORKSPACE}/${tarball}")
|
|
if [[ -n "${tag}" ]]; then
|
|
publish_cmd+=(--tag "${tag}")
|
|
fi
|
|
|
|
echo "+ ${publish_cmd[*]}"
|
|
set +e
|
|
publish_output="$("${publish_cmd[@]}" 2>&1)"
|
|
publish_status=$?
|
|
set -e
|
|
|
|
echo "${publish_output}"
|
|
if [[ ${publish_status} -eq 0 ]]; then
|
|
continue
|
|
fi
|
|
|
|
if grep -qiE "previously published|cannot publish over|version already exists" <<< "${publish_output}"; then
|
|
echo "Skipping already-published package version for ${filename}"
|
|
continue
|
|
fi
|
|
|
|
exit "${publish_status}"
|
|
done
|
|
|
|
# Publish the platform-specific Python runtime wheels using PyPI trusted publishing.
|
|
# PyPI project configuration must trust this workflow and job. Keep this
|
|
# non-blocking while the Python runtime publishing path is new; failures still
|
|
# need release follow-up, but should not invalidate the Rust release itself.
|
|
publish-python-runtime:
|
|
# Publish to PyPI for stable releases and alpha pre-releases with numeric suffixes.
|
|
if: >-
|
|
${{
|
|
!cancelled() &&
|
|
needs.release.result == 'success' &&
|
|
needs.release.outputs.should_publish_python_runtime == 'true'
|
|
}}
|
|
name: publish-python-runtime
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
continue-on-error: true
|
|
environment: pypi
|
|
permissions:
|
|
id-token: write # Required for PyPI trusted publishing.
|
|
contents: read
|
|
|
|
steps:
|
|
- name: Download Python runtime wheels from release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
RELEASE_TAG: ${{ needs.release.outputs.tag }}
|
|
RELEASE_VERSION: ${{ needs.release.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
python_version="$RELEASE_VERSION"
|
|
python_version="${python_version/-alpha./a}"
|
|
python_version="${python_version/-beta./b}"
|
|
python_version="${python_version/-rc./rc}"
|
|
|
|
mkdir -p dist/python-runtime
|
|
gh release download "$RELEASE_TAG" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--pattern "openai_codex_cli_bin-${python_version}-*.whl" \
|
|
--dir dist/python-runtime
|
|
ls -lh dist/python-runtime
|
|
|
|
- name: Publish Python runtime wheels to PyPI
|
|
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
|
with:
|
|
packages-dir: dist/python-runtime
|
|
skip-existing: true
|
|
|
|
winget:
|
|
name: winget
|
|
needs: release
|
|
# Only publish stable/mainline releases to WinGet; pre-releases include a
|
|
# '-' in the semver string (e.g., 1.2.3-alpha.1).
|
|
if: >-
|
|
${{
|
|
!cancelled() &&
|
|
needs.release.result == 'success' &&
|
|
needs.release.outputs.sign_macos == 'true' &&
|
|
!contains(needs.release.outputs.version, '-')
|
|
}}
|
|
# This job only invokes a GitHub Action to open/update the winget-pkgs PR;
|
|
# it does not execute Windows-only tooling, so Linux is sufficient.
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- name: Publish to WinGet
|
|
uses: vedantmgoyal9/winget-releaser@7bd472be23763def6e16bd06cc8b1cdfab0e2fd5
|
|
with:
|
|
identifier: OpenAI.Codex
|
|
version: ${{ needs.release.outputs.version }}
|
|
release-tag: ${{ needs.release.outputs.tag }}
|
|
fork-user: openai-oss-forks
|
|
installers-regex: '^codex-(?:x86_64|aarch64)-pc-windows-msvc\.exe\.zip$'
|
|
token: ${{ secrets.WINGET_PUBLISH_PAT }}
|
|
|
|
update-branch:
|
|
name: Update latest-alpha-cli branch
|
|
if: >-
|
|
${{
|
|
!cancelled() &&
|
|
needs.release.result == 'success' &&
|
|
needs.release.outputs.sign_macos == 'true'
|
|
}}
|
|
permissions:
|
|
contents: write
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Update latest-alpha-cli branch
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh api \
|
|
repos/${GITHUB_REPOSITORY}/git/refs/heads/latest-alpha-cli \
|
|
-X PATCH \
|
|
-f sha="${GITHUB_SHA}" \
|
|
-F force=true
|