diff --git a/.github/actions/build-codex-packages/action.yml b/.github/actions/build-codex-packages/action.yml new file mode 100644 index 0000000000..ac1bd2ea54 --- /dev/null +++ b/.github/actions/build-codex-packages/action.yml @@ -0,0 +1,300 @@ +name: build-codex-packages +description: Build and smoke-test unsigned Codex CLI and app-server release packages. +inputs: + target: + description: Rust release target triple to build and test. + required: true + package-version: + description: Semantic version to record in both packages. + required: true + build-mode: + description: Use release optimizations (release) or disable them for faster smoke coverage (debug). + required: true + +# Resolve package sources relative to the action so it works in both repository layouts. +runs: + using: composite + steps: + - name: Select release binaries + shell: bash + working-directory: codex-rs + env: + BASE_BINARIES: "codex codex-app-server codex-code-mode-host" + WINDOWS_ONLY_BINARIES: "codex-command-runner codex-windows-sandbox-setup" + BUILD_MODE: ${{ inputs.build-mode }} + # macOS symbol extraction needs packed dSYM bundles before binaries are stripped. + CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO: ${{ contains(inputs.target, 'apple-darwin') && 'packed' || 'off' }} + run: | + set -euo pipefail + needed_binaries="$BASE_BINARIES" + if [[ "$RUNNER_OS" == "Windows" ]]; then + needed_binaries+=" $WINDOWS_ONLY_BINARIES" + fi + echo "NEEDED_BINARIES=$needed_binaries" >> "$GITHUB_ENV" + echo "CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=$CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO" >> "$GITHUB_ENV" + case "$BUILD_MODE" in + release) ;; + debug) + # Use the release profile so assertions, symbols and packaging match production. + # Only trade compiler optimizations for faster builds. + { + echo "CARGO_PROFILE_RELEASE_OPT_LEVEL=0" + echo "CARGO_PROFILE_RELEASE_LTO=off" + echo "CARGO_PROFILE_RELEASE_CODEGEN_UNITS=256" + } >> "$GITHUB_ENV" + ;; + *) echo "Unsupported build mode: $BUILD_MODE" >&2; exit 1 ;; + esac + + - uses: ./.github/actions/setup-ci + + - name: Set up Python 3.12 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + version: "0.11.3" + + - name: Install Linux build dependencies + if: ${{ runner.os == 'Linux' }} + shell: bash + working-directory: codex-rs + run: | + set -euo pipefail + sudo apt-get update -y + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends binutils make pkg-config libcap-dev + + - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 + with: + targets: ${{ inputs.target }} + + - name: Use hermetic Cargo home (musl) + if: ${{ runner.os == 'Linux' }} + shell: bash + working-directory: codex-rs + 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" + + - name: Install Zig + if: ${{ runner.os == 'Linux' }} + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 + with: + version: 0.14.0 + use-cache: false + + - name: Install musl build tools + if: ${{ runner.os == 'Linux' }} + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh" + + - name: Disable aws-lc jitter entropy (musl) + if: ${{ runner.os == 'Linux' }} + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + run: | + set -euo pipefail + # Match public releases: aws-lc's jitter entropy path does not work + # reliably on musl builders, so disable it globally and per target. + echo "AWS_LC_SYS_NO_JITTER_ENTROPY=1" >> "$GITHUB_ENV" + target_no_jitter="AWS_LC_SYS_NO_JITTER_ENTROPY_${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: ${{ inputs.target }} + + - name: Configure MSVC environment + if: ${{ runner.os == 'Windows' }} + uses: ./.github/actions/setup-msvc-env + with: + target: ${{ inputs.target }} + + - name: Build bwrap and export digest + if: ${{ runner.os == 'Linux' }} + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + run: | + set -euo pipefail + cargo build --target "${TARGET}" --release --timings --bin bwrap + + bwrap_path="${CARGO_TARGET_DIR}/${TARGET}/release/bwrap" + # Codex embeds this digest at build time and verifies the packaged + # bwrap at runtime, so hash its final stripped bytes first. + strip --strip-debug --strip-unneeded "$bwrap_path" + digest="$(sha256sum "$bwrap_path" | awk '{print $1}')" + echo "CODEX_BWRAP_SHA256=${digest}" >> "$GITHUB_ENV" + + - name: Build Codex package binaries + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + run: | + set -euo pipefail + build_args=() + for binary in $NEEDED_BINARIES; do + build_args+=(--bin "$binary") + done + # SQLite intrinsics can emit instructions unsupported by older x64 + # CPUs, causing Windows release binaries to crash during startup. + if [[ "${TARGET}" == "x86_64-pc-windows-msvc" ]]; then + export LIBSQLITE3_FLAGS=SQLITE_DISABLE_INTRINSIC + fi + STABLE_GIT_COMMIT="$(git rev-parse HEAD)" + export STABLE_GIT_COMMIT + cargo build --target "${TARGET}" --release --timings "${build_args[@]}" + + - name: Normalize Windows PDB filenames + if: ${{ runner.os == 'Windows' }} + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + run: | + set -euo pipefail + release_dir="${CARGO_TARGET_DIR}/${TARGET}/release" + for binary in $NEEDED_BINARIES; do + expected_pdb="${release_dir}/${binary}.pdb" + if [[ -f "$expected_pdb" ]]; then + continue + fi + + underscored_pdb="${release_dir}/${binary//-/_}.pdb" + if [[ ! -f "$underscored_pdb" ]]; then + echo "PDB for ${binary} not found at ${expected_pdb} or ${underscored_pdb}" >&2 + exit 1 + fi + cp "$underscored_pdb" "$expected_pdb" + done + + - name: Download packaged zsh manifest + if: ${{ runner.os != 'Windows' }} + shell: bash + working-directory: codex-rs + run: | + set -euo pipefail + curl -fsSL \ + "https://github.com/openai/codex/releases/download/codex-zsh-v0.1.0/codex-zsh" \ + -o "${RUNNER_TEMP}/codex-zsh" + + - name: Archive symbols and strip binaries + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + PACKAGE_ACTION_PATH: ${{ github.action_path }} + run: | + set -euo pipefail + bash "${PACKAGE_ACTION_PATH}/../../../.github/scripts/archive-release-symbols-and-strip-binaries.sh" \ + --target "${TARGET}" \ + --artifact-name "${TARGET}" \ + --release-dir "${CARGO_TARGET_DIR}/${TARGET}/release" \ + --archive-dir "../symbols-dist/${TARGET}" \ + --binaries "$NEEDED_BINARIES" + + - name: Build Codex package archives + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + CODEX_PACKAGE_VERSION: ${{ inputs.package-version }} + PACKAGE_ACTION_PATH: ${{ github.action_path }} + run: | + set -euo pipefail + target="${TARGET}" + release_dir="${CARGO_TARGET_DIR}/${target}/release" + archive_dir="${GITHUB_WORKSPACE}/dist/${target}" + exe_suffix="" + if [[ "$RUNNER_OS" == "Windows" ]]; then + exe_suffix=".exe" + fi + + package_args=( + --target "$target" + --code-mode-host-bin "${release_dir}/codex-code-mode-host${exe_suffix}" + --cargo-profile release + --package-version "$CODEX_PACKAGE_VERSION" + --force + ) + if [[ "$RUNNER_OS" == "Linux" ]]; then + package_args+=(--bwrap-bin "${release_dir}/bwrap") + fi + if [[ "$RUNNER_OS" == "Windows" ]]; then + package_args+=( + --codex-command-runner-bin "${release_dir}/codex-command-runner.exe" + --codex-windows-sandbox-setup-bin "${release_dir}/codex-windows-sandbox-setup.exe" + ) + else + package_args+=(--zsh-manifest "${RUNNER_TEMP}/codex-zsh") + fi + + python3 "${PACKAGE_ACTION_PATH}/../../../scripts/build_codex_package.py" \ + "${package_args[@]}" \ + --variant codex \ + --entrypoint-bin "${release_dir}/codex${exe_suffix}" \ + --package-dir "${RUNNER_TEMP}/codex-package-${target}" \ + --archive-output "${archive_dir}/codex-package-${target}.tar.gz" \ + --archive-output "${archive_dir}/codex-package-${target}.tar.zst" + + python3 "${PACKAGE_ACTION_PATH}/../../../scripts/build_codex_package.py" \ + "${package_args[@]}" \ + --variant codex-app-server \ + --entrypoint-bin "${release_dir}/codex-app-server${exe_suffix}" \ + --package-dir "${RUNNER_TEMP}/codex-app-server-package-${target}" \ + --archive-output "${archive_dir}/codex-app-server-package-${target}.tar.gz" \ + --archive-output "${archive_dir}/codex-app-server-package-${target}.tar.zst" + + - name: Smoke-test assembled packages + shell: bash + working-directory: codex-rs + env: + TARGET: ${{ inputs.target }} + PYTHONPATH: ${{ github.workspace }}/sdk/python/src${{ runner.os == 'Windows' && ';' || ':' }}${{ github.workspace }}/sdk/python/tests + PACKAGE_ACTION_PATH: ${{ github.action_path }} + run: | + set -euo pipefail + cd "${PACKAGE_ACTION_PATH}/../../../scripts/codex_package/smoke_tests" + target="${TARGET}" + archive_dir="${GITHUB_WORKSPACE}/dist/${target}" + mkdir -p "${GITHUB_WORKSPACE}/codex-rs/target" + pytest_args=( + -v + --basetemp "${GITHUB_WORKSPACE}/codex-rs/target/package-smoke-tmp" + --package-target "$target" + --symbols-archive "${GITHUB_WORKSPACE}/symbols-dist/${target}/codex-symbols-${target}.tar.gz" + ) + + # CI must exercise packaged tools; local smoke runs may use host tools. + if command -v rg || { [[ "$RUNNER_OS" == "Linux" ]] && command -v bwrap; }; then + echo "Host rg (or bwrap on Linux) is on PATH; CI must use packaged tools." >&2 + exit 1 + fi + + uv run --frozen pytest "${pytest_args[@]}" \ + --compression gzip \ + --cli-archive "${archive_dir}/codex-package-${target}.tar.gz" \ + --app-server-archive "${archive_dir}/codex-app-server-package-${target}.tar.gz" \ + test_codex_package.py + + uv run --frozen pytest "${pytest_args[@]}" \ + --compression zstd \ + --cli-archive "${archive_dir}/codex-package-${target}.tar.zst" \ + --app-server-archive "${archive_dir}/codex-app-server-package-${target}.tar.zst" \ + test_codex_package.py