Termux rust-v0.131.0-alpha.9 #164
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |