Skip to content

Termux rust-v0.131.0-alpha.9 #164

Termux rust-v0.131.0-alpha.9

Termux rust-v0.131.0-alpha.9 #164

Workflow file for this run

# 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
# ```
name: rust-release
on:
pull_request:
branches:
- "release/**"
workflow_dispatch:
inputs:
source_ref:
description: "Branch, tag, or commit SHA to build (code only; keeps .github from dispatched ref)"
required: false
type: string
default: ""
build_target:
description: "Target to build"
required: true
type: choice
default: all
options:
- all
- aarch64-apple-darwin
- x86_64-apple-darwin
- x86_64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- aarch64-unknown-linux-gnu
- aarch64-linux-android
- x86_64-pc-windows-msvc
- aarch64-pc-windows-msvc
permissions:
actions: read
attestations: read
checks: read
contents: read
deployments: read
issues: read
discussions: read
packages: read
pages: read
pull-requests: read
repository-projects: read
statuses: read
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
jobs:
tag-check:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: 🧰 Actions Toolbox
# This is required for the GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: wallentx/gh-actions/composite/actions-toolbox@main
- uses: dtolnay/rust-toolchain@1.92
- name: Validate tag matches Cargo.toml version
shell: bash
run: |
set -euo pipefail
echo "::group::Tag validation"
# 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]+)?)?(-termux)?$ ]] \
|| { 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::"
select-build-matrix:
if: always() && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || needs.tag-check.result == 'success')
needs: tag-check
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.select.outputs.matrix }}
build_version: ${{ steps.select.outputs.build_version }}
steps:
- uses: actions/checkout@v6
- name: 🧰 Actions Toolbox
# This is required for the GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: wallentx/gh-actions/composite/actions-toolbox@main
- id: select
shell: bash
env:
REPO_OWNER: ${{ github.repository_owner }}
EVENT_NAME: ${{ github.event_name }}
BUILD_TARGET: ${{ inputs.build_target }}
run: |
set -euo pipefail
matrix='[
{"runner":"macos-15-xlarge","target":"aarch64-apple-darwin"},
{"runner":"macos-15-xlarge","target":"x86_64-apple-darwin"},
{"runner":"ubuntu-24.04","target":"x86_64-unknown-linux-musl"},
{"runner":"ubuntu-24.04","target":"x86_64-unknown-linux-gnu"},
{"runner":"ubuntu-24.04-arm","target":"aarch64-unknown-linux-musl"},
{"runner":"ubuntu-24.04-arm","target":"aarch64-unknown-linux-gnu"},
{"runner":"ubuntu-24.04","target":"aarch64-linux-android"},
{"runner":"windows-latest","target":"x86_64-pc-windows-msvc"},
{"runner":"windows-11-arm","target":"aarch64-pc-windows-msvc"}
]'
if [[ "${REPO_OWNER}" != "openai" ]]; then
matrix="$(jq -c '[.[] | select(.runner | startswith("macos") | not)]' <<< "${matrix}")"
fi
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
matrix="$(jq -c '[.[] | select(.target == "aarch64-linux-android")]' <<< "${matrix}")"
elif [[ "${EVENT_NAME}" == "workflow_dispatch" && -n "${BUILD_TARGET}" && "${BUILD_TARGET}" != "all" ]]; then
matrix="$(jq -c --arg build_target "${BUILD_TARGET}" '[.[] | select(.target == $build_target)]' <<< "${matrix}")"
fi
if [[ "$(jq 'length' <<< "${matrix}")" -eq 0 ]]; then
echo "No build targets selected after applying owner/dispatch filters." >&2
exit 1
fi
echo "matrix={\"include\":${matrix}}" >> "$GITHUB_OUTPUT"
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
metadata=".github/termux-release.json"
if [[ ! -f "${metadata}" ]]; then
echo "${metadata} is required for release train PR builds" >&2
exit 1
fi
upstream_tag="$(jq -r '.upstream_tag // empty' "${metadata}")"
if [[ -z "${upstream_tag}" || "${upstream_tag}" != rust-v* ]]; then
echo "Unable to determine upstream rust tag from ${metadata}" >&2
exit 1
fi
build_version="${upstream_tag#rust-v}"
echo "build_version=${build_version}" >> "$GITHUB_OUTPUT"
elif [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then
latest_release_tag="$(
GH_TOKEN="${{ github.token }}" gh api repos/openai/codex/releases/latest --jq '.tag_name'
)"
if [[ -z "${latest_release_tag}" || "${latest_release_tag}" != rust-v* ]]; then
echo "Unable to determine latest stable release tag from openai/codex" >&2
exit 1
fi
latest_release_version="${latest_release_tag#rust-v}"
build_version="${latest_release_version}-dev"
echo "build_version=${build_version}" >> "$GITHUB_OUTPUT"
fi
build:
if: >-
always() &&
(github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || needs.tag-check.result == 'success') &&
needs.select-build-matrix.result == 'success'
needs:
- tag-check
- select-build-matrix
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
permissions: write-all
timeout-minutes: 120
defaults:
run:
working-directory: codex-rs
env:
CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }}
CODEX_BUILD_VERSION: ${{ needs.select-build-matrix.outputs.build_version }}
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.select-build-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
- name: 🧰 Actions Toolbox
# This is required for the GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: wallentx/gh-actions/composite/actions-toolbox@main
with:
verbose: true
- name: Checkout selected source ref
if: ${{ github.event_name == 'workflow_dispatch' && inputs.source_ref != '' }}
uses: actions/checkout@v6
with:
ref: ${{ inputs.source_ref }}
path: __source__
fetch-depth: 1
- name: Overlay selected source ref (excluding .github)
if: ${{ github.event_name == 'workflow_dispatch' && inputs.source_ref != '' }}
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -euo pipefail
echo "Using source ref: ${{ inputs.source_ref }}"
# Keep workflow/actions from the dispatch ref while replacing all other files.
find . -mindepth 1 -maxdepth 1 \
! -name .git \
! -name .github \
! -name __source__ \
-exec rm -rf -- {} +
tar --exclude='.github' -C __source__ -cf - . | tar -xf -
rm -rf __source__
- name: Apply selected build version
if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -euo pipefail
if [[ -z "${CODEX_BUILD_VERSION}" ]]; then
echo "CODEX_BUILD_VERSION is empty for workflow_dispatch" >&2
exit 1
fi
echo "Using build version: ${CODEX_BUILD_VERSION}"
BUILD_VERSION="${CODEX_BUILD_VERSION}" perl -0777 -i -pe \
's/(\[workspace\.package\][^\[]*?version = ")([^"]+)(")/${1}$ENV{BUILD_VERSION}$3/s' \
codex-rs/Cargo.toml
grep -n "^\[workspace.package\]\|^version = " codex-rs/Cargo.toml | head -n 4
- name: Select Android NDK
if: ${{ matrix.target == 'aarch64-linux-android' }}
shell: bash
run: |
set -euo pipefail
fallback_version="29.0.13113456"
ndk_root="${ANDROID_HOME}/ndk"
selected_ndk=""
if [[ -d "${ndk_root}" ]]; then
while IFS= read -r candidate; do
toolchain="${candidate}/toolchains/llvm/prebuilt/linux-x86_64"
if [[ -x "${toolchain}/bin/aarch64-linux-android24-clang" ]]; then
selected_ndk="${candidate}"
break
fi
done < <(find "${ndk_root}" -mindepth 1 -maxdepth 1 -type d | sort -Vr)
fi
if [[ -z "${selected_ndk}" ]]; then
echo "No usable preinstalled Android NDK found; installing ${fallback_version}"
yes | sudo "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses || true
sudo "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "ndk;${fallback_version}"
selected_ndk="${ndk_root}/${fallback_version}"
fi
if [[ ! -x "${selected_ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang" ]]; then
echo "Selected Android NDK does not contain a usable aarch64 API 24 clang: ${selected_ndk}" >&2
exit 1
fi
echo "Selected Android NDK: ${selected_ndk}"
"${selected_ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" --version
echo "ANDROID_NDK_HOME=${selected_ndk}" >> "$GITHUB_ENV"
echo "NDK_HOME=${selected_ndk}" >> "$GITHUB_ENV"
- name: Install Linux bwrap build dependencies
if: ${{ runner.os == 'Linux' && matrix.target != 'aarch64-linux-android' }}
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@1.93
with:
targets: ${{ matrix.target }}
- name: Set up sccache (Android)
if: ${{ matrix.target == 'aarch64-linux-android' }}
uses: mozilla-actions/sccache-action@main
- name: Enable sccache (Android)
if: ${{ matrix.target == 'aarch64-linux-android' }}
shell: bash
run: |
set -euo pipefail
echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
echo "SCCACHE_GHA_VERSION=android-release-${{ matrix.target }}" >> "$GITHUB_ENV"
echo "SCCACHE_CACHE_SIZE=5G" >> "$GITHUB_ENV"
- name: Ensure Rust target is installed
shell: bash
run: |
set -euo pipefail
rustup target add "${{ matrix.target }}"
- if: ${{ matrix.target == 'aarch64-linux-android' }}
name: Configure Android build environment
shell: bash
run: |
set -euo pipefail
ndk="${ANDROID_NDK_HOME}"
target="${{ matrix.target }}"
# Set up the Android toolchain
toolchain="${ndk}/toolchains/llvm/prebuilt/linux-x86_64"
# Use API level 24 to follow termux-build convention
api_level="24"
# Configure Cargo to use the NDK linker
cargo_target_var="CARGO_TARGET_${target^^}_LINKER"
cargo_target_var="${cargo_target_var//-/_}"
echo "${cargo_target_var}=${toolchain}/bin/aarch64-linux-android${api_level}-clang" >> "$GITHUB_ENV"
# Set CC and AR for the target
target_cc_var="CC_${target}"
target_cc_var="${target_cc_var//-/_}"
echo "${target_cc_var}=${toolchain}/bin/aarch64-linux-android${api_level}-clang" >> "$GITHUB_ENV"
target_cxx_var="CXX_${target}"
target_cxx_var="${target_cxx_var//-/_}"
echo "${target_cxx_var}=${toolchain}/bin/aarch64-linux-android${api_level}-clang++" >> "$GITHUB_ENV"
target_ar_var="AR_${target}"
target_ar_var="${target_ar_var//-/_}"
echo "${target_ar_var}=${toolchain}/bin/llvm-ar" >> "$GITHUB_ENV"
# Add toolchain to PATH
echo "${toolchain}/bin" >> "$GITHUB_PATH"
tls_align_source="${RUNNER_TEMP}/codex-android-tls-align.S"
tls_align_object="${RUNNER_TEMP}/codex-android-tls-align.o"
cat > "${tls_align_source}" <<'EOF'
.section .tdata.codex_android_tls_align,"awT",%progbits
.p2align 6
.globl __codex_android_tls_align
.hidden __codex_android_tls_align
.type __codex_android_tls_align, %object
__codex_android_tls_align:
.byte 0
.size __codex_android_tls_align, 1
EOF
"${toolchain}/bin/aarch64-linux-android${api_level}-clang" -c "${tls_align_source}" -o "${tls_align_object}"
v8_compat_source="${RUNNER_TEMP}/codex-android-v8-compat.c"
v8_compat_object="${RUNNER_TEMP}/codex-android-v8-compat.o"
cat > "${v8_compat_source}" <<'EOF'
#include <locale.h>
#include <stddef.h>
#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size) {
void *ptr = NULL;
if (posix_memalign(&ptr, alignment, size) != 0) {
return NULL;
}
return ptr;
}
double strtod_l(const char *nptr, char **endptr, locale_t locale) {
(void)locale;
return strtod(nptr, endptr);
}
float strtof_l(const char *nptr, char **endptr, locale_t locale) {
(void)locale;
return strtof(nptr, endptr);
}
EOF
"${toolchain}/bin/aarch64-linux-android${api_level}-clang" -c "${v8_compat_source}" -o "${v8_compat_object}"
builtins_archive="$(
find "${toolchain}/lib/clang" \
-path "*/lib/linux/libclang_rt.builtins-aarch64-android.a" \
-print -quit
)"
if [[ -z "${builtins_archive}" ]]; then
echo "Could not find Android compiler-rt builtins archive under ${toolchain}/lib/clang" >&2
exit 1
fi
builtins_dir="$(dirname "${builtins_archive}")"
# Provide compatibility symbols and C++ ABI needed by the Android rusty_v8 archive.
rustflags_var="CARGO_TARGET_${target^^}_RUSTFLAGS"
rustflags_var="${rustflags_var//-/_}"
echo "${rustflags_var}=-C link-arg=${tls_align_object} -C link-arg=${v8_compat_object} -C link-arg=-Wl,-u,__codex_android_tls_align -C link-arg=-L${builtins_dir} -C link-arg=-lclang_rt.builtins-aarch64-android -C link-arg=-lc++abi -C link-arg=-Wl,-z,max-page-size=65536" >> "$GITHUB_ENV"
# Configure for vendored OpenSSL build
echo "CARGO_BUILD_TARGET_APPLIES_TO_HOST=false" >> "$GITHUB_ENV"
echo "CARGO_BUILD_TARGET=${{ matrix.target }}" >> "$GITHUB_ENV"
- if: ${{ matrix.target == 'aarch64-linux-android' }}
name: Configure Android rusty_v8 artifact overrides
env:
GH_TOKEN: ${{ github.token }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
binding_dir="${RUNNER_TEMP}/rusty_v8"
archive="${binding_dir}/librusty_v8_release_${TARGET}.a.gz"
binding_path="${binding_dir}/src_binding_release_${TARGET}.rs"
checksums_path="${binding_dir}/rusty_v8_release_${TARGET}.sha256"
artifact_repository="wallentx/codex-termux"
release_tag="rusty-v8-v147.4.0"
mkdir -p "${binding_dir}"
echo "Downloading Android rusty_v8 artifacts from ${artifact_repository}@${release_tag}"
gh release download "${release_tag}" \
--repo "${artifact_repository}" \
--dir "${binding_dir}" \
--pattern "librusty_v8_release_${TARGET}.a.gz" \
--pattern "src_binding_release_${TARGET}.rs" \
--pattern "rusty_v8_release_${TARGET}.sha256"
(cd "${binding_dir}" && sha256sum -c "$(basename "${checksums_path}")")
echo "RUSTY_V8_ARCHIVE=${archive}" >> "$GITHUB_ENV"
echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "$GITHUB_ENV"
- if: ${{ matrix.target == 'aarch64-linux-android' }}
name: Install termux-elf-cleaner
shell: bash
run: |
set -euo pipefail
version="v3.0.1"
src_dir="${RUNNER_TEMP}/termux-elf-cleaner-src"
build_dir="${RUNNER_TEMP}/termux-elf-cleaner-build"
bin_dir="${RUNNER_TEMP}/termux-elf-cleaner-bin"
rm -rf "${src_dir}" "${build_dir}" "${bin_dir}"
mkdir -p "${src_dir}" "${build_dir}" "${bin_dir}"
curl -fsSL "https://github.com/termux/termux-elf-cleaner/archive/refs/tags/${version}.tar.gz" \
| tar -xzf - --strip-components=1 -C "${src_dir}"
cmake -S "${src_dir}" -B "${build_dir}" -DCMAKE_BUILD_TYPE=Release
cmake --build "${build_dir}" --parallel
install "${build_dir}/termux-elf-cleaner" "${bin_dir}/termux-elf-cleaner"
echo "${bin_dir}" >> "$GITHUB_PATH"
- 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"
- uses: actions/cache@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
${{ github.workspace }}/.cargo-home/bin/
${{ github.workspace }}/.cargo-home/registry/index/
${{ github.workspace }}/.cargo-home/registry/cache/
${{ github.workspace }}/.cargo-home/git/db/
${{ github.workspace }}/codex-rs/target/
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-release-${{ hashFiles('**/Cargo.lock') }}
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: 0.14.0
- 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
# 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"
- name: Cargo build
shell: bash
run: |
set -euo pipefail
if [[ "${{ matrix.target }}" == 'aarch64-linux-android' ]]; then
export CARGO_BUILD_JOBS=4
export CARGO_PROFILE_RELEASE_LTO=thin
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8
cargo build --target ${{ matrix.target }} --release --bin codex
exit 0
fi
if [[ "${{ contains(matrix.target, 'windows') }}" == 'true' ]]; then
cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy --bin codex-windows-sandbox-setup --bin codex-command-runner
else
cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy
fi
- name: sccache stats (Android)
if: ${{ matrix.target == 'aarch64-linux-android' }}
shell: bash
run: ${SCCACHE_PATH:-sccache} --show-stats || true
- if: ${{ matrix.target == 'aarch64-linux-android' }}
name: Normalize Android ELF for Termux
shell: bash
run: |
set -euo pipefail
for binary in codex codex-responses-api-proxy; do
binary_path="target/${{ matrix.target }}/release/${binary}"
if [[ -f "${binary_path}" ]]; then
termux-elf-cleaner --api-level 24 "${binary_path}"
chmod +x "${binary_path}"
# if [[ "${binary}" == "codex" ]]; then
# # Patch PT_TLS (0x7) to PT_NULL (0x0) at offset 400 to bypass Bionic alignment checks.
# printf '\x00\x00\x00\x00' | dd of="${binary_path}" bs=1 seek=400 count=4 conv=notrunc
# fi
fi
done
- if: ${{ contains(matrix.target, 'linux') && !contains(matrix.target, 'android') && github.repository_owner == 'openai' }}
name: Cosign Linux artifacts
uses: ./.github/actions/linux-code-sign
with:
target: ${{ matrix.target }}
artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release
- if: ${{ contains(matrix.target, 'windows') && github.repository_owner == 'openai' }}
name: Sign Windows binaries with Azure Trusted Signing
uses: ./.github/actions/windows-code-sign
with:
target: ${{ matrix.target }}
client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_TRUSTED_SIGNING_SUBSCRIPTION_ID }}
endpoint: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }}
- if: ${{ runner.os == 'macOS' && github.repository_owner == 'openai' }}
name: MacOS code signing (binaries)
uses: ./.github/actions/macos-code-sign
with:
target: ${{ matrix.target }}
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' && github.repository_owner == 'openai' }}
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.
codex_binary_path="${release_dir}/codex"
proxy_binary_path="${release_dir}/codex-responses-api-proxy"
rm -rf "$dmg_root"
mkdir -p "$dmg_root"
if [[ ! -f "$codex_binary_path" ]]; then
echo "Binary $codex_binary_path not found"
exit 1
fi
if [[ ! -f "$proxy_binary_path" ]]; then
echo "Binary $proxy_binary_path not found"
exit 1
fi
ditto "$codex_binary_path" "${dmg_root}/codex"
ditto "$proxy_binary_path" "${dmg_root}/codex-responses-api-proxy"
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' && github.repository_owner == 'openai' }}
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
shell: bash
run: |
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
if [[ "${{ matrix.runner }}" == windows* ]]; then
cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.exe "$dest/codex-responses-api-proxy-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe "$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe"
else
# For Android, we want the binary to be named just 'codex' in the archive.
if [[ "${{ matrix.target }}" == "aarch64-linux-android" ]]; then
cp target/${{ matrix.target }}/release/codex "$dest/codex"
else
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
fi
if [[ -f target/${{ matrix.target }}/release/codex-responses-api-proxy ]]; then
cp target/${{ matrix.target }}/release/codex-responses-api-proxy "$dest/codex-responses-api-proxy-${{ matrix.target }}"
fi
fi
if [[ "${{ matrix.target }}" == *linux* && "${{ matrix.target }}" != *android* ]]; then
cp target/${{ matrix.target }}/release/codex.sigstore "$dest/codex-${{ matrix.target }}.sigstore"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.sigstore "$dest/codex-responses-api-proxy-${{ matrix.target }}.sigstore"
fi
if [[ "${{ matrix.target }}" == *apple-darwin ]]; then
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
fi
- if: ${{ matrix.runner == 'windows-11-arm' }}
name: Install zstd
shell: powershell
run: choco install -y zstandard
- name: Compress artifacts
shell: bash
run: |
# Path that contains the uncompressed binaries for the current
# ${{ matrix.target }}
dest="dist/${{ matrix.target }}"
repo_root=$PWD
# We want to ship the raw Windows executables in the GitHub Release
# in addition to the compressed archives. Keep the originals for
# Windows targets; remove them elsewhere to limit the number of
# artifacts that end up in the GitHub Release.
keep_originals=false
if [[ "${{ matrix.runner }}" == windows* ]]; then
keep_originals=true
fi
# For compatibility with environments that lack the `zstd` tool we
# additionally create a `.tar.gz` for all platforms and `.zip` for
# Windows alongside every single binary that we publish. The end result is:
# codex-<target>.zst (existing)
# codex-<target>.tar.gz (new)
# codex-<target>.zip (only for Windows)
# 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" == *.zip || "$base" == *.dmg ]]; then
continue
fi
# Don't try to compress signature bundles.
if [[ "$base" == *.sigstore ]]; then
continue
fi
# Create per-binary tar.gz
# For Android, we want the archive to have the target suffix even though the binary is just 'codex'.
archive_name="${base}"
if [[ "${{ matrix.target }}" == "aarch64-linux-android" && "${base}" == "codex" ]]; then
archive_name="codex-${{ matrix.target }}"
fi
tar -C "$dest" -czf "$dest/${archive_name}.tar.gz" "$base"
# Create zip archive for Windows binaries
# Must run from inside the dest dir so 7z won't
# embed the directory path inside the zip.
if [[ "${{ matrix.runner }}" == windows* ]]; then
if [[ "$base" == "codex-${{ matrix.target }}.exe" ]]; then
# Bundle the sandbox helper binaries into the main codex zip so
# WinGet installs include the required helpers next to codex.exe.
# Fall back to the single-binary zip if the helpers are missing
# to avoid breaking releases.
bundle_dir="$(mktemp -d)"
runner_src="$dest/codex-command-runner-${{ matrix.target }}.exe"
setup_src="$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
if [[ -f "$runner_src" && -f "$setup_src" ]]; then
cp "$dest/$base" "$bundle_dir/$base"
cp "$runner_src" "$bundle_dir/codex-command-runner.exe"
cp "$setup_src" "$bundle_dir/codex-windows-sandbox-setup.exe"
# Use an absolute path so bundle zips land in the real dist
# dir even when 7z runs from a temp directory.
(cd "$bundle_dir" && 7z a "$repo_root/$dest/${base}.zip" .)
else
echo "warning: missing sandbox binaries; falling back to single-binary zip"
echo "warning: expected $runner_src and $setup_src"
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
rm -rf "$bundle_dir"
else
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
fi
# Also create .zst (existing behaviour) *and* remove the original
# uncompressed binary to keep the directory small.
zstd_args=(-T0 -19)
if [[ "${keep_originals}" == false ]]; then
zstd_args+=(--rm)
fi
if [[ "${archive_name}" != "${base}" ]]; then
zstd "${zstd_args[@]}" "$dest/$base" -o "$dest/${archive_name}.zst"
else
zstd "${zstd_args[@]}" "$dest/$base"
fi
done
- name: Add Termux release metadata
if: ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && matrix.target == 'aarch64-linux-android' }}
shell: bash
run: |
set -euo pipefail
if [[ ! -f "${GITHUB_WORKSPACE}/.github/termux-release.json" ]]; then
echo "No Termux release metadata found; skipping metadata attachment."
exit 0
fi
dest="dist/${{ matrix.target }}"
cp "${GITHUB_WORKSPACE}/.github/termux-release.json" "${dest}/termux-release.json"
(
cd "${dest}"
sha256sum ./* > SHA256SUMS
)
- id: upload-artifact
uses: actions/upload-artifact@v6
with:
name: ${{ github.event_name == 'pull_request' && format('termux-android-pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.sha) || matrix.target }}
# Upload the per-binary .zst files as well as the new .tar.gz
# equivalents we generated in the previous step.
path: |
codex-rs/dist/${{ matrix.target }}/*
- name: Comment Termux artifact download link
if: ${{ github.event_name == 'pull_request' && startsWith(github.base_ref, 'release/') && matrix.target == 'aarch64-linux-android' }}
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARTIFACT_ID: ${{ steps.upload-artifact.outputs.artifact-id }}
ARTIFACT_NAME: ${{ github.event_name == 'pull_request' && format('termux-android-pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.sha) || matrix.target }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
if [[ -z "${ARTIFACT_ID}" ]]; then
echo "upload-artifact did not return an artifact id" >&2
exit 1
fi
marker="<!-- termux-android-artifact -->"
artifact_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts/${ARTIFACT_ID}"
run_url="${GH_WORKFLOW_URL:-https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}}"
body_path="${RUNNER_TEMP}/termux-artifact-comment.md"
{
echo "${marker}"
echo "Termux Android artifact ready for testing:"
echo
echo "- Artifact: [${ARTIFACT_NAME}](${artifact_url})"
echo "- Workflow run: [${GITHUB_RUN_ID}](${run_url})"
echo
echo "You must be signed in to GitHub with repository access to download Actions artifacts."
} > "${body_path}"
existing_comment_id="$(
gh pr view "${PR_NUMBER}" \
--repo "${GITHUB_REPOSITORY}" \
--json comments \
--jq ".comments | map(select(.author.is_bot == true and (.body | contains(\"${marker}\")))) | .[-1].id // \"\""
)"
if [[ -n "${existing_comment_id}" ]]; then
gh api graphql \
-f query='
mutation($id: ID!, $body: String!) {
updateIssueComment(input: {id: $id, body: $body}) {
issueComment {
id
}
}
}
' \
-f id="${existing_comment_id}" \
-f body="$(cat "${body_path}")" \
>/dev/null
else
gh pr comment "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --body-file "${body_path}"
fi
gh label create binary-ready \
--repo "${GITHUB_REPOSITORY}" \
--color 0e8a16 \
--description "Android binary is ready for testing" \
--force
gh pr edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --add-label binary-ready
shell-tool-mcp:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && github.repository_owner == 'openai'
name: shell-tool-mcp
needs: tag-check
permissions:
contents: read
id-token: write
uses: ./.github/workflows/shell-tool-mcp.yml
with:
release-tag: ${{ github.ref_name }}
publish: true
secrets: inherit
release:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
needs:
- build
- shell-tool-mcp
name: release
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
outputs:
version: ${{ steps.release_name.outputs.name }}
tag: ${{ github.ref_name }}
should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }}
npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: 🧰 Actions Toolbox
# This is required for the GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: wallentx/gh-actions/composite/actions-toolbox@main
- 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@v7
with:
path: dist
- name: List
run: ls -R dist/
# This is a temporary fix: we should modify shell-tool-mcp.yml so these
# files do not end up in dist/ in the first place.
- name: Delete entries from dist/ that should not go in the release
run: |
rm -rf dist/shell-tool-mcp*
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 [[ "${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: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Setup Node.js for npm packaging
uses: actions/setup-node@v6
with:
node-version: 22
- name: Install dependencies
run: pnpm install --frozen-lockfile
# stage_npm_packages.py requires DotSlash when staging releases.
- uses: facebook/install-dotslash@v2
- name: Stage npm packages
if: github.repository_owner == 'openai'
env:
GH_TOKEN: ${{ github.token }}
run: |
./scripts/stage_npm_packages.py \
--release-version "${{ steps.release_name.outputs.name }}" \
--package codex \
--package codex-responses-api-proxy \
--package codex-sdk
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: ${{ steps.release_name.outputs.name }}
tag_name: ${{ github.ref_name }}
body_path: ${{ steps.release_notes.outputs.path }}
files: dist/**
# 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, '-') }}
- if: github.repository_owner == 'openai'
uses: facebook/dotslash-publish-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ github.ref_name }}
config: .github/dotslash-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: ${{ !contains(steps.release_name.outputs.name, '-') && github.repository_owner == 'openai' }}
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' && github.repository_owner == 'openai' }}
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@v6
with:
node-version: 22
registry-url: "https://registry.npmjs.org"
scope: "@openai"
# Trusted publishing requires npm CLI version 11.5.1 or later.
- name: Update npm
run: npm install -g npm@latest
- name: Download npm tarballs from release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
version="${{ needs.release.outputs.version }}"
tag="${{ needs.release.outputs.tag }}"
mkdir -p dist/npm
gh release download "$tag" \
--repo "${GITHUB_REPOSITORY}" \
--pattern "codex-npm-${version}.tgz" \
--dir dist/npm
gh release download "$tag" \
--repo "${GITHUB_REPOSITORY}" \
--pattern "codex-responses-api-proxy-npm-${version}.tgz" \
--dir dist/npm
gh release download "$tag" \
--repo "${GITHUB_REPOSITORY}" \
--pattern "codex-sdk-npm-${version}.tgz" \
--dir dist/npm
# 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
tag_args=()
if [[ -n "${NPM_TAG}" ]]; then
tag_args+=(--tag "${NPM_TAG}")
fi
tarballs=(
"codex-npm-${VERSION}.tgz"
"codex-responses-api-proxy-npm-${VERSION}.tgz"
"codex-sdk-npm-${VERSION}.tgz"
)
for tarball in "${tarballs[@]}"; do
npm publish "${GITHUB_WORKSPACE}/dist/npm/${tarball}" "${tag_args[@]}"
done
update-branch:
name: Update latest-alpha-cli branch
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