mirror of
https://github.com/openai/codex.git
synced 2026-05-16 01:02:48 +00:00
## Why The `release_mode=promote_signed` path intentionally skips the build jobs after signed macOS artifacts are staged, then runs the `release` job from the signed handoff. In the `rust-v0.131.0-alpha.19` promotion run, `release` succeeded but the npm, PyPI, and `latest-alpha-cli` follow-up jobs were skipped because their custom job `if:` expressions let GitHub Actions apply the implicit `success()` status check before reading `needs.release.outputs.*`. The unsigned build handoff does not need DotSlash manifests. Publishing unsigned DotSlash manifests creates release assets that can conflict with the later signed promotion, especially shared outputs such as `bwrap`, `codex-command-runner`, and `codex-windows-sandbox-setup`. ## What Changed - Stop publishing DotSlash manifests when `SIGN_MACOS == 'false'`. - Delete `.github/dotslash-unsigned-config.json`. - Gate post-release jobs with the `!cancelled()` status function plus an explicit `needs.release.result == 'success'` check before consulting release outputs. - Keep the existing publish eligibility rules for npm, PyPI, WinGet, and `latest-alpha-cli`. ## Verification - `rg -n "dotslash-unsigned-config|SIGN_MACOS == 'false'.*dotslash|unsigned-config" .github/workflows/rust-release.yml .github || true` - `git diff --check -- .github/workflows/rust-release.yml .github/dotslash-unsigned-config.json`
1520 lines
59 KiB
YAML
1520 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 == '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
|