mirror of
https://github.com/openai/codex.git
synced 2026-05-15 16:53:05 +00:00
## Why `rust-release.yml` can create unsigned macOS artifacts for external signing, but there was no signed resume path after those artifacts returned from a secure enclave. Release operators need a way to reuse the first run artifacts, ingest signed macOS binaries and DMGs, and continue the normal signed release path without rebuilding every platform or treating handoff assets as final release assets. ## How this is meant to be used First, start the release as an unsigned macOS build against the release tag: ```shell gh workflow run rust-release.yml \ --repo openai/codex \ --ref rust-vX.Y.Z \ -f release_mode=build_unsigned ``` That run builds the normal Linux/Windows artifacts and publishes unsigned macOS handoff artifacts. The unsigned macOS binaries are then copied to the secure enclave, signed and notarized there, packaged as a signed handoff archive, and uploaded back to the GitHub Release for the same tag. The signed handoff asset should contain either target directories such as `aarch64-apple-darwin/` and `x86_64-apple-darwin/`, or artifact directories such as `aarch64-apple-darwin-app-server/`. The promote workflow accepts either layout. The directories should contain the signed binaries and, for primary macOS bundles, the signed and stapled DMGs. For example, after signing, upload the handoff asset to the release: ```shell gh release upload rust-vX.Y.Z \ signed-macos-rust-vX.Y.Z.tar.zst \ --repo openai/codex \ --clobber ``` Then start the promotion run. `unsigned_run_id` is the workflow run id from the first `build_unsigned` run, and `signed_macos_asset` is the exact Release asset name uploaded by the secure enclave: ```shell gh workflow run rust-release.yml \ --repo openai/codex \ --ref rust-vX.Y.Z \ -f release_mode=promote_signed \ -f unsigned_run_id=1234567890 \ -f signed_macos_asset=signed-macos-rust-vX.Y.Z.tar.zst \ -f signed_macos_sha256=<sha256> ``` The `signed_macos_sha256` input is optional, but when provided the promotion run verifies the handoff archive before unpacking it. The promotion run also validates that `unsigned_run_id` points to a successful manual `rust-release` run for the same tag and commit before importing artifacts. ## What Changed - Add explicit manual `release_mode` values for `build_unsigned` and `promote_signed` while keeping `sign_macos` as a deprecated compatibility input. - Add promote inputs for `unsigned_run_id`, `signed_macos_asset`, and optional `signed_macos_sha256`. - Add a `stage-signed-macos` job that downloads the signed handoff asset from the GitHub Release, verifies signed binaries and stapled DMGs, repacks normal macOS release artifacts, and builds macOS Python runtime wheels. - Teach the release job to download Part 1 artifacts from the unsigned run, discard unsigned macOS staging artifacts, re-upload promoted Linux and Windows artifacts for npm staging, and then run the signed release tail. - Validate that `unsigned_run_id` points to a successful manual `rust-release` run for the same tag and commit before importing artifacts. - Limit unsigned macOS artifact upload to the unsigned build path so normal signed releases do not publish unsigned handoff binaries. - Clean up unsigned and signed handoff release assets after successful promotion. ## Verification - Parsed `.github/workflows/rust-release.yml` with Ruby YAML loading. No developers.openai.com documentation update is needed.
1504 lines
59 KiB
YAML
1504 lines
59 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 and signed DMGs.
|
|
|
|
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: ubuntu-24.04
|
|
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: ubuntu-24.04
|
|
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: ubuntu-24.04-arm
|
|
target: aarch64-unknown-linux-musl
|
|
bundle: primary
|
|
artifact_name: aarch64-unknown-linux-musl
|
|
binaries: "codex codex-responses-api-proxy bwrap"
|
|
build_dmg: "false"
|
|
- runner: ubuntu-24.04-arm
|
|
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') && matrix.bundle == 'primary' }}
|
|
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 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="musllinux_1_1_aarch64"
|
|
;;
|
|
x86_64-unknown-linux-musl)
|
|
platform_tag="musllinux_1_1_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: "true"
|
|
- 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: "true"
|
|
- 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
|
|
|
|
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: 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 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
|
|
|
|
# stage_npm_packages.py requires DotSlash when staging releases.
|
|
- uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
|
- 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 == 'false' }}
|
|
uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
tag: ${{ github.ref_name }}
|
|
config: .github/dotslash-unsigned-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.
|
|
if: ${{ 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: ${{ 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: ${{ 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: ${{ 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
|