| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| name: rust-release |
| on: |
| push: |
| tags: |
| - "rust-v*.*.*" |
|
|
| env: |
| CODEX_REPO_ROOT: ${{ github.workspace }} |
| CODEX_ZSH_RELEASE_TAG: codex-zsh-v0.1.0 |
| CODEX_ZSH_MANIFEST_SHA256: c534eab89dcea7e3d8a5e5b3f49c025c7c64cd4e4d9814ee24871de58a9359a1 |
|
|
| concurrency: |
| group: ${{ github.workflow }} |
| cancel-in-progress: false |
|
|
| jobs: |
| tag-check: |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
| - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 |
| - name: Validate tag matches Cargo.toml version |
| shell: bash |
| run: | |
| set -euo pipefail |
| echo "::group::Tag validation" |
| |
| |
| [[ "${GITHUB_REF_TYPE}" == "tag" ]] \ |
| || { echo "❌ Not a tag ref"; exit 1; } |
|
|
| |
| |
| |
| [[ "${GITHUB_REF_NAME}" =~ ^rust-v[0-9]+\.[0-9]+\.[0-9]+(-(alpha(\.[0-9]+){0,2}|beta(\.[0-9]+)?))?$ ]] \ |
| || { echo "❌ Tag '${GITHUB_REF_NAME}' doesn't match expected format"; exit 1; } |
|
|
| tag_ver="${GITHUB_REF_NAME#rust-v}" |
| cargo_ver="$(grep -m1 '^version' codex-rs/Cargo.toml \ |
| | sed -E 's/version *= *"([^"]+)".*/\1/')" |
|
|
| [[ "${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: |
| needs: tag-check |
| name: Build - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }} |
| runs-on: ${{ matrix.runs_on || matrix.runner }} |
| |
| timeout-minutes: 120 |
| permissions: |
| contents: read |
| id-token: write |
| defaults: |
| run: |
| working-directory: codex-rs |
| env: |
| |
| CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO: ${{ contains(matrix.target, 'apple-darwin') && 'packed' || 'off' }} |
| |
| |
| |
| |
| CARGO_NET_GIT_FETCH_WITH_CLI: "true" |
|
|
| strategy: |
| fail-fast: false |
| matrix: |
| include: |
| - runner: macos-15-xlarge |
| target: aarch64-apple-darwin |
| bundle: primary |
| artifact_name: aarch64-apple-darwin |
| binaries: "codex codex-code-mode-host 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 codex-code-mode-host" |
| build_dmg: "false" |
| - runner: macos-15-xlarge |
| target: x86_64-apple-darwin |
| bundle: primary |
| artifact_name: x86_64-apple-darwin |
| binaries: "codex codex-code-mode-host 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 codex-code-mode-host" |
| build_dmg: "false" |
| |
| - runner: ${{ github.event.repository.name }}-linux-x64-xl |
| target: x86_64-unknown-linux-musl |
| bundle: primary |
| artifact_name: x86_64-unknown-linux-musl |
| binaries: "codex codex-code-mode-host codex-responses-api-proxy bwrap" |
| build_dmg: "false" |
| - runner: ${{ github.event.repository.name }}-linux-x64-xl |
| target: x86_64-unknown-linux-musl |
| bundle: app-server |
| artifact_name: x86_64-unknown-linux-musl-app-server |
| binaries: "codex-app-server codex-code-mode-host" |
| build_dmg: "false" |
| - runner: ${{ github.event.repository.name }}-linux-arm64 |
| target: aarch64-unknown-linux-musl |
| bundle: primary |
| artifact_name: aarch64-unknown-linux-musl |
| binaries: "codex codex-code-mode-host codex-responses-api-proxy bwrap" |
| build_dmg: "false" |
| - runner: ${{ github.event.repository.name }}-linux-arm64 |
| target: aarch64-unknown-linux-musl |
| bundle: app-server |
| artifact_name: aarch64-unknown-linux-musl-app-server |
| binaries: "codex-app-server codex-code-mode-host" |
| build_dmg: "false" |
|
|
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| 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 binutils pkg-config libcap-dev |
| - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 |
| 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 |
| 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: Disable aws-lc jitter entropy (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" |
| |
| - name: Configure rusty_v8 artifact overrides and verify checksums |
| uses: ./.github/actions/setup-rusty-v8 |
| with: |
| target: ${{ matrix.target }} |
|
|
| - if: ${{ contains(matrix.target, 'linux') }} |
| name: Build bwrap and export digest |
| shell: bash |
| run: | |
| set -euo pipefail |
| target="${{ matrix.target }}" |
| cargo build --target "$target" --release --timings --bin bwrap |
| |
| bwrap_path="target/${target}/release/bwrap" |
| if [[ ! -f "$bwrap_path" ]]; then |
| echo "bwrap binary ${bwrap_path} not found" |
| exit 1 |
| fi |
|
|
| |
| |
| |
| strip --strip-debug --strip-unneeded "$bwrap_path" |
| 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: | |
| target="${{ matrix.target }}" |
| if [[ "$target" == "x86_64-pc-windows-msvc" ]]; then |
| export LIBSQLITE3_FLAGS=SQLITE_DISABLE_INTRINSIC |
| fi |
| build_args=() |
| for binary in ${{ matrix.binaries }}; do |
| # bwrap was built, finalized, and hashed before this build so |
| # Codex can embed the digest of the bytes that will be packaged. |
| if [[ "$binary" == "bwrap" ]]; then |
| continue |
| fi |
| build_args+=(--bin "$binary") |
| done |
| STABLE_GIT_COMMIT="$(git rev-parse HEAD)" |
| export STABLE_GIT_COMMIT |
| cargo build --target "$target" --release --timings "${build_args[@]}" |
| |
| - name: Upload Cargo timings |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: cargo-timings-rust-release-${{ matrix.target }}-${{ matrix.bundle }} |
| path: codex-rs/target/**/cargo-timings/cargo-timing.html |
| if-no-files-found: warn |
|
|
| - name: Build symbols archive and strip binaries |
| shell: bash |
| run: | |
| binaries=() |
| for binary in ${{ matrix.binaries }}; do |
| # bwrap is already stripped before hashing. Its symbols are not |
| # useful enough to justify a separate pre-Codex symbols pass. |
| if [[ "$binary" == "bwrap" ]]; then |
| continue |
| fi |
| binaries+=("$binary") |
| done |
| bash "${GITHUB_WORKSPACE}/.github/scripts/archive-release-symbols-and-strip-binaries.sh" \ |
| --target "${{ matrix.target }}" \ |
| --artifact-name "${{ matrix.artifact_name }}" \ |
| --release-dir "target/${{ matrix.target }}/release" \ |
| --archive-dir "symbols-dist/${{ matrix.artifact_name }}" \ |
| --binaries "${binaries[*]}" |
| |
| - name: Upload symbols archive |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }}-symbols |
| path: codex-rs/symbols-dist/${{ matrix.artifact_name }}/* |
| if-no-files-found: error |
|
|
| - name: Set up Bazel for Linux voice |
| if: ${{ matrix.bundle == 'primary' && contains(matrix.target, 'linux') }} |
| uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 |
| with: |
| bazelisk-version: 1.28.1 |
|
|
| - name: Build Linux voice runtime |
| if: ${{ matrix.bundle == 'primary' && contains(matrix.target, 'linux') }} |
| shell: bash |
| env: |
| APP_TARGET: ${{ matrix.target }} |
| run: | |
| set -euo pipefail |
| cd "$GITHUB_WORKSPACE" |
| voice_target="${APP_TARGET%-musl}-gnu" |
| case "$voice_target" in |
| aarch64-unknown-linux-gnu) prefix=linux_aarch64 ;; |
| x86_64-unknown-linux-gnu) prefix=linux_x86_64 ;; |
| *) exit 1 ;; |
| esac |
| bazel build -c opt //codex-rs/voice-host:codex-voice-host //third_party/voice:native_runtime |
| source="bazel-bin/third_party/voice/native_runtime_${prefix}" |
| output="${RUNNER_TEMP}/signed-voice/${APP_TARGET}" |
| mkdir -p "$output" |
| python3 third_party/voice/release_runtime.py stage \ |
| --target "$voice_target" --source "$source" --output "$output/runtime" |
| cp bazel-bin/codex-rs/voice-host/codex-voice-host "$output/codex-voice-host" |
| chmod 0755 "$output/codex-voice-host" |
| python3 third_party/voice/release_runtime.py seal \ |
| --target "$voice_target" --output "$output/runtime" |
| |
| - if: ${{ runner.os == 'macOS' }} |
| 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' }} |
| name: Upload unsigned macOS artifacts |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| 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 }} |
|
|
| - name: Stage artifacts |
| if: ${{ runner.os != 'macOS' }} |
| shell: bash |
| run: | |
| dest="dist/${{ matrix.target }}" |
| mkdir -p "$dest" |
| |
| for binary in ${{ matrix.binaries }}; do |
| |
| |
| if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then |
| continue |
| fi |
| 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.build_dmg }}" == "true" ]]; then |
| cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg" |
| fi |
|
|
| - name: Download packaged zsh manifest |
| if: ${{ runner.os != 'macOS' }} |
| shell: bash |
| run: | |
| set -euo pipefail |
| curl -fsSL \ |
| "https://github.com/${GITHUB_REPOSITORY}/releases/download/${CODEX_ZSH_RELEASE_TAG}/codex-zsh" \ |
| -o "${RUNNER_TEMP}/codex-zsh" |
| bash "${GITHUB_WORKSPACE}/.github/scripts/verify-zsh-manifest.sh" \ |
| "${RUNNER_TEMP}/codex-zsh" "$CODEX_ZSH_MANIFEST_SHA256" |
| |
| - name: Build Codex package archive |
| if: ${{ runner.os != 'macOS' }} |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| BUNDLE: ${{ matrix.bundle }} |
| run: | |
| set -euo pipefail |
| voice_args=() |
| if [[ "$BUNDLE" == "primary" && "$TARGET" == *-unknown-linux-musl ]]; then |
| voice_args+=(--voice-release-dir "${RUNNER_TEMP}/signed-voice/${TARGET}") |
| voice_args+=(--release-version "${GITHUB_REF_NAME#rust-v}") |
| fi |
| bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \ |
| --target "$TARGET" \ |
| --bundle "$BUNDLE" \ |
| --entrypoint-dir "target/${TARGET}/release" \ |
| --archive-dir "dist/${TARGET}" \ |
| --zsh-manifest "${RUNNER_TEMP}/codex-zsh" \ |
| "${voice_args[@]}" |
| |
| - name: Cosign Linux voice package archives |
| if: ${{ matrix.bundle == 'primary' && contains(matrix.target, 'linux') }} |
| uses: ./.github/actions/linux-code-sign |
| with: |
| target: ${{ matrix.target }} |
| artifacts-dir: ${{ github.workspace }}/codex-rs/dist/${{ matrix.target }} |
| binaries: codex-package-${{ matrix.target }}.tar.gz codex-package-${{ matrix.target }}.tar.zst |
|
|
| - name: Build Python runtime wheel |
| if: ${{ matrix.bundle == 'primary' && runner.os != 'macOS' }} |
| shell: bash |
| run: | |
| set -euo pipefail |
| |
| case "${{ matrix.target }}" in |
| aarch64-apple-darwin) |
| platform_tag="macosx_11_0_arm64" |
| ;; |
| x86_64-apple-darwin) |
| platform_tag="macosx_10_9_x86_64" |
| ;; |
| aarch64-unknown-linux-musl) |
| platform_tag="manylinux_2_17_aarch64" |
| ;; |
| x86_64-unknown-linux-musl) |
| platform_tag="manylinux_2_17_x86_64" |
| ;; |
| *) |
| echo "No Python runtime wheel platform tag for ${{ matrix.target }}" |
| exit 1 |
| ;; |
| esac |
|
|
| python3 -m venv "${RUNNER_TEMP}/python-runtime-build-venv" |
| |
| |
| "${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m pip install build |
|
|
| |
| |
| |
| wheel_archives="${RUNNER_TEMP}/voice-free-wheel/${{ matrix.target }}" |
| bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \ |
| --target "${{ matrix.target }}" \ |
| --bundle primary \ |
| --entrypoint-dir "target/${{ matrix.target }}/release" \ |
| --archive-dir "$wheel_archives" \ |
| --zsh-manifest "${RUNNER_TEMP}/codex-zsh" |
| wheel_archive="${wheel_archives}/codex-package-${{ matrix.target }}.tar.gz" |
| python3 - "$wheel_archive" <<'PY' |
| import sys |
| import tarfile |
| with tarfile.open(sys.argv[1]) as archive: |
| assert not any("codex-resources/voice/" in item.name for item in archive) |
| PY |
|
|
| 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" |
| "$wheel_archive" |
| --codex-version "${GITHUB_REF_NAME}" |
| --platform-tag "$platform_tag" |
| ) |
| 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' }} |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| 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' }} |
| shell: bash |
| run: | |
| # Path that contains the uncompressed binaries for the current |
| # ${{ matrix.target }} |
| 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 |
|
|
| |
| if [[ "$base" == *.sigstore ]]; then |
| continue |
| fi |
|
|
| |
| tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base" |
|
|
| |
| |
| zstd -T0 -19 --rm "$dest/$base" |
| done |
|
|
| - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| if: ${{ runner.os != 'macOS' }} |
| with: |
| name: ${{ matrix.artifact_name }} |
| |
| |
| path: | |
| codex-rs/dist/${{ matrix.target }}/* |
| |
| build-macos-voice: |
| needs: tag-check |
| name: Build voice runtime - ${{ matrix.target }} |
| runs-on: ${{ matrix.runner }} |
| timeout-minutes: 120 |
| permissions: |
| contents: read |
| strategy: |
| fail-fast: false |
| matrix: |
| include: |
| - target: aarch64-apple-darwin |
| runner: macos-15 |
| prefix: macos_aarch64 |
| - target: x86_64-apple-darwin |
| runner: macos-15-intel |
| prefix: macos_x86_64 |
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 |
| with: |
| python-version: "3.12" |
| - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 |
| with: |
| bazelisk-version: 1.28.1 |
| - name: Build matching helper and native runtime |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| PREFIX: ${{ matrix.prefix }} |
| run: | |
| set -euo pipefail |
| # Release tags bump Cargo.toml without rewriting the workspace lockfile. |
| # Refresh workspace versions before Bazel reads the Cargo dependency graph. |
| (cd codex-rs && cargo update --workspace) |
| bazel build -c opt //codex-rs/voice-host:codex-voice-host //third_party/voice:native_runtime |
| runtime="bazel-bin/third_party/voice/native_runtime_${PREFIX}" |
| PYTHONPATH=third_party/voice python3 - "$runtime" "$TARGET" <<'PY' |
| from pathlib import Path |
| import sys |
| from package_runtime import runtime_files |
| runtime_files(Path(sys.argv[1]).resolve(strict=True), sys.argv[2]) |
| PY |
| mkdir -p "voice-unsigned/${TARGET}" |
| cp -R "$runtime" "voice-unsigned/${TARGET}/runtime" |
| # GNU tar extraction and signing need writable copies of Bazel outputs. |
| chmod -R u+w "voice-unsigned/${TARGET}/runtime" |
| cp bazel-bin/codex-rs/voice-host/codex-voice-host "voice-unsigned/${TARGET}/codex-voice-host" |
| tar -C "voice-unsigned/${TARGET}" -czf "voice-unsigned-${TARGET}.tar.gz" runtime codex-voice-host |
| - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: voice-${{ matrix.target }}-unsigned |
| path: voice-unsigned-${{ matrix.target }}.tar.gz |
| if-no-files-found: error |
|
|
| sign-macos-binaries: |
| needs: [build, build-macos-voice] |
| if: ${{ always() && needs.build.result == 'success' && needs.build-macos-voice.result == 'success' }} |
| name: Sign macOS binaries - ${{ matrix.target }} - ${{ matrix.bundle }} |
| runs-on: ubuntu-latest |
| timeout-minutes: 45 |
| environment: |
| name: codesigning |
| deployment: false |
| permissions: |
| contents: read |
| id-token: write |
|
|
| strategy: |
| fail-fast: false |
| matrix: |
| include: |
| - target: aarch64-apple-darwin |
| bundle: primary |
| artifact_name: aarch64-apple-darwin |
| binaries: "codex codex-code-mode-host codex-responses-api-proxy" |
| - target: aarch64-apple-darwin |
| bundle: app-server |
| artifact_name: aarch64-apple-darwin-app-server |
| binaries: "codex-app-server codex-code-mode-host" |
| - target: x86_64-apple-darwin |
| bundle: primary |
| artifact_name: x86_64-apple-darwin |
| binaries: "codex codex-code-mode-host codex-responses-api-proxy" |
| - target: x86_64-apple-darwin |
| bundle: app-server |
| artifact_name: x86_64-apple-darwin-app-server |
| binaries: "codex-app-server codex-code-mode-host" |
|
|
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
|
|
| - name: Download unsigned macOS binaries |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: ${{ matrix.artifact_name }}-unsigned |
| path: ${{ runner.temp }}/unsigned-macos |
|
|
| - name: Download unsigned voice runtime |
| if: ${{ matrix.bundle == 'primary' }} |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: voice-${{ matrix.target }}-unsigned |
| path: ${{ runner.temp }}/unsigned-voice |
|
|
| - name: Set up AKV PKCS11 macOS signing |
| uses: ./.github/actions/setup-akv-pkcs11-codesigning |
| with: |
| rcodesign-blob-uri: ${{ secrets.AKV_CODESIGN_RCODESIGN_BLOB_URI }} |
| rcodesign-sha256: ${{ secrets.AKV_CODESIGN_RCODESIGN_SHA256 }} |
| akv-pkcs11-library-blob-uri: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_BLOB_URI }} |
| akv-pkcs11-library-sha256: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_SHA256 }} |
| azure-client-id: ${{ secrets.AKV_CODESIGN_AZURE_CLIENT_ID }} |
| azure-tenant-id: ${{ secrets.AKV_CODESIGN_TENANT }} |
| azure-subscription-id: ${{ secrets.AKV_CODESIGN_SUBSCRIPTION }} |
| key-vault-name: ${{ secrets.AKV_CODESIGN_KEY_VAULT_NAME }} |
| key-name: ${{ secrets.AKV_CODESIGN_KEY_NAME }} |
| key-version: ${{ secrets.AKV_CODESIGN_KEY_VERSION || '' }} |
| certificate-sha256: ${{ secrets.AKV_CODESIGN_CERTIFICATE_SHA256 || '' }} |
|
|
| - name: Sign and notarize macOS binaries |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| BINARIES: ${{ matrix.binaries }} |
| APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }} |
| APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }} |
| run: | |
| set -euo pipefail |
| |
| input_dir="${RUNNER_TEMP}/unsigned-macos" |
| output_dir="${GITHUB_WORKSPACE}/signed-macos/${TARGET}" |
| report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}" |
| mkdir -p "$output_dir" "$report_dir" |
|
|
| for binary in ${BINARIES}; do |
| unsigned_path="${input_dir}/${binary}-${TARGET}-unsigned.zst" |
| signed_path="${output_dir}/${binary}" |
| if [[ ! -f "$unsigned_path" ]]; then |
| echo "Unsigned binary $unsigned_path not found" |
| exit 1 |
| fi |
|
|
| zstd -d --stdout "$unsigned_path" >"$signed_path" |
| chmod 0755 "$signed_path" |
|
|
| entitlements="${GITHUB_WORKSPACE}/.github/scripts/macos-signing/${binary}.entitlements.plist" |
| if [[ ! -f "$entitlements" ]]; then |
| echo "Entitlements file $entitlements not found" |
| exit 1 |
| fi |
|
|
| .github/scripts/macos-signing/sign_macos_code.sh \ |
| --target "$signed_path" \ |
| --identity unused \ |
| --deep false \ |
| --identifier "$binary" \ |
| --options runtime \ |
| --timestamp true \ |
| --entitlements "$entitlements" |
|
|
| mkdir -p "${report_dir}/${binary}" |
| rcodesign print-signature-info "$signed_path" \ |
| >"${report_dir}/${binary}/signature-info.yaml" |
|
|
| .github/scripts/macos-signing/notarize_macos_binary_with_akv.sh \ |
| --binary "$signed_path" \ |
| --report-dir "${report_dir}/${binary}" |
| done |
|
|
| - name: Fetch, sign, and notarize pinned macOS helpers |
| if: ${{ matrix.bundle == 'primary' }} |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }} |
| APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }} |
| run: | |
| set -euo pipefail |
| |
| signed_root="${GITHUB_WORKSPACE}/signed-resources/${TARGET}" |
| zsh_manifest="${RUNNER_TEMP}/codex-zsh-${TARGET}" |
| mkdir -p "$signed_root" |
| curl -fsSL \ |
| "https://github.com/${GITHUB_REPOSITORY}/releases/download/${CODEX_ZSH_RELEASE_TAG}/codex-zsh" \ |
| -o "$zsh_manifest" |
| bash "${GITHUB_WORKSPACE}/.github/scripts/verify-zsh-manifest.sh" \ |
| "$zsh_manifest" "$CODEX_ZSH_MANIFEST_SHA256" |
|
|
| PYTHONPATH="${GITHUB_WORKSPACE}/scripts" python3 - "$TARGET" "$signed_root" "$zsh_manifest" <<'PY' |
| import shutil |
| import sys |
| from pathlib import Path |
|
|
| from codex_package.ripgrep import fetch_rg |
| from codex_package.targets import TARGET_SPECS |
| from codex_package.zsh import resolve_zsh_bin |
|
|
| spec = TARGET_SPECS[sys.argv[1]] |
| signed_root = Path(sys.argv[2]) |
| zsh_bin = resolve_zsh_bin(spec, Path(sys.argv[3])) |
| if zsh_bin is None: |
| raise RuntimeError(f"Pinned zsh release is missing {spec.target}") |
| shutil.copy2(fetch_rg(spec), signed_root / "rg") |
| shutil.copy2(zsh_bin, signed_root / "zsh") |
| PY |
|
|
| for resource in rg zsh; do |
| binary="${signed_root}/${resource}" |
| report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}/${resource}" |
| mkdir -p "$report_dir" |
| chmod 0755 "$binary" |
| .github/scripts/macos-signing/sign_macos_code.sh \ |
| --target "$binary" \ |
| --identity unused \ |
| --deep false \ |
| --identifier "com.openai.codex.${resource}" \ |
| --options runtime \ |
| --timestamp true |
|
|
| rcodesign print-signature-info "$binary" \ |
| >"${report_dir}/signature-info.yaml" |
|
|
| .github/scripts/macos-signing/notarize_macos_binary_with_akv.sh \ |
| --binary "$binary" \ |
| --report-dir "$report_dir" |
| done |
|
|
| - name: Sign and notarize voice runtime |
| if: ${{ matrix.bundle == 'primary' }} |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }} |
| APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }} |
| run: | |
| set -euo pipefail |
| unsigned="${RUNNER_TEMP}/unsigned-voice/extracted" |
| signed="${GITHUB_WORKSPACE}/signed-voice/${TARGET}" |
| mkdir -p "$unsigned" "$signed" |
| tar -xzf "${RUNNER_TEMP}/unsigned-voice/voice-unsigned-${TARGET}.tar.gz" -C "$unsigned" |
| python3 third_party/voice/release_runtime.py stage \ |
| --target "$TARGET" --source "$unsigned/runtime" --output "$signed/runtime" |
| cp "$unsigned/codex-voice-host" "$signed/codex-voice-host" |
| chmod 0755 "$signed/codex-voice-host" |
| |
| while IFS= read -r -d '' library; do |
| name="$(basename "$library")" |
| .github/scripts/macos-signing/sign_macos_code.sh \ |
| --target "$library" --identity unused --deep false \ |
| --identifier "com.openai.codex.voice.${name}" \ |
| --options runtime --timestamp true |
| done < <(find "$signed/runtime" -name '*.dylib' -type f -print0) |
| .github/scripts/macos-signing/sign_macos_code.sh \ |
| --target "$signed/codex-voice-host" --identity unused --deep false \ |
| --identifier com.openai.codex.voice-host --options runtime --timestamp true \ |
| --entitlements .github/scripts/macos-signing/codex-voice-host.entitlements.plist |
|
|
| |
| (cd "$signed" && zip -qr "${RUNNER_TEMP}/voice-${TARGET}.zip" runtime codex-voice-host) |
| report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}/voice" |
| mkdir -p "$report_dir" |
| python3 .github/scripts/macos-signing/notarize_with_akv.py \ |
| --file "${RUNNER_TEMP}/voice-${TARGET}.zip" \ |
| --report-log "${report_dir}/notarization.json" \ |
| --max-wait-seconds 600 |
| python3 third_party/voice/release_runtime.py seal \ |
| --target "$TARGET" --output "$signed/runtime" |
| tar -C "$signed" -czf "${GITHUB_WORKSPACE}/signed-voice-${TARGET}.tar.gz" \ |
| runtime codex-voice-host |
|
|
| - name: Upload signed voice runtime |
| if: ${{ matrix.bundle == 'primary' }} |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: voice-${{ matrix.target }}-signed |
| path: signed-voice-${{ matrix.target }}.tar.gz |
| if-no-files-found: error |
|
|
| - name: Upload signed macOS binaries |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }}-signed-binaries |
| path: signed-macos/${{ matrix.target }}/* |
| if-no-files-found: error |
|
|
| - name: Upload signed macOS helpers |
| if: ${{ matrix.bundle == 'primary' }} |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.target }}-signed-resources |
| path: signed-resources/${{ matrix.target }}/* |
| if-no-files-found: error |
|
|
| - name: Upload binary signing verification |
| if: ${{ always() }} |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }}-binary-signing-verification |
| path: macos-binary-signing-verification/${{ matrix.target }}/ |
| if-no-files-found: warn |
|
|
| package-macos: |
| needs: sign-macos-binaries |
| name: Package macOS artifacts - ${{ matrix.target }} - ${{ matrix.bundle }} |
| runs-on: macos-15-xlarge |
| timeout-minutes: 45 |
| 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-code-mode-host 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 codex-code-mode-host" |
| build_dmg: "false" |
| - target: x86_64-apple-darwin |
| bundle: primary |
| artifact_name: x86_64-apple-darwin |
| binaries: "codex codex-code-mode-host 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 codex-code-mode-host" |
| build_dmg: "false" |
|
|
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
|
|
| - name: Download signed macOS binaries |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: ${{ matrix.artifact_name }}-signed-binaries |
| path: codex-rs/target/${{ matrix.target }}/release |
|
|
| - name: Download signed macOS helpers |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: ${{ matrix.target }}-signed-resources |
| path: codex-rs/signed-resources/${{ matrix.target }} |
|
|
| - name: Download signed voice runtime |
| if: ${{ matrix.bundle == 'primary' }} |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: voice-${{ matrix.target }}-signed |
| path: ${{ runner.temp }}/signed-voice |
|
|
| - name: Verify signed voice runtime |
| if: ${{ matrix.bundle == 'primary' }} |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| run: | |
| set -euo pipefail |
| root="${RUNNER_TEMP}/signed-voice/${TARGET}" |
| mkdir -p "$root" |
| tar -xzf "${RUNNER_TEMP}/signed-voice/signed-voice-${TARGET}.tar.gz" -C "$root" |
| case "$TARGET" in |
| aarch64-apple-darwin) arch=arm64 ;; |
| x86_64-apple-darwin) arch=x86_64 ;; |
| *) exit 1 ;; |
| esac |
| PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$root/runtime" "$TARGET" <<'PY' |
| from pathlib import Path |
| import sys |
| from package_runtime import runtime_files |
| runtime_files(Path(sys.argv[1]).resolve(strict=True), sys.argv[2], public_release=True) |
| PY |
| while IFS= read -r -d '' binary; do |
| lipo "$binary" -verify_arch "$arch" |
| codesign --verify --strict --verbose=2 "$binary" |
| done < <(find "$root/runtime" -name '*.dylib' -type f -print0) |
| lipo "$root/codex-voice-host" -verify_arch "$arch" |
| codesign --verify --strict --verbose=2 "$root/codex-voice-host" |
| |
| - name: Verify signed macOS binaries |
| shell: bash |
| run: | |
| set -euo pipefail |
| for binary in ${{ matrix.binaries }}; do |
| binary_path="target/${{ matrix.target }}/release/${binary}" |
| chmod 0755 "$binary_path" |
| codesign --verify --strict --verbose=2 "$binary_path" |
| done |
| |
| for resource in rg zsh; do |
| resource_path="signed-resources/${{ matrix.target }}/${resource}" |
| chmod 0755 "$resource_path" |
| codesign --verify --strict --verbose=2 "$resource_path" |
| done |
|
|
| - name: Stage macOS artifacts |
| shell: bash |
| run: | |
| set -euo pipefail |
| dest="dist/${{ matrix.target }}" |
| mkdir -p "$dest" |
| |
| for binary in ${{ matrix.binaries }}; do |
| |
| |
| if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then |
| continue |
| fi |
| cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}" |
| done |
|
|
| - name: Build Codex package archive |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| BUNDLE: ${{ matrix.bundle }} |
| run: | |
| set -euo pipefail |
| voice_args=() |
| if [[ "$BUNDLE" == "primary" ]]; then |
| voice_args+=(--voice-release-dir "${RUNNER_TEMP}/signed-voice/${TARGET}") |
| voice_args+=(--release-version "${GITHUB_REF_NAME#rust-v}") |
| fi |
| bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \ |
| --target "$TARGET" \ |
| --bundle "$BUNDLE" \ |
| --entrypoint-dir "target/${TARGET}/release" \ |
| --archive-dir "dist/${TARGET}" \ |
| --rg-bin "signed-resources/${TARGET}/rg" \ |
| --zsh-bin "signed-resources/${TARGET}/zsh" \ |
| ${voice_args[@]+"${voice_args[@]}"} |
| |
| - name: Build unsigned macOS DMG |
| if: ${{ matrix.build_dmg == 'true' }} |
| shell: bash |
| run: | |
| set -euo pipefail |
| |
| target="${{ matrix.target }}" |
| release_dir="target/${target}/release" |
| dmg_root="${RUNNER_TEMP}/codex-dmg-root-${target}" |
| volname="Codex (${target})" |
| dmg_path="${release_dir}/codex-${target}.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 |
|
|
| if [[ "${{ matrix.bundle }}" == "primary" ]]; then |
| |
| |
| package="${RUNNER_TEMP}/codex-package-voice-${target}" |
| ditto "$package" "$dmg_root" |
| rm "${dmg_root}/codex" |
| ln -s bin/codex "${dmg_root}/codex" |
| fi |
|
|
| 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 |
|
|
| - name: Upload unsigned macOS DMG |
| if: ${{ matrix.build_dmg == 'true' }} |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }}-unsigned-dmg |
| path: codex-rs/target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg |
| if-no-files-found: error |
|
|
| - 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 |
|
|
| |
| |
| |
| wheel_archives="${RUNNER_TEMP}/voice-free-wheel/${{ matrix.target }}" |
| bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \ |
| --target "${{ matrix.target }}" \ |
| --bundle primary \ |
| --entrypoint-dir "target/${{ matrix.target }}/release" \ |
| --archive-dir "$wheel_archives" \ |
| --rg-bin "signed-resources/${{ matrix.target }}/rg" \ |
| --zsh-bin "signed-resources/${{ matrix.target }}/zsh" |
| wheel_archive="${wheel_archives}/codex-package-${{ matrix.target }}.tar.gz" |
| python3 - "$wheel_archive" <<'PY' |
| import sys |
| import tarfile |
| with tarfile.open(sys.argv[1]) as archive: |
| assert not any("codex-resources/voice/" in item.name for item in archive) |
| PY |
|
|
| 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" \ |
| "$wheel_archive" \ |
| --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 |
| 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 |
|
|
| - name: Upload packaged macOS artifacts |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }}-packaged |
| path: codex-rs/dist/${{ matrix.target }}/* |
| if-no-files-found: error |
|
|
| sign-macos-dmg: |
| needs: package-macos |
| name: Sign macOS DMG - ${{ matrix.target }} |
| runs-on: ubuntu-latest |
| timeout-minutes: 45 |
| environment: |
| name: codesigning |
| deployment: false |
| permissions: |
| contents: read |
| id-token: write |
|
|
| strategy: |
| fail-fast: false |
| matrix: |
| include: |
| - target: aarch64-apple-darwin |
| artifact_name: aarch64-apple-darwin |
| - target: x86_64-apple-darwin |
| artifact_name: x86_64-apple-darwin |
|
|
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
|
|
| - name: Download unsigned macOS DMG |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: ${{ matrix.artifact_name }}-unsigned-dmg |
| path: ${{ runner.temp }}/unsigned-dmg |
|
|
| - name: Set up AKV PKCS11 macOS signing |
| uses: ./.github/actions/setup-akv-pkcs11-codesigning |
| with: |
| rcodesign-blob-uri: ${{ secrets.AKV_CODESIGN_RCODESIGN_BLOB_URI }} |
| rcodesign-sha256: ${{ secrets.AKV_CODESIGN_RCODESIGN_SHA256 }} |
| akv-pkcs11-library-blob-uri: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_BLOB_URI }} |
| akv-pkcs11-library-sha256: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_SHA256 }} |
| azure-client-id: ${{ secrets.AKV_CODESIGN_AZURE_CLIENT_ID }} |
| azure-tenant-id: ${{ secrets.AKV_CODESIGN_TENANT }} |
| azure-subscription-id: ${{ secrets.AKV_CODESIGN_SUBSCRIPTION }} |
| key-vault-name: ${{ secrets.AKV_CODESIGN_KEY_VAULT_NAME }} |
| key-name: ${{ secrets.AKV_CODESIGN_KEY_NAME }} |
| key-version: ${{ secrets.AKV_CODESIGN_KEY_VERSION || '' }} |
| certificate-sha256: ${{ secrets.AKV_CODESIGN_CERTIFICATE_SHA256 || '' }} |
|
|
| - name: Sign, notarize, and staple macOS DMG |
| shell: bash |
| env: |
| TARGET: ${{ matrix.target }} |
| APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }} |
| APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }} |
| run: | |
| set -euo pipefail |
| |
| dmg_path="${RUNNER_TEMP}/unsigned-dmg/codex-${TARGET}.dmg" |
| report_dir="${GITHUB_WORKSPACE}/macos-dmg-signing-verification/${TARGET}" |
| if [[ ! -f "$dmg_path" ]]; then |
| echo "Unsigned DMG $dmg_path not found" |
| exit 1 |
| fi |
|
|
| .github/scripts/macos-signing/sign_macos_code.sh \ |
| --target "$dmg_path" \ |
| --identity unused \ |
| --deep false \ |
| --timestamp true |
|
|
| mkdir -p "$report_dir" |
| rcodesign print-signature-info "$dmg_path" \ |
| >"${report_dir}/signature-info-before-notarization.yaml" |
|
|
| .github/scripts/macos-signing/notarize_macos_dmg_with_akv.sh \ |
| --dmg "$dmg_path" \ |
| --report-dir "$report_dir" |
|
|
| rcodesign print-signature-info "$dmg_path" \ |
| >"${report_dir}/signature-info.yaml" |
|
|
| - name: Upload signed macOS DMG |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }}-signed-dmg |
| path: ${{ runner.temp }}/unsigned-dmg/codex-${{ matrix.target }}.dmg |
| if-no-files-found: error |
|
|
| - name: Upload DMG signing verification |
| if: ${{ always() }} |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }}-dmg-signing-verification |
| path: macos-dmg-signing-verification/${{ matrix.target }}/ |
| if-no-files-found: warn |
|
|
| finalize-macos: |
| needs: |
| - package-macos |
| - sign-macos-dmg |
| name: Verify macOS artifacts - ${{ 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-code-mode-host codex-responses-api-proxy" |
| verify_dmg: "true" |
| - target: aarch64-apple-darwin |
| bundle: app-server |
| artifact_name: aarch64-apple-darwin-app-server |
| binaries: "codex-app-server codex-code-mode-host" |
| verify_dmg: "false" |
| - target: x86_64-apple-darwin |
| bundle: primary |
| artifact_name: x86_64-apple-darwin |
| binaries: "codex codex-code-mode-host codex-responses-api-proxy" |
| verify_dmg: "true" |
| - target: x86_64-apple-darwin |
| bundle: app-server |
| artifact_name: x86_64-apple-darwin-app-server |
| binaries: "codex-app-server codex-code-mode-host" |
| verify_dmg: "false" |
|
|
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
|
|
| - name: Download packaged macOS artifacts |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: ${{ matrix.artifact_name }}-packaged |
| path: codex-rs/dist/${{ matrix.target }} |
|
|
| - name: Download signed macOS binaries |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: ${{ matrix.artifact_name }}-signed-binaries |
| path: ${{ runner.temp }}/signed-binaries |
|
|
| - name: Download signed macOS DMG |
| if: ${{ matrix.verify_dmg == 'true' }} |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: ${{ matrix.artifact_name }}-signed-dmg |
| path: ${{ runner.temp }}/signed-dmg |
|
|
| - name: Verify signed macOS artifacts |
| shell: bash |
| run: | |
| set -euo pipefail |
| |
| target="${{ matrix.target }}" |
| packaged_dir="dist/${target}" |
| case "$target" in |
| aarch64-apple-darwin) expected_arch="arm64" ;; |
| x86_64-apple-darwin) expected_arch="x86_64" ;; |
| *) |
| echo "Unexpected macOS target: $target" |
| exit 1 |
| ;; |
| esac |
|
|
| verify_signed_binary() { |
| local path="$1" |
| local binary="$2" |
| local actual_entitlements expected_entitlements normalized_actual normalized_expected |
|
|
| chmod 0755 "$path" |
| lipo "$path" -verify_arch "$expected_arch" |
| codesign --verify --strict --verbose=2 "$path" |
|
|
| expected_entitlements="${GITHUB_WORKSPACE}/.github/scripts/macos-signing/${binary}.entitlements.plist" |
| if [[ ! -f "$expected_entitlements" ]]; then |
| echo "Expected entitlements file $expected_entitlements not found" |
| exit 1 |
| fi |
| actual_entitlements="$(mktemp)" |
| normalized_actual="$(mktemp)" |
| normalized_expected="$(mktemp)" |
| codesign -d --entitlements :- "$path" >"$actual_entitlements" |
| plutil -convert xml1 -o "$normalized_actual" "$actual_entitlements" |
| plutil -convert xml1 -o "$normalized_expected" "$expected_entitlements" |
| diff -u "$normalized_expected" "$normalized_actual" |
| rm -f "$actual_entitlements" "$normalized_actual" "$normalized_expected" |
| } |
|
|
| for binary in ${{ matrix.binaries }}; do |
| binary_path="${RUNNER_TEMP}/signed-binaries/${binary}" |
| verify_signed_binary "$binary_path" "$binary" |
|
|
| |
| |
| if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then |
| continue |
| fi |
|
|
| direct_archive_dir="${RUNNER_TEMP}/direct-archive-${binary}-${target}" |
| rm -rf "$direct_archive_dir" |
| mkdir -p "$direct_archive_dir" |
| tar -xzf "${packaged_dir}/${binary}-${target}.tar.gz" -C "$direct_archive_dir" |
| verify_signed_binary "${direct_archive_dir}/${binary}-${target}" "$binary" |
|
|
| direct_zstd_path="${RUNNER_TEMP}/${binary}-${target}-from-zstd" |
| zstd -d --stdout "${packaged_dir}/${binary}-${target}.zst" >"$direct_zstd_path" |
| verify_signed_binary "$direct_zstd_path" "$binary" |
| done |
|
|
| case "${{ matrix.bundle }}" in |
| primary) |
| package_stem="codex-package" |
| package_entrypoint="codex" |
| ;; |
| app-server) |
| package_stem="codex-app-server-package" |
| package_entrypoint="codex-app-server" |
| ;; |
| *) |
| echo "Unexpected macOS bundle: ${{ matrix.bundle }}" |
| exit 1 |
| ;; |
| esac |
|
|
| package_dir="${RUNNER_TEMP}/${package_stem}-${target}" |
| rm -rf "$package_dir" |
| mkdir -p "$package_dir" |
| tar -xzf "${packaged_dir}/${package_stem}-${target}.tar.gz" -C "$package_dir" |
| verify_signed_binary "${package_dir}/bin/${package_entrypoint}" "$package_entrypoint" |
| verify_signed_binary "${package_dir}/bin/codex-code-mode-host" "codex-code-mode-host" |
|
|
| for resource in \ |
| "${package_dir}/codex-path/rg" \ |
| "${package_dir}/codex-resources/zsh/bin/zsh" |
| do |
| chmod 0755 "$resource" |
| lipo "$resource" -verify_arch "$expected_arch" |
| codesign --verify --strict --verbose=2 "$resource" |
| entitlements="$(mktemp)" |
| codesign -d --entitlements :- "$resource" >"$entitlements" |
| if [[ -s "$entitlements" ]]; then |
| echo "Bundled helper $resource must not have code-signing entitlements." >&2 |
| plutil -p "$entitlements" >&2 |
| exit 1 |
| fi |
| rm -f "$entitlements" |
| done |
|
|
| if [[ "${{ matrix.bundle }}" == "primary" ]]; then |
| voice="${package_dir}/codex-resources/voice" |
| [[ -f "${voice}/lib/libgstreamer-1.0.0.dylib" ]] |
| export VOICE_BUILD_COMMIT="$(git rev-parse HEAD)" |
| PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$voice" "$target" "$package_dir" <<'PY' |
| import json |
| import sys |
| from pathlib import Path |
| from package_runtime import runtime_files |
| from runtime import digest |
|
|
| voice, target, package = map(Path, sys.argv[1:]) |
| runtime_files(voice.resolve(strict=True), str(target), public_release=True) |
| manifest = json.loads((voice / "manifest.json").read_text()) |
| assert manifest["buildCommit"] == __import__("os").environ["VOICE_BUILD_COMMIT"] |
| for relative, expected in manifest["sha256"].items(): |
| assert digest(package / relative) == expected, relative |
| PY |
| helper="${voice}/bin/codex-voice-host" |
| [[ "$("$helper" --build-commit)" == "$VOICE_BUILD_COMMIT" ]] |
| while IFS= read -r -d '' library; do |
| lipo "$library" -verify_arch "$expected_arch" |
| codesign --verify --strict --verbose=2 "$library" |
| done < <(find "$voice" -name '*.dylib' -type f -print0) |
| verify_signed_binary "$helper" "codex-voice-host" |
| fi |
|
|
| if [[ "${{ matrix.verify_dmg }}" != "true" ]]; then |
| exit 0 |
| fi |
|
|
| dmg_path="${RUNNER_TEMP}/signed-dmg/codex-${target}.dmg" |
| mount_dir="${RUNNER_TEMP}/codex-dmg-mount-${target}" |
| if [[ ! -f "$dmg_path" ]]; then |
| echo "Signed DMG $dmg_path not found" |
| exit 1 |
| fi |
|
|
| hdiutil verify "$dmg_path" |
| codesign --verify --strict --verbose=2 "$dmg_path" |
| xcrun stapler validate "$dmg_path" |
|
|
| rm -rf "$mount_dir" |
| mkdir -p "$mount_dir" |
| hdiutil attach "$dmg_path" -nobrowse -readonly -mountpoint "$mount_dir" |
| cleanup_mount() { |
| hdiutil detach "$mount_dir" >/dev/null |
| } |
| trap cleanup_mount EXIT |
|
|
| for binary in ${{ matrix.binaries }}; do |
| verify_signed_binary "${mount_dir}/${binary}" "$binary" |
| done |
|
|
| if [[ "${{ matrix.bundle }}" == "primary" ]]; then |
| [[ "$(readlink "${mount_dir}/codex")" == "bin/codex" ]] |
| cmp "${mount_dir}/codex-package.json" "${package_dir}/codex-package.json" |
| cmp "${mount_dir}/codex-resources/voice/manifest.json" \ |
| "${package_dir}/codex-resources/voice/manifest.json" |
| cmp "${mount_dir}/codex-resources/voice/bin/codex-voice-host" \ |
| "${package_dir}/codex-resources/voice/bin/codex-voice-host" |
| fi |
|
|
| cleanup_mount |
| trap - EXIT |
| cp "$dmg_path" "dist/${target}/codex-${target}.dmg" |
|
|
| - name: Upload verified macOS artifacts |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: ${{ matrix.artifact_name }} |
| path: codex-rs/dist/${{ matrix.target }}/* |
| if-no-files-found: error |
|
|
| |
| |
| provisioned-macos-candidate: |
| needs: finalize-macos |
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/rust-v') && vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true' |
| permissions: |
| contents: read |
| id-token: write |
| uses: ./.github/workflows/rust-release-provisioned-macos.yml |
| secrets: inherit |
|
|
| build-windows: |
| needs: tag-check |
| uses: ./.github/workflows/rust-release-windows.yml |
| secrets: inherit |
|
|
| argument-comment-lint-release-assets: |
| name: argument-comment-lint release assets |
| needs: tag-check |
| uses: ./.github/workflows/rust-release-argument-comment-lint.yml |
| with: |
| publish: true |
|
|
| stage-npm-packages: |
| name: stage npm packages |
| needs: |
| - build |
| - finalize-macos |
| - build-windows |
| runs-on: ubuntu-latest |
| permissions: |
| contents: read |
|
|
| steps: |
| - name: Checkout repository |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
|
|
| - name: Download target artifacts |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| path: dist |
| pattern: "{aarch64,x86_64}-{apple-darwin{,-app-server},unknown-linux-musl{,-app-server},pc-windows-msvc}" |
|
|
| - name: Setup pnpm |
| uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 |
| with: |
| run_install: false |
|
|
| - name: Setup Node.js for npm packaging |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f |
| with: |
| node-version: 22 |
|
|
| - name: Install dependencies |
| run: pnpm install --frozen-lockfile |
|
|
| - name: Stage npm packages |
| run: | |
| release_version="${GITHUB_REF_NAME#rust-v}" |
| ./scripts/stage_npm_packages.py \ |
| --release-version "$release_version" \ |
| --artifacts-dir "${GITHUB_WORKSPACE}/dist" \ |
| --package codex \ |
| --package codex-responses-api-proxy \ |
| --package codex-sdk |
| |
| - name: Upload staged npm packages |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: npm-packages |
| path: dist/npm/*.tgz |
| if-no-files-found: error |
|
|
| release: |
| needs: |
| - tag-check |
| - build |
| - finalize-macos |
| - build-windows |
| - argument-comment-lint-release-assets |
| - stage-npm-packages |
| - provisioned-macos-candidate |
| if: >- |
| ${{ |
| always() && |
| needs.tag-check.result == 'success' && |
| needs.build.result == 'success' && |
| needs.finalize-macos.result == 'success' && |
| needs.build-windows.result == 'success' && |
| needs.argument-comment-lint-release-assets.result == 'success' && |
| needs.stage-npm-packages.result == 'success' && |
| (needs.provisioned-macos-candidate.result == 'success' || |
| (vars.CODEX_PROVISIONED_MACOS_CANDIDATE != 'true' && |
| needs.provisioned-macos-candidate.result == 'skipped')) |
| }} |
| name: release |
| runs-on: ubuntu-latest |
| permissions: |
| contents: write |
| outputs: |
| version: ${{ steps.release_name.outputs.name }} |
| tag: ${{ github.ref_name }} |
| make_latest: ${{ steps.release_name.outputs.make_latest }} |
| prerelease: ${{ steps.release_name.outputs.prerelease }} |
| 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
|
|
| - name: Generate release notes from tag commit message |
| id: release_notes |
| shell: bash |
| run: | |
| set -euo pipefail |
| |
| |
| |
| commit="$(git rev-parse "${GITHUB_SHA}^{commit}")" |
| notes_path="${RUNNER_TEMP}/release-notes.md" |
|
|
| |
| |
| git log -1 --format=%B "${commit}" > "${notes_path}" |
| |
| |
| echo >> "${notes_path}" |
|
|
| echo "path=${notes_path}" >> "${GITHUB_OUTPUT}" |
|
|
| - name: Download release artifacts |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| path: dist |
| pattern: "{{aarch64,x86_64}-{apple-darwin{,-app-server},unknown-linux-musl{,-app-server},pc-windows-msvc},*-symbols,argument-comment-lint-*,python-runtime-wheel-*,npm-packages}" |
| |
| - name: Download verified provisioned macOS packages |
| if: vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true' |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 |
| with: |
| path: dist |
| pattern: "provisioned-macos-verified-candidate-*" |
| |
| - name: Remove per-architecture checksum filenames before release upload |
| if: vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true' |
| run: rm dist/provisioned-macos-verified-candidate-*/SHA256SUMS |
| |
| - name: List |
| run: ls -R dist/ |
| |
| - name: Add Codex package checksum manifest |
| run: | |
| set -euo pipefail |
| |
| manifest="dist/codex-package_SHA256SUMS" |
| tmp_manifest="$(mktemp)" |
| find dist -type f \ |
| \( -name 'codex-package-*.tar.gz' -o -name 'codex-app-server-package-*.tar.gz' -o -name 'codex-provisioned-package-*.tar.gz' \) \ |
| -print | |
| sort | |
| while IFS= read -r archive; do |
| sha256sum "$archive" | |
| awk -v name="$(basename "$archive")" '{ print $1 " " name }' |
| done > "$tmp_manifest" |
| |
| if [[ ! -s "$tmp_manifest" ]]; then |
| echo "No Codex package archives found for checksum manifest" |
| exit 1 |
| fi |
| |
| mv "$tmp_manifest" "$manifest" |
| cat "$manifest" |
| |
| - name: Add config schema release asset |
| run: | |
| cp codex-rs/core/config.schema.json dist/config-schema.json |
| |
| - name: Define release name |
| id: release_name |
| run: | |
| # Extract the version from the tag name, which is in the format |
| # "rust-v0.1.0". |
| version="${GITHUB_REF_NAME#rust-v}" |
| echo "name=${version}" >> $GITHUB_OUTPUT |
| if [[ "${version}" == *-* ]]; then |
| echo "make_latest=false" >> $GITHUB_OUTPUT |
| echo "prerelease=true" >> $GITHUB_OUTPUT |
| else |
| echo "make_latest=true" >> $GITHUB_OUTPUT |
| echo "prerelease=false" >> $GITHUB_OUTPUT |
| fi |
| |
| - 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]+(\.[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: Stage installer scripts |
| 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 |
| 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: ${{ steps.release_name.outputs.make_latest }} |
| |
| |
| prerelease: ${{ steps.release_name.outputs.prerelease }} |
|
|
| publish-r2-assets: |
| name: publish-r2-assets |
| needs: release |
| if: ${{ !cancelled() && needs.release.result == 'success' }} |
| permissions: |
| contents: read |
| uses: ./.github/workflows/r2-release.yml |
| secrets: inherit |
| with: |
| tag: ${{ needs.release.outputs.tag }} |
| make_latest: ${{ fromJSON(needs.release.outputs.make_latest) }} |
| prerelease: ${{ fromJSON(needs.release.outputs.prerelease) }} |
| stage: assets |
|
|
| publish-r2: |
| name: publish-r2 |
| needs: [release, publish-dotslash, publish-r2-assets] |
| if: >- |
| ${{ |
| !cancelled() && |
| needs.release.result == 'success' && |
| needs.publish-dotslash.result == 'success' && |
| needs.publish-r2-assets.result == 'success' |
| }} |
| permissions: |
| contents: read |
| uses: ./.github/workflows/r2-release.yml |
| secrets: inherit |
| with: |
| tag: ${{ needs.release.outputs.tag }} |
| make_latest: ${{ fromJSON(needs.release.outputs.make_latest) }} |
| prerelease: ${{ fromJSON(needs.release.outputs.prerelease) }} |
| stage: finalize |
|
|
| publish-dotslash: |
| name: publish-dotslash |
| needs: release |
| if: ${{ !cancelled() && needs.release.result == 'success' }} |
| runs-on: ubuntu-latest |
| permissions: |
| contents: write |
|
|
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
|
|
| - uses: ./.github/actions/publish-dotslash |
| env: |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| with: |
| tag: ${{ github.ref_name }} |
| configs: | |
| .github/dotslash-config.json |
| .github/dotslash-argument-comment-lint-config.json |
| |
| - name: Publish the provisioned macOS package manifest |
| if: vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true' |
| uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 |
| env: |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| with: |
| tag: ${{ github.ref_name }} |
| config: .github/dotslash-provisioned-config.json |
|
|
| |
| |
| |
| publish-npm: |
| |
| 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 |
| contents: read |
|
|
| steps: |
| - name: Setup Node.js |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f |
| with: |
| |
| 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 |
| |
| |
| - 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" |
| |
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| 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 |
|
|
| deploy-dev-website: |
| name: Trigger developers.openai.com deploy |
| needs: release |
| |
| |
| if: >- |
| ${{ |
| !cancelled() && |
| needs.release.result == 'success' && |
| !contains(needs.release.outputs.version, '-') |
| }} |
| runs-on: ubuntu-latest |
| continue-on-error: true |
| permissions: {} |
| environment: |
| name: dev-website-vercel-deploy |
| deployment: false |
|
|
| steps: |
| - name: Trigger developers.openai.com deploy |
| 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 |
| |
| winget: |
| name: winget |
| needs: release |
| |
| |
| if: >- |
| ${{ |
| !cancelled() && |
| needs.release.result == 'success' && |
| !contains(needs.release.outputs.version, '-') |
| }} |
| |
| |
| runs-on: ubuntu-latest |
| permissions: |
| contents: read |
| environment: |
| name: mainline-release-winget |
| deployment: false |
|
|
| 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.publish-r2.result == 'success' && |
| ( |
| needs.release.outputs.should_publish_npm != 'true' || |
| needs.publish-npm.result == 'success' |
| ) |
| }} |
| permissions: |
| contents: write |
| |
| needs: |
| - release |
| - publish-r2 |
| - publish-npm |
| 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 |
| |