diff --git a/.github/workflows/release.yml b/.github/workflows/build.yml similarity index 73% rename from .github/workflows/release.yml rename to .github/workflows/build.yml index 8b3770a..e01cc5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/build.yml @@ -1,16 +1,19 @@ -name: Build and release +name: Build release candidate on: workflow_dispatch: inputs: - tag: - description: Immutable release tag (must match versions.env) + ort_version: + description: Stable upstream ONNX Runtime version (MAJOR.MINOR.PATCH) required: true - prerelease: - description: Mark the GitHub release as a prerelease + package_revision: + description: Packaging revision required: true - type: boolean - default: false + default: "1" + +concurrency: + group: ort-build-${{ inputs.ort_version }}-r${{ inputs.package_revision }} + cancel-in-progress: false permissions: contents: read @@ -30,60 +33,63 @@ jobs: outputs: ort_ref: ${{ steps.meta.outputs.ort_ref }} ort_version: ${{ steps.meta.outputs.ort_version }} + ort_commit: ${{ steps.meta.outputs.ort_commit }} ort_telemetry: ${{ steps.meta.outputs.ort_telemetry }} + python_version: ${{ steps.meta.outputs.python_version }} + node_version: ${{ steps.meta.outputs.node_version }} package_channel: ${{ steps.meta.outputs.package_channel }} package_revision: ${{ steps.meta.outputs.package_revision }} package_label: ${{ steps.meta.outputs.package_label }} release_tag: ${{ steps.meta.outputs.release_tag }} + build_env_sha256: ${{ steps.meta.outputs.build_env_sha256 }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - id: meta shell: bash env: - REQUESTED_TAG: ${{ inputs.tag }} - REQUESTED_PRERELEASE: ${{ inputs.prerelease }} + GH_TOKEN: ${{ github.token }} + ORT_VERSION_INPUT: ${{ inputs.ort_version }} + PACKAGE_REVISION_INPUT: ${{ inputs.package_revision }} run: | set -euo pipefail - source versions.env - [[ "$ORT_TELEMETRY" == disabled ]] || { - echo "ORT_TELEMETRY must be disabled; found ${ORT_TELEMETRY:-}" >&2 - exit 1 - } - case "$PACKAGE_CHANNEL" in - pilot) package_label="pilot.$PACKAGE_REVISION"; expected_prerelease=true ;; - stable) package_label="r$PACKAGE_REVISION"; expected_prerelease=false ;; - *) echo "unsupported PACKAGE_CHANNEL: $PACKAGE_CHANNEL" >&2; exit 1 ;; - esac - release_tag="ort-$ORT_VERSION-$package_label" - [[ "$GITHUB_REF" == refs/heads/main ]] || { - echo "releases may only run from main; found $GITHUB_REF" >&2 - exit 1 - } - [[ "$REQUESTED_TAG" == "$release_tag" ]] || { - echo "release tag must be $release_tag; found ${REQUESTED_TAG:-}" >&2 - exit 1 - } - [[ "$REQUESTED_PRERELEASE" == "$expected_prerelease" ]] || { - echo "PACKAGE_CHANNEL=$PACKAGE_CHANNEL requires prerelease=$expected_prerelease" >&2 + echo "release candidates may only build from main; found $GITHUB_REF" >&2 exit 1 } + bash scripts/resolve-release.sh "$ORT_VERSION_INPUT" "$PACKAGE_REVISION_INPUT" >> "$GITHUB_OUTPUT" - { - echo "ort_ref=$ORT_REF" - echo "ort_version=$ORT_VERSION" - echo "ort_telemetry=$ORT_TELEMETRY" - echo "package_channel=$PACKAGE_CHANNEL" - echo "package_revision=$PACKAGE_REVISION" - echo "package_label=$package_label" - echo "release_tag=$release_tag" - } >> "$GITHUB_OUTPUT" + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: microsoft/onnxruntime + ref: ${{ steps.meta.outputs.ort_commit }} + path: onnxruntime + fetch-depth: 1 + + - name: Verify patches and portable smoke-test headers + shell: bash + env: + ORT_COMMIT: ${{ steps.meta.outputs.ort_commit }} + run: | + set -euo pipefail + [ "$(git -C onnxruntime rev-parse HEAD)" = "$ORT_COMMIT" ] + for patch in "$GITHUB_WORKSPACE"/patches/*.patch; do + git -C onnxruntime apply --check "$patch" + git -C onnxruntime apply "$patch" + done + cmake -S tests -B "$RUNNER_TEMP/runtime-smoke" \ + -DORT_INCLUDE_DIR="$GITHUB_WORKSPACE/onnxruntime/include/onnxruntime/core/session" + cmake --build "$RUNNER_TEMP/runtime-smoke" --parallel android: name: Android ${{ matrix.abi }} needs: release-meta runs-on: ubuntu-24.04 timeout-minutes: 360 + env: + ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} + ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} + PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} strategy: fail-fast: false matrix: @@ -93,7 +99,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: microsoft/onnxruntime - ref: ${{ needs.release-meta.outputs.ort_ref }} + ref: ${{ needs.release-meta.outputs.ort_commit }} path: onnxruntime fetch-depth: 1 @@ -111,10 +117,10 @@ jobs: - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.12" + python-version: ${{ needs.release-meta.outputs.python_version }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "20" + node-version: ${{ needs.release-meta.outputs.node_version }} - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: temurin @@ -125,7 +131,7 @@ jobs: shell: bash run: | set -euo pipefail - source versions.env + source build.env sudo apt-get update sudo apt-get install -y --no-install-recommends ninja-build unzip zip yes | sdkmanager --licenses >/dev/null || true @@ -150,7 +156,7 @@ jobs: shell: bash run: | set -euo pipefail - source versions.env + source build.env export ANDROID_AVD_HOME="$RUNNER_TEMP/android-avd" mkdir -p "$ANDROID_AVD_HOME" echo no | avdmanager create avd --force \ @@ -204,12 +210,17 @@ jobs: needs: [release-meta, android] runs-on: ubuntu-24.04 timeout-minutes: 30 + env: + ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} + ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} + PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: microsoft/onnxruntime - ref: ${{ needs.release-meta.outputs.ort_ref }} + ref: ${{ needs.release-meta.outputs.ort_commit }} path: onnxruntime fetch-depth: 1 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -236,12 +247,17 @@ jobs: needs: release-meta runs-on: macos-15 timeout-minutes: 360 + env: + ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} + ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} + PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: microsoft/onnxruntime - ref: ${{ needs.release-meta.outputs.ort_ref }} + ref: ${{ needs.release-meta.outputs.ort_commit }} path: onnxruntime fetch-depth: 1 @@ -258,7 +274,7 @@ jobs: disable-terrapin: "true" - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.12" + python-version: ${{ needs.release-meta.outputs.python_version }} - name: Install Apple packaging requirements run: | @@ -305,12 +321,16 @@ jobs: env: CC: gcc CXX: g++ + ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} + ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} + PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: microsoft/onnxruntime - ref: ${{ needs.release-meta.outputs.ort_ref }} + ref: ${{ needs.release-meta.outputs.ort_commit }} path: onnxruntime fetch-depth: 1 @@ -324,12 +344,14 @@ jobs: shell: bash run: | set -euo pipefail - source versions.env + source build.env [ "$(uname -m)" = "${{ matrix.machine }}" ] [ "$(getconf GNU_LIBC_VERSION)" = "glibc $LINUX_MAX_GLIBC" ] - [ -x /opt/python/cp312-cp312/bin/python ] + python_tag="${PYTHON_VERSION//./}" + python_dir="/opt/python/cp$python_tag-cp$python_tag" + [ -x "$python_dir/bin/python" ] [ "$(gcc -dumpversion | cut -d. -f1)" = 14 ] - echo /opt/python/cp312-cp312/bin >> "$GITHUB_PATH" + echo "$python_dir/bin" >> "$GITHUB_PATH" gcc --version - name: Install Linux build tools @@ -349,7 +371,7 @@ jobs: disable-terrapin: "true" - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "20" + node-version: ${{ needs.release-meta.outputs.node_version }} - name: Install pinned Python build tools shell: bash @@ -372,6 +394,11 @@ jobs: needs: [release-meta, linux-desktop] runs-on: ${{ matrix.runner }} timeout-minutes: 60 + env: + ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} + ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} + PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} strategy: fail-fast: false matrix: @@ -385,7 +412,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: microsoft/onnxruntime - ref: ${{ needs.release-meta.outputs.ort_ref }} + ref: ${{ needs.release-meta.outputs.ort_commit }} path: onnxruntime fetch-depth: 1 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -417,6 +444,11 @@ jobs: needs: release-meta runs-on: ${{ matrix.runner }} timeout-minutes: 360 + env: + ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} + ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} + PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} strategy: fail-fast: false matrix: @@ -434,7 +466,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: microsoft/onnxruntime - ref: ${{ needs.release-meta.outputs.ort_ref }} + ref: ${{ needs.release-meta.outputs.ort_commit }} path: onnxruntime fetch-depth: 1 - uses: microsoft/onnxruntime-github-actions/setup-build-tools@8bad63a3c05d448311dfa8e5f531171c97471aa1 # v0.0.12 @@ -450,15 +482,15 @@ jobs: disable-terrapin: "true" - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.12" + python-version: ${{ needs.release-meta.outputs.python_version }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "20" + node-version: ${{ needs.release-meta.outputs.node_version }} - name: Install pinned Python build tools shell: bash run: | - source versions.env + source build.env python -m pip install --disable-pip-version-check \ -r onnxruntime/tools/ci_build/github/linux/docker/scripts/requirements.txt @@ -480,6 +512,11 @@ jobs: needs: release-meta runs-on: ${{ matrix.runner }} timeout-minutes: 360 + env: + ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} + ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} + PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} strategy: fail-fast: false matrix: @@ -496,7 +533,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: microsoft/onnxruntime - ref: ${{ needs.release-meta.outputs.ort_ref }} + ref: ${{ needs.release-meta.outputs.ort_commit }} path: onnxruntime fetch-depth: 1 - uses: microsoft/onnxruntime-github-actions/setup-build-tools@8bad63a3c05d448311dfa8e5f531171c97471aa1 # v0.0.12 @@ -525,15 +562,15 @@ jobs: disable-terrapin: "true" - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.12" + python-version: ${{ needs.release-meta.outputs.python_version }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "20" + node-version: ${{ needs.release-meta.outputs.node_version }} - name: Install pinned build tools shell: pwsh run: | - Get-Content versions.env | ForEach-Object { + Get-Content build.env | ForEach-Object { if ($_ -match '^([^#=]+)=(.*)$') { Set-Item "env:$($Matches[1])" $Matches[2] } } python -m pip install --disable-pip-version-check ` @@ -554,17 +591,36 @@ jobs: if-no-files-found: error retention-days: 14 - release: - name: Publish release assets + candidate: + name: Assemble release candidate needs: [release-meta, android-universal, ios, linux-runtime-smoke, macos-desktop, windows] runs-on: ubuntu-24.04 - permissions: - contents: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Download Android artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: android-* + path: ${{ runner.temp }}/artifacts + - name: Download iOS artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ios + path: ${{ runner.temp }}/artifacts + - name: Download Linux artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: linux-* + path: ${{ runner.temp }}/artifacts + - name: Download macOS artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: macos-* + path: ${{ runner.temp }}/artifacts + - name: Download Windows artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - pattern: "*" + pattern: windows-* path: ${{ runner.temp }}/artifacts - name: Collect and verify assets @@ -572,11 +628,13 @@ jobs: env: ORT_REF: ${{ needs.release-meta.outputs.ort_ref }} ORT_VERSION: ${{ needs.release-meta.outputs.ort_version }} + ORT_COMMIT: ${{ needs.release-meta.outputs.ort_commit }} ORT_TELEMETRY: ${{ needs.release-meta.outputs.ort_telemetry }} PACKAGE_CHANNEL: ${{ needs.release-meta.outputs.package_channel }} PACKAGE_REVISION: ${{ needs.release-meta.outputs.package_revision }} PACKAGE_LABEL: ${{ needs.release-meta.outputs.package_label }} RELEASE_TAG: ${{ needs.release-meta.outputs.release_tag }} + BUILD_ENV_SHA256: ${{ needs.release-meta.outputs.build_env_sha256 }} run: | set -euo pipefail mkdir release-assets @@ -599,6 +657,7 @@ jobs: [ "$actual" = "$expected" ] grep -Fqx "ORT_REF=$ORT_REF" "$manifest" grep -Fqx "ORT_VERSION=$ORT_VERSION" "$manifest" + grep -Fqx "ORT_COMMIT=$ORT_COMMIT" "$manifest" grep -Fqx "ORT_TELEMETRY=$ORT_TELEMETRY" "$manifest" grep -Fqx "PACKAGING_COMMIT=$GITHUB_SHA" "$manifest" grep -Fqx "PACKAGE_CHANNEL=$PACKAGE_CHANNEL" "$manifest" @@ -611,22 +670,50 @@ jobs: done [ "$(find release-assets -maxdepth 1 -type f | wc -l)" -eq 33 ] + set -a + source build.env + set +a python3 - <<'PY' import json import os + release_notes = ( + f"Custom ONNX Runtime packaging build for {os.environ['ORT_REF']}.\n\n" + f"Package channel: stable ({os.environ['PACKAGE_LABEL']}). " + "Telemetry is compiled out of every package. " + f"Android assets target API {os.environ['ANDROID_MIN_SDK']} and contain a custom " + "ONNX Runtime core with WebGPU and XNNPACK built in. Linux and Windows runtime " + "bundles contain a matching custom ONNX Runtime core and WebGPU plugin; Linux " + f"packages require no newer than GLIBC {os.environ['LINUX_MAX_GLIBC']} and " + f"GLIBCXX {os.environ['LINUX_MAX_GLIBCXX']}, and Windows archives include the " + f"checksum-pinned DXC {os.environ['DXC_VERSION']} runtime DLLs and their licenses. " + "macOS archives contain a CoreML-enabled ORT core targeting macOS " + f"{os.environ['MACOS_MIN_VERSION']} and later, with no WebGPU provider. The iOS " + "ZIP contains one static-library XCFramework with device and ARM64 Simulator " + f"slices, CoreML and CPU support, and an iOS {os.environ['IOS_MIN_VERSION']} " + "deployment target. Custom-built binaries are stripped of or packaged without " + "debug symbol data. CI ran packaged-runtime inference tests on every native " + "runner before publication. Every binary has a verified SHA-256 sidecar and " + "build manifest. The build-provenance.json asset records the source commits and " + "release inputs." + ) provenance = { "release_tag": os.environ["RELEASE_TAG"], "packaging_commit": os.environ["GITHUB_SHA"], "upstream_ort_ref": os.environ["ORT_REF"], "upstream_ort_version": os.environ["ORT_VERSION"], + "upstream_ort_commit": os.environ["ORT_COMMIT"], "ort_telemetry": os.environ["ORT_TELEMETRY"], + "build_env_sha256": os.environ["BUILD_ENV_SHA256"], "package_channel": os.environ["PACKAGE_CHANNEL"], "package_revision": int(os.environ["PACKAGE_REVISION"]), "package_label": os.environ["PACKAGE_LABEL"], "release_scope": "all", "github_repository": os.environ["GITHUB_REPOSITORY"], - "github_run_id": os.environ["GITHUB_RUN_ID"], + "github_build_run_id": int(os.environ["GITHUB_RUN_ID"]), + "github_build_run_attempt": int(os.environ["GITHUB_RUN_ATTEMPT"]), + "github_workflow_ref": os.environ["GITHUB_WORKFLOW_REF"], + "release_notes": release_notes, } with open("release-assets/build-provenance.json", "w", encoding="utf-8") as provenance_file: json.dump(provenance, provenance_file, indent=2, sort_keys=True) @@ -640,33 +727,30 @@ jobs: \( -name '*.aar' -o -name '*.tar.gz' -o -name '*.zip' \) | sort) sha256sum "${binaries[@]}" > SHA256SUMS ) + bash scripts/verify-release-candidate.sh \ + release-assets "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT" "$GITHUB_SHA" \ + >/dev/null + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: release-candidate + path: release-assets/* + if-no-files-found: error + retention-days: 14 - - name: Publish immutable GitHub release + - name: Write publication instructions env: - GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ needs.release-meta.outputs.release_tag }} - PRERELEASE: ${{ inputs.prerelease }} shell: bash run: | - set -euo pipefail - prerelease_flag=() - if [ "$PRERELEASE" = "true" ]; then prerelease_flag+=(--prerelease); fi - validation_note="CI runs packaged-runtime inference tests on every native runner before this release is published." - notes=$(mktemp) - cat >"$notes" </dev/null 2>&1; then - echo "Release $RELEASE_TAG already exists; immutable releases cannot be overwritten." >&2 - exit 1 - fi - gh release create "$RELEASE_TAG" release-assets/* \ - --target "$GITHUB_SHA" \ - --title "$RELEASE_TAG" \ - --notes-file "$notes" \ - "${prerelease_flag[@]}" - - gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" \ - --jq 'if .draft == false and .immutable == true and (.assets | length) == 35 then empty else error("published release is not immutable or has the wrong asset count") end' + { + echo "## $RELEASE_TAG is ready" + echo + echo "Publish this exact candidate with:" + echo + echo '```sh' + echo "gh workflow run publish.yml --repo $GITHUB_REPOSITORY --ref main -f build_run_id=$GITHUB_RUN_ID" + echo '```' + echo + echo "Candidate artifacts expire after 14 days." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f750fc7 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,154 @@ +name: Publish release + +on: + workflow_dispatch: + inputs: + build_run_id: + description: Successful Build release candidate run ID + required: true + +concurrency: + group: ort-publish + cancel-in-progress: false + +permissions: + actions: read + contents: write + +jobs: + publish: + name: Publish verified candidate + runs-on: ubuntu-24.04 + steps: + - name: Validate build run + id: build + env: + BUILD_RUN_ID: ${{ inputs.build_run_id }} + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + [[ "$GITHUB_REF" == refs/heads/main ]] || { + echo "releases may only publish from main; found $GITHUB_REF" >&2 + exit 1 + } + [[ "$BUILD_RUN_ID" =~ ^[1-9][0-9]*$ ]] || { + echo "build run ID must be a positive integer" >&2 + exit 1 + } + + run="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN_ID")" + jq --exit-status \ + --arg repository "$GITHUB_REPOSITORY" ' + .event == "workflow_dispatch" and + .status == "completed" and + .conclusion == "success" and + .head_branch == "main" and + .path == ".github/workflows/build.yml" and + .head_repository.full_name == $repository and + (.head_sha | test("^[0-9a-f]{40}$")) and + (.run_attempt | type) == "number" and + .run_attempt >= 1 + ' <<<"$run" >/dev/null || { + echo "run $BUILD_RUN_ID is not a successful main-branch release-candidate build" >&2 + exit 1 + } + + echo "commit=$(jq -r .head_sha <<<"$run")" >> "$GITHUB_OUTPUT" + echo "attempt=$(jq -r .run_attempt <<<"$run")" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - name: Verify build commit belongs to main + env: + BUILD_COMMIT: ${{ steps.build.outputs.commit }} + shell: bash + run: | + set -euo pipefail + git cat-file -e "$BUILD_COMMIT^{commit}" + git merge-base --is-ancestor "$BUILD_COMMIT" origin/main + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-candidate + path: ${{ runner.temp }}/release-candidate + github-token: ${{ github.token }} + repository: ${{ github.repository }} + run-id: ${{ inputs.build_run_id }} + digest-mismatch: error + + - name: Verify release candidate + id: candidate + env: + BUILD_COMMIT: ${{ steps.build.outputs.commit }} + BUILD_RUN_ATTEMPT: ${{ steps.build.outputs.attempt }} + BUILD_RUN_ID: ${{ inputs.build_run_id }} + CANDIDATE_DIR: ${{ runner.temp }}/release-candidate + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + bash scripts/verify-release-candidate.sh \ + "$CANDIDATE_DIR" "$BUILD_RUN_ID" "$BUILD_RUN_ATTEMPT" "$BUILD_COMMIT" \ + >> "$GITHUB_OUTPUT" + + resolved="$(mktemp)" + bash scripts/resolve-release.sh \ + "$(jq -r .upstream_ort_version "$CANDIDATE_DIR/build-provenance.json")" \ + "$(jq -r .package_revision "$CANDIDATE_DIR/build-provenance.json")" \ + > "$resolved" + grep -Fqx "ort_commit=$(jq -r .upstream_ort_commit "$CANDIDATE_DIR/build-provenance.json")" "$resolved" + + - name: Determine latest-release status + id: latest + env: + GH_TOKEN: ${{ github.token }} + ORT_VERSION: ${{ steps.candidate.outputs.ort_version }} + PACKAGE_REVISION: ${{ steps.candidate.outputs.package_revision }} + shell: bash + run: | + set -euo pipefail + current="$ORT_VERSION-r$PACKAGE_REVISION" + highest="$( + gh api --paginate "repos/$GITHUB_REPOSITORY/releases" \ + --jq '.[] | select(.draft == false and .prerelease == false) | .tag_name' | + sed -nE 's/^ort-([0-9]+\.[0-9]+\.[0-9]+-r[1-9][0-9]*)$/\1/p' | + sort -V | + tail -1 + )" + latest=false + if [ -z "$highest" ] || [ "$(printf '%s\n%s\n' "$highest" "$current" | sort -V | tail -1)" = "$current" ]; then + latest=true + fi + echo "latest=$latest" >> "$GITHUB_OUTPUT" + + - name: Publish immutable GitHub release + env: + BUILD_COMMIT: ${{ steps.candidate.outputs.packaging_commit }} + CANDIDATE_DIR: ${{ runner.temp }}/release-candidate + GH_TOKEN: ${{ github.token }} + LATEST: ${{ steps.latest.outputs.latest }} + RELEASE_TAG: ${{ steps.candidate.outputs.release_tag }} + shell: bash + run: | + set -euo pipefail + latest_flag=--latest=false + if [ "$LATEST" = true ]; then latest_flag=--latest; fi + notes="$(mktemp)" + jq -r .release_notes "$CANDIDATE_DIR/build-provenance.json" > "$notes" + + [ -z "$(git ls-remote "https://github.com/$GITHUB_REPOSITORY.git" "refs/tags/$RELEASE_TAG")" ] || { + echo "tag $RELEASE_TAG already exists" >&2 + exit 1 + } + gh release create "$RELEASE_TAG" "$CANDIDATE_DIR"/* \ + --target "$BUILD_COMMIT" \ + --title "$RELEASE_TAG" \ + --notes-file "$notes" \ + "$latest_flag" + + gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" \ + --jq 'if .draft == false and .prerelease == false and .immutable == true and (.assets | length) == 35 then empty else error("published release is not immutable or has the wrong asset count") end' + [ "$(git ls-remote "https://github.com/$GITHUB_REPOSITORY.git" "refs/tags/$RELEASE_TAG" | awk '{ print $1 }')" = "$BUILD_COMMIT" ] diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 52495fd..b9fd6b8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -24,28 +24,9 @@ jobs: shellcheck -x -P scripts scripts/*.sh for script in scripts/*.sh; do bash -n "$script"; done jq --exit-status . config/*.json >/dev/null - source versions.env + source build.env [ "$ORT_TELEMETRY" = disabled ] jq --exit-status ".build_params | index(\"--no_telemetry\") != null" config/android-webgpu.json >/dev/null jq --exit-status ".build_params.base | index(\"--no_telemetry\") != null" config/ios-coreml.json >/dev/null # shellcheck disable=SC2016 pwsh -NoProfile -Command '$errors = $null; Get-ChildItem scripts/*.ps1 | ForEach-Object { [System.Management.Automation.Language.Parser]::ParseFile($_.FullName, [ref]$null, [ref]$errors) | Out-Null }; if ($errors.Count) { $errors | Format-List; exit 1 }' - - - name: Verify pinned ONNX Runtime patches - shell: bash - run: | - set -euo pipefail - git clone --depth 1 --branch "$(. versions.env; printf '%s' "$ORT_REF")" \ - https://github.com/microsoft/onnxruntime.git "$RUNNER_TEMP/onnxruntime" - for patch in "$GITHUB_WORKSPACE"/patches/*.patch; do - git -C "$RUNNER_TEMP/onnxruntime" apply --check "$patch" - git -C "$RUNNER_TEMP/onnxruntime" apply "$patch" - done - - - name: Compile portable runtime smoke test - shell: bash - run: | - set -euo pipefail - cmake -S tests -B "$RUNNER_TEMP/runtime-smoke" \ - -DORT_INCLUDE_DIR="$RUNNER_TEMP/onnxruntime/include/onnxruntime/core/session" - cmake --build "$RUNNER_TEMP/runtime-smoke" --parallel diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 8c1ca48..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,35 +0,0 @@ -# Changelog - -## 1.29.0-r1 — 2026-08-12 - -- Update the pinned upstream runtime to ONNX Runtime `v1.29.0` (`2e2543fbe9fae542f921d47a72d21d5a4ef0b710`). -- Include the upstream 1.29 security hardening, runtime fixes, and WebGPU execution-provider improvements. -- Compile telemetry out of every package and fail builds or publishing if telemetry is enabled. -- Keep Windows ARM64 warning validation strict without depending on upstream source line numbers. -- Revalidate the CoreML runtime-availability and public-vcpkg patches against the new upstream tag. -- Build every supported target from source for the first stable 1.29 packaging revision. - -## 1.28.0-r1 — 2026-07-28 - -- Update the pinned upstream runtime to ONNX Runtime `v1.28.0` (`da9b5e364c465de65c49d91e696cd6485270757f`). -- Include the upstream 1.28 security and runtime fixes and WebGPU plugin EP 0.3.0 update. -- Revalidate the CoreML runtime-availability and public-vcpkg patches against the new upstream tag. -- Build every supported target from source for the first stable 1.28 packaging revision. - -## 1.27.0-r3 — 2026-07-27 - -- Package iOS as one static-library XCFramework whose device and ARM64 Simulator slices each contain `libonnxruntime.a` and public headers. -- Remove the duplicate framework-wrapped archives and top-level `static-lib` copies from the iOS ZIP. -- Add a provenance-preserving path for packaging-only releases to reuse verified binaries from an immutable source release without recompiling them. - -## 1.27.0-r2 — 2026-07-20 - -- Add pre-thinned device and ARM64 Simulator `libonnxruntime.a` archives to the iOS ZIP for Rust and other consumers that cannot link the XCFramework directly. -- Run the iOS Simulator CPU and CoreML inference smoke test against the packaged pre-thinned archive. - -## 1.27.0-r1 — 2026-07-20 - -- First stable custom packaging release derived from ONNX Runtime `v1.27.0`. -- Android uses built-in WebGPU/XNNPACK/CPU; Linux and Windows use a shared WebGPU plugin; iOS and macOS use built-in CoreML/CPU. -- Native CI verifies packaged runtime inference, symbol/debug-data constraints, package licences, checksums, manifests, and release provenance. -- Stable releases use immutable `r` tags and assets rather than pilot labels. diff --git a/PATCHES.md b/PATCHES.md index ae1e729..f8896f8 100644 --- a/PATCHES.md +++ b/PATCHES.md @@ -1,6 +1,6 @@ # Upstream patch inventory -Every patch in `patches/` is applied to the exact upstream tag in `versions.env` by both CI validation and platform builds. Revalidate each patch when changing the upstream ONNX Runtime version. +Every patch in `patches/` is applied to the exact upstream commit resolved by the release-candidate workflow. Its preflight job verifies the patches and compiles the portable smoke test against the requested ONNX Runtime release before any platform build starts. ## `onnxruntime-coreml-runtime-availability.patch` @@ -12,8 +12,8 @@ This patch makes CoreML availability explicit at runtime for the APIs that are n Instead of relying on an unguarded availability call, the runtime returns a descriptive ONNX Runtime error on an older OS. The patch also propagates that error from model loading. It is required to support the documented iOS 15.1 deployment target safely. -Remove this patch only when the selected upstream ONNX Runtime version contains equivalent runtime guards. The release workflow rejects builds that emit unguarded-availability warnings, and `Validate` checks that the patch still applies cleanly. +Remove this patch only when the selected upstream ONNX Runtime version contains equivalent runtime guards. Apple platform builds reject unguarded-availability warnings, and the release-candidate preflight checks that the patch still applies cleanly to the requested upstream version. ## `onnxruntime-public-vcpkg.patch` -This existing build-system patch keeps the required vcpkg integration reviewable and reproducible for the pinned upstream source. Its applicability is likewise checked by `Validate` before any release is built. +This existing build-system patch keeps the required vcpkg integration reviewable and reproducible for the resolved upstream source. Its applicability is likewise checked by the release-candidate preflight before any platform build starts. diff --git a/README.md b/README.md index 17e0e49..9035de1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,67 @@ # Custom ONNX Runtime Packaging -Builds pinned, self-contained ONNX Runtime runtime packages for Android, iOS, Linux, macOS, and Windows without consuming Microsoft-provided ONNX Runtime binaries. The current upstream base is **ONNX Runtime 1.29.0** from `v1.29.0`; package-channel and toolchain pins live in [`versions.env`](versions.env). +Builds pinned, self-contained ONNX Runtime runtime packages for Android, iOS, Linux, macOS, and Windows without consuming Microsoft-provided ONNX Runtime binaries. Shared toolchain and platform policy lives in [`build.env`](build.env); each release build resolves its requested upstream tag to an immutable commit. -This repository's release tags describe a **custom packaging** of upstream ONNX Runtime, not an upstream Microsoft release. A stable package revision is named `ort--r`; the first stable 1.29 release is therefore `ort-1.29.0-r1`. +This repository's release tags describe a **custom packaging** of upstream ONNX Runtime, not an upstream Microsoft release. Stable package revisions are named `ort--r`. See [Releases](https://github.com/ente/ort-packaging/releases) for available versions and assets. + +## Release process + +A routine ONNX Runtime release requires no repository changes. Do not edit `build.env` to select an ONNX Runtime version; change it only when the shared build policy or toolchain must change. + +Prerequisites: + +- The exact packaging code to release is on `main`. +- The **Validate** workflow for that `main` commit succeeded. + +### 1. Build a release candidate + +Open [Build release candidate](https://github.com/ente/ort-packaging/actions/workflows/build.yml), select **Run workflow**, and use: + +- **Branch:** `main` +- **ORT version:** the stable upstream version without a `v` prefix, such as `1.28.1` +- **Package revision:** `1` for the first package of that ORT version; use the next integer only to correct the packaging. Revisions must be sequential. + +The equivalent CLI command is: + +```sh +gh workflow run build.yml \ + --repo ente/ort-packaging \ + --ref main \ + -f ort_version=1.28.1 \ + -f package_revision=1 +``` + +The workflow verifies the upstream release, resolves its tag to a commit, applies the repository patches, and builds and tests every package. The candidate is ready only when **Assemble release candidate** succeeds. Candidates expire after 14 days. + +### 2. Complete physical-device validation + +Before publishing, test Android WebGPU on representative supported physical devices and validate the iOS device slice and vendor-GPU behavior. Hosted CI validates package structure and runtime inference, but it cannot replace these device checks. + +### 3. Publish the candidate + +Copy the numeric run ID from the successful candidate build. Open [Publish release](https://github.com/ente/ort-packaging/actions/workflows/publish.yml), select **Run workflow** on `main`, and enter that ID. + +The equivalent CLI command is: + +```sh +gh workflow run publish.yml \ + --repo ente/ort-packaging \ + --ref main \ + -f build_run_id=123456789 +``` + +Publication accepts only a successful `build.yml` run from this repository's `main` branch. It revalidates the candidate provenance, checksums, upstream commit, package revision, and target tag before creating an immutable release from the exact packaging commit that produced the candidate. If the candidate expired, run a new build instead. + +### 4. Verify the published release + +Confirm that the release: + +- uses the tag `ort--r`; +- is immutable and contains 35 assets; +- includes 11 packages, their 11 checksum sidecars, their 11 manifests, `SHA256SUMS`, and `build-provenance.json`; +- passes `sha256sum -c SHA256SUMS` after download. + +Never replace or retag an existing release. Create the next package revision instead. ## Packages @@ -16,7 +75,7 @@ This repository's release tags describe a **custom packaging** of upstream ONNX The provider in an asset name is intentional: Android/Linux/Windows support WebGPU, while Apple packages intentionally use CoreML rather than WebGPU. Do not mix a packaged runtime, plugin, or headers with another ONNX Runtime build. -Telemetry is compiled out of every package. Build entrypoints pass ONNX Runtime's `--no_telemetry` option and reject generated CMake configurations unless `onnxruntime_USE_TELEMETRY=OFF`, so an upstream default change cannot silently enable telemetry in a future release. +Telemetry is compiled out of every package. Platform build jobs pass ONNX Runtime's `--no_telemetry` option and reject generated CMake configurations unless `onnxruntime_USE_TELEMETRY=OFF`, so an upstream default change cannot silently enable telemetry in a future release. ## Consumer requirements @@ -32,33 +91,20 @@ For Linux and Windows, register the shared WebGPU plugin and select an EP device ## Repository layout -- `versions.env`: upstream version, release channel/revision, and toolchain pins +- `build.env`: shared toolchain, deployment-target, ABI, and telemetry policy - `config/`: Android and iOS build configurations -- `scripts/`: platform build, packaging, and packaged-runtime smoke-test entrypoints +- `scripts/`: workflow implementation helpers for platform builds, packaging, and packaged-runtime smoke tests - `patches/`: reviewable upstream compatibility patches; see [`PATCHES.md`](PATCHES.md) - `tests/`: portable packaged-runtime inference smoke tests - [`RELEASE-CHECKLIST.md`](RELEASE-CHECKLIST.md): required stable-release promotion steps -- `.github/workflows/release.yml`: native build and immutable-release matrix - -Local scripts write to `build/` and `dist/` by default. CI sets the equivalent hidden directories `.build/` and `.dist/`; all four are ignored by Git. +- `.github/workflows/build.yml`: version-driven native build and release-candidate assembly +- `.github/workflows/publish.yml`: immutable publication of one successful build run -## CI and releases +The build and smoke-test helpers under `scripts/` are workflow implementation details, not supported standalone release commands. The release workflow supplies their resolved metadata and writes to `.build/` and `.dist/`; local fallback directories `build/` and `dist/` are also ignored by Git. -The release workflow builds every target on its native GitHub-hosted runner. Linux compilation runs in checksum-pinned PyPA `manylinux_2_28` job containers on native x64 and ARM64 hosts, decoupling the package ABI from the GitHub runner image. Android ABIs are built independently and merged into a universal AAR. iOS is distributed for ARM64 devices and Apple Silicon Simulator hosts; Intel Simulator hosts are not supported. Its XCFramework contains ordinary `libonnxruntime.a` slices and public headers, so Swift Package Manager and consumers that link the archive directly use the same single copy of the machine code. +## Build and validation design -`versions.env` is the release source of truth. The workflow derives the required tag from `ORT_VERSION`, `PACKAGE_CHANNEL`, and `PACKAGE_REVISION`; a release rejects a mismatched tag, a mismatched prerelease flag, or any ref other than `main` before starting platform builds. - -Dispatch every release from `main` with the computed tag and prerelease setting. Every run builds and tests all targets from source before publishing. For example: - -```sh -gh workflow run release.yml \ - --repo ente/ort-packaging \ - --ref main \ - -f tag=ort-1.29.0-r1 \ - -f prerelease=false -``` - -GitHub release immutability must be enabled under **Settings → General → Releases**. The workflow rejects an existing tag and verifies that the completed release is immutable with the expected asset count. +The build workflow builds every target on its native GitHub-hosted runner. Linux compilation runs in checksum-pinned PyPA `manylinux_2_28` job containers on native x64 and ARM64 hosts, decoupling the package ABI from the GitHub runner image. Android ABIs are built independently and merged into a universal AAR. iOS is distributed for ARM64 devices and Apple Silicon Simulator hosts; Intel Simulator hosts are not supported. Its XCFramework contains ordinary `libonnxruntime.a` slices and public headers, so Swift Package Manager and consumers that link the archive directly use the same single copy of the machine code. ## Verification and provenance @@ -66,19 +112,7 @@ Every binary asset includes a `.sha256` checksum and `.manifest.env` file. Manif Custom-built binaries are stripped of or packaged without debug-symbol data. Linux and Android builds additionally reject debug/symbol-table sections and executable stacks. Archives contain the ONNX Runtime licence and third-party notices; Android AARs store them under `META-INF/`. Windows archives include the licence files shipped with the pinned DXC runtime. -Example: - -```sh -tag=ort-1.29.0-r1 -asset=onnxruntime-webgpu-android-1.29.0-r1.aar -base=https://github.com/ente/ort-packaging/releases/download/$tag - -curl -fL -o "$asset" "$base/$asset" -curl -fL -o "$asset.sha256" "$base/$asset.sha256" -printf '%s %s\n' "$(cat "$asset.sha256")" "$asset" | sha256sum -c - -``` - -Always pin a release tag and checksum downstream. +Always pin a release tag and verify its checksum sidecar downstream. ## Validation scope diff --git a/RELEASE-CHECKLIST.md b/RELEASE-CHECKLIST.md index c7efc82..0fec73c 100644 --- a/RELEASE-CHECKLIST.md +++ b/RELEASE-CHECKLIST.md @@ -1,11 +1,23 @@ # Stable release checklist -Use this checklist for a non-prerelease package release. Pilot tags remain immutable evidence and are never renamed or replaced. - -1. Update `versions.env` with the intended upstream tag, `PACKAGE_CHANNEL=stable`, and a new `PACKAGE_REVISION`. The computed label is `r` and the required release tag is `ort--r`. -2. Ensure the packaging branch is merged into `main` through a PR with the `Validate / scripts` check passing. -3. Confirm representative-device correctness and performance evidence, including Android WebGPU on the supported physical-device matrix. -4. Dispatch `release.yml` from `main` with the computed tag and `prerelease=false`. Every release builds and tests all targets from source. -5. Monitor every native build and the publish job. A successful publish verifies that telemetry is disabled, that the release is immutable, and that it has 35 assets: 11 binaries, 11 checksums, 11 manifests, `SHA256SUMS`, and `build-provenance.json`. -6. Independently download the release, verify `SHA256SUMS`, confirm `ORT_TELEMETRY=disabled` in every manifest and provenance, and retain the device-test record with the release notes. -7. If a packaging correction is needed, increment `PACKAGE_REVISION` and create the next immutable `r` tag. Never replace or retag an existing release. +Follow the canonical procedure in [README.md](README.md#release-process). + +Before building: + +- [ ] The exact packaging code to release is on `main`. +- [ ] The **Validate** workflow for that `main` commit succeeded. + +Before publishing: + +- [ ] **Assemble release candidate** succeeded for the intended ORT version and package revision. +- [ ] Android WebGPU passed on representative supported physical devices. +- [ ] The iOS device slice and vendor-GPU behavior were validated. +- [ ] The candidate build is less than 14 days old. + +After publishing: + +- [ ] The release tag is correct, the release is immutable, and all 35 assets are present. +- [ ] `SHA256SUMS` verifies every package. +- [ ] Every manifest and `build-provenance.json` records `ORT_TELEMETRY=disabled`. + +Never replace or retag an existing release. Create the next package revision instead. diff --git a/versions.env b/build.env similarity index 81% rename from versions.env rename to build.env index ea9e4e4..37efc17 100644 --- a/versions.env +++ b/build.env @@ -1,8 +1,4 @@ -ORT_REF=v1.29.0 -ORT_VERSION=1.29.0 ORT_TELEMETRY=disabled -PACKAGE_CHANNEL=stable -PACKAGE_REVISION=1 PYTHON_VERSION=3.12 NODE_VERSION=20 ANDROID_NDK_VERSION=28.0.13004108 diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 1f69f4e..9917eb8 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -42,7 +42,7 @@ actual = settings.get("android_min_sdk_version") expected = int(sys.argv[2]) if actual != expected: raise SystemExit( - f"Android build configuration uses minSdk {actual}; expected {expected} from versions.env" + f"Android build configuration uses minSdk {actual}; expected {expected} from build.env" ) if "--no_telemetry" not in settings.get("build_params", []): raise SystemExit("Android build configuration must disable telemetry") diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index e31dab2..6f08fbe 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -2,12 +2,18 @@ $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $RepoRoot = Split-Path -Parent $PSScriptRoot -$versions = @{} -Get-Content (Join-Path $RepoRoot "versions.env") | ForEach-Object { - if ($_ -match '^([^#=]+)=(.*)$') { $versions[$Matches[1]] = $Matches[2] } +$buildConfig = @{} +Get-Content (Join-Path $RepoRoot "build.env") | ForEach-Object { + if ($_ -match '^([^#=]+)=(.*)$') { $buildConfig[$Matches[1]] = $Matches[2] } } -if ($versions.ORT_TELEMETRY -ne "disabled") { throw "ORT_TELEMETRY must be disabled" } +if ($buildConfig.ORT_TELEMETRY -ne "disabled") { throw "ORT_TELEMETRY must be disabled" } +if ($env:ORT_VERSION -notmatch '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$') { + throw "ORT_VERSION must be MAJOR.MINOR.PATCH" +} +if ($env:ORT_REF -ne "v$($env:ORT_VERSION)") { throw "ORT_REF must be v$($env:ORT_VERSION)" } +if ($env:ORT_COMMIT -notmatch '^[0-9a-f]{40}$') { throw "ORT_COMMIT must be a full commit SHA" } +if ($env:PACKAGE_REVISION -notmatch '^[1-9][0-9]*$') { throw "PACKAGE_REVISION must be a positive integer" } function Assert-OrtTelemetryDisabled { param([string]$Root) @@ -33,11 +39,7 @@ if ($target -notin @("windows-x64", "windows-arm64")) { $ortSource = if ($env:ORT_SOURCE_DIR) { $env:ORT_SOURCE_DIR } else { Join-Path $RepoRoot "onnxruntime" } $buildRoot = if ($env:BUILD_ROOT) { $env:BUILD_ROOT } else { Join-Path $RepoRoot "build" } $distRoot = if ($env:DIST_ROOT) { $env:DIST_ROOT } else { Join-Path $RepoRoot "dist" } -$packageLabel = switch ($versions.PACKAGE_CHANNEL) { - "pilot" { "pilot.$($versions.PACKAGE_REVISION)"; break } - "stable" { "r$($versions.PACKAGE_REVISION)"; break } - default { throw "unsupported PACKAGE_CHANNEL: $($versions.PACKAGE_CHANNEL)" } -} +$packageLabel = "r$($env:PACKAGE_REVISION)" $buildDir = Join-Path $buildRoot $target $distDir = Join-Path $distRoot $target $packageDir = Join-Path $distDir "package" @@ -55,8 +57,8 @@ if ($hostArch -ne $requiredHostArch) { throw "$target must build on a native $requiredHostArch runner; found $hostArch" } -$actualRef = git -C $ortSource describe --tags --exact-match -if ($actualRef -ne $versions.ORT_REF) { throw "expected ORT $($versions.ORT_REF), found $actualRef" } +$actualCommit = git -C $ortSource rev-parse HEAD +if ($actualCommit -ne $env:ORT_COMMIT) { throw "expected ORT $($env:ORT_COMMIT), found $actualCommit" } git -C $ortSource apply --reverse --check (Join-Path $RepoRoot "patches/onnxruntime-public-vcpkg.patch") 2>$null if ($LASTEXITCODE -ne 0) { git -C $ortSource apply (Join-Path $RepoRoot "patches/onnxruntime-public-vcpkg.patch") @@ -147,11 +149,11 @@ if (Get-ChildItem $packageDir -Filter "*.pdb" -Recurse) { # depending on incidental DLL placement in Dawn's build tree. $dxcArchive = Join-Path $buildDir "dxc.zip" $dxcExtract = Join-Path $buildDir "dxc" -$dxcUrl = "https://github.com/microsoft/DirectXShaderCompiler/releases/download/$($versions.DXC_VERSION)/dxc_2025_02_20.zip" +$dxcUrl = "https://github.com/microsoft/DirectXShaderCompiler/releases/download/$($buildConfig.DXC_VERSION)/dxc_2025_02_20.zip" Invoke-WebRequest -Uri $dxcUrl -OutFile $dxcArchive $dxcHash = (Get-FileHash $dxcArchive -Algorithm SHA256).Hash -if ($dxcHash -ne $versions.DXC_ARCHIVE_SHA256) { - throw "DXC archive hash mismatch: expected $($versions.DXC_ARCHIVE_SHA256), found $dxcHash" +if ($dxcHash -ne $buildConfig.DXC_ARCHIVE_SHA256) { + throw "DXC archive hash mismatch: expected $($buildConfig.DXC_ARCHIVE_SHA256), found $dxcHash" } Expand-Archive -Path $dxcArchive -DestinationPath $dxcExtract -Force $dxcArch = if ($target -eq "windows-arm64") { "arm64" } else { "x64" } @@ -180,13 +182,13 @@ $commit = git -C $ortSource rev-parse HEAD $packagingCommit = git -C $RepoRoot rev-parse HEAD $utf8NoBom = [System.Text.UTF8Encoding]::new($false) $manifestText = (@( - "ORT_REF=$($versions.ORT_REF)", - "ORT_VERSION=$($versions.ORT_VERSION)", - "ORT_TELEMETRY=$($versions.ORT_TELEMETRY)", + "ORT_REF=$($env:ORT_REF)", + "ORT_VERSION=$($env:ORT_VERSION)", + "ORT_TELEMETRY=$($buildConfig.ORT_TELEMETRY)", "ORT_COMMIT=$commit", "PACKAGING_COMMIT=$packagingCommit", - "PACKAGE_CHANNEL=$($versions.PACKAGE_CHANNEL)", - "PACKAGE_REVISION=$($versions.PACKAGE_REVISION)", + "PACKAGE_CHANNEL=stable", + "PACKAGE_REVISION=$($env:PACKAGE_REVISION)", "PACKAGE_LABEL=$packageLabel", "TARGET=$target", "ORT_CORE_INCLUDED=1", @@ -197,7 +199,7 @@ $manifestText = (@( $packageManifest = Join-Path $packageDir "manifest.env" [System.IO.File]::WriteAllText($packageManifest, $manifestText, $utf8NoBom) -$assetName = "onnxruntime-webgpu-$target-$($versions.ORT_VERSION)-$packageLabel.zip" +$assetName = "onnxruntime-webgpu-$target-$($env:ORT_VERSION)-$packageLabel.zip" $asset = Join-Path $distDir $assetName Compress-Archive -Path (Join-Path $packageDir "*") -DestinationPath $asset $hash = (Get-FileHash $asset -Algorithm SHA256).Hash.ToLowerInvariant() diff --git a/scripts/common.sh b/scripts/common.sh index a4f0b46..c6957b9 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -6,9 +6,23 @@ export REPO_ROOT="${REPO_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}" set -a # shellcheck disable=SC1091 -source "$REPO_ROOT/versions.env" +source "$REPO_ROOT/build.env" set +a +: "${ORT_REF:?ORT_REF must identify the upstream release tag}" +: "${ORT_VERSION:?ORT_VERSION must be MAJOR.MINOR.PATCH}" +: "${ORT_COMMIT:?ORT_COMMIT must be the resolved upstream commit}" +: "${PACKAGE_REVISION:?PACKAGE_REVISION must be a positive integer}" +[[ "$ORT_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || + { printf 'error: invalid ORT_VERSION: %s\n' "$ORT_VERSION" >&2; exit 1; } +[ "$ORT_REF" = "v$ORT_VERSION" ] || + { printf 'error: ORT_REF must be v%s; found %s\n' "$ORT_VERSION" "$ORT_REF" >&2; exit 1; } +[[ "$ORT_COMMIT" =~ ^[0-9a-f]{40}$ ]] || + { printf 'error: invalid ORT_COMMIT: %s\n' "$ORT_COMMIT" >&2; exit 1; } +[[ "$PACKAGE_REVISION" =~ ^[1-9][0-9]*$ ]] || + { printf 'error: invalid PACKAGE_REVISION: %s\n' "$PACKAGE_REVISION" >&2; exit 1; } +export PACKAGE_CHANNEL=stable + export ORT_SOURCE_DIR="${ORT_SOURCE_DIR:-$REPO_ROOT/onnxruntime}" export BUILD_ROOT="${BUILD_ROOT:-$REPO_ROOT/build}" export DIST_ROOT="${DIST_ROOT:-$REPO_ROOT/dist}" @@ -72,11 +86,7 @@ write_checksum() { } package_label() { - case "$PACKAGE_CHANNEL" in - pilot) printf 'pilot.%s' "$PACKAGE_REVISION" ;; - stable) printf 'r%s' "$PACKAGE_REVISION" ;; - *) die "unsupported PACKAGE_CHANNEL: $PACKAGE_CHANNEL" ;; - esac + printf 'r%s' "$PACKAGE_REVISION" } package_release_tag() { @@ -92,7 +102,7 @@ ort_commit() { printf '%s\n' "$ORT_COMMIT_OVERRIDE" return fi - git -C "$ORT_SOURCE_DIR" rev-parse HEAD + printf '%s\n' "$ORT_COMMIT" } write_manifest() { @@ -131,9 +141,10 @@ write_manifest() { prepare_ort_source() { require_dir "$ORT_SOURCE_DIR/.git" - local actual_ref - actual_ref="$(git -C "$ORT_SOURCE_DIR" describe --tags --exact-match 2>/dev/null || true)" - [ "$actual_ref" = "$ORT_REF" ] || die "expected ORT source at $ORT_REF, found ${actual_ref:-untagged commit}" + local actual_commit + actual_commit="$(git -C "$ORT_SOURCE_DIR" rev-parse HEAD)" + [ "$actual_commit" = "$ORT_COMMIT" ] || + die "expected ORT source at $ORT_COMMIT, found $actual_commit" local patch for patch in "$REPO_ROOT"/patches/*.patch; do diff --git a/scripts/repackage-release.sh b/scripts/repackage-release.sh deleted file mode 100755 index 1b9a833..0000000 --- a/scripts/repackage-release.sh +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=common.sh -source "$SCRIPT_DIR/common.sh" - -if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then - die "usage: $0 [all|non-linux]" -fi - -source_dir="$1" -source_tag="$2" -repackage_scope="${3:-all}" -destination_label="$(package_label)" -destination_tag="$(package_release_tag)" -source_prefix="ort-$ORT_VERSION-" - -case "$repackage_scope" in - all) expected_binary_count=11 ;; - non-linux) expected_binary_count=9 ;; - *) die "unsupported repackage scope: $repackage_scope" ;; -esac - -require_cmd cmp -require_cmd python3 -require_cmd tar -require_cmd unzip -require_cmd zip -require_dir "$source_dir" -require_file "$source_dir/SHA256SUMS" -require_file "$source_dir/build-provenance.json" - -case "$source_tag" in - "$source_prefix"*) source_label="${source_tag#"$source_prefix"}" ;; - *) die "source tag $source_tag does not contain ORT version $ORT_VERSION" ;; -esac -[ "$source_tag" != "$destination_tag" ] || die "source and destination release tags must differ" -[ -n "$source_label" ] || die "source package label is empty" - -python3 - "$source_dir/build-provenance.json" "$source_tag" "$ORT_REF" "$ORT_VERSION" <<'PY' -import json -import sys - -with open(sys.argv[1], encoding="utf-8") as provenance_file: - provenance = json.load(provenance_file) - -expected = { - "release_tag": sys.argv[2], - "upstream_ort_ref": sys.argv[3], - "upstream_ort_version": sys.argv[4], - "ort_telemetry": "disabled", -} -for key, value in expected.items(): - if provenance.get(key) != value: - raise SystemExit( - f"source provenance has {key}={provenance.get(key)!r}; expected {value!r}" - ) -if not provenance.get("packaging_commit"): - raise SystemExit("source provenance is missing packaging_commit") -PY - -source_packaging_commit="$(python3 - "$source_dir/build-provenance.json" <<'PY' -import json -import sys - -with open(sys.argv[1], encoding="utf-8") as provenance_file: - print(json.load(provenance_file)["packaging_commit"]) -PY -)" - -dist_dir="$DIST_ROOT/repackaged" -work_dir="$BUILD_ROOT/repackage" -case "$dist_dir" in "$DIST_ROOT"/*) ;; *) die "unsafe repackaged dist path: $dist_dir" ;; esac -case "$work_dir" in "$BUILD_ROOT"/*) ;; *) die "unsafe repackaging work path: $work_dir" ;; esac -rm -rf "$dist_dir" "$work_dir" -mkdir -p "$dist_dir" "$work_dir" - -source_names=( - "onnxruntime-coreml-ios-$ORT_VERSION-$source_label.zip" - "onnxruntime-coreml-macos-arm64-$ORT_VERSION-$source_label.tar.gz" - "onnxruntime-coreml-macos-x64-$ORT_VERSION-$source_label.tar.gz" - "onnxruntime-webgpu-android-$ORT_VERSION-$source_label.aar" - "onnxruntime-webgpu-android-arm64-v8a-$ORT_VERSION-$source_label.aar" - "onnxruntime-webgpu-android-armeabi-v7a-$ORT_VERSION-$source_label.aar" - "onnxruntime-webgpu-android-x86_64-$ORT_VERSION-$source_label.aar" - "onnxruntime-webgpu-linux-arm64-$ORT_VERSION-$source_label.tar.gz" - "onnxruntime-webgpu-linux-x64-$ORT_VERSION-$source_label.tar.gz" - "onnxruntime-webgpu-windows-arm64-$ORT_VERSION-$source_label.zip" - "onnxruntime-webgpu-windows-x64-$ORT_VERSION-$source_label.zip" -) - -if [ "$repackage_scope" = "non-linux" ]; then - non_linux_source_names=() - for source_name in "${source_names[@]}"; do - case "$source_name" in - onnxruntime-webgpu-linux-*) ;; - *) non_linux_source_names+=("$source_name") ;; - esac - done - source_names=("${non_linux_source_names[@]}") -fi - -verify_source_asset() { - local source_asset="$1" - local source_manifest="$source_asset.manifest.env" - local source_checksum="$source_asset.sha256" - require_file "$source_asset" - require_file "$source_manifest" - require_file "$source_checksum" - - local expected actual - expected="$(tr -d '[:space:]' <"$source_checksum")" - [[ "$expected" =~ ^[0-9a-f]{64}$ ]] || die "invalid source checksum: $source_checksum" - actual="$(sha256_file "$source_asset")" - [ "$actual" = "$expected" ] || die "source checksum mismatch: $source_asset" - grep -Fqx "$expected ./$(basename "$source_asset")" "$source_dir/SHA256SUMS" || - die "source SHA256SUMS does not contain $(basename "$source_asset")" - grep -Fqx "ORT_REF=$ORT_REF" "$source_manifest" - grep -Fqx "ORT_VERSION=$ORT_VERSION" "$source_manifest" - grep -Fqx "ORT_TELEMETRY=$ORT_TELEMETRY" "$source_manifest" - grep -Fqx "PACKAGING_COMMIT=$source_packaging_commit" "$source_manifest" - grep -Fqx "PACKAGE_LABEL=$source_label" "$source_manifest" - grep -Fqx 'ORT_CORE_INCLUDED=1' "$source_manifest" -} - -manifest_value() { - local manifest="$1" - local key="$2" - awk -F= -v key="$key" '$1 == key { sub(/^[^=]*=/, ""); print; found += 1 } END { if (found != 1) exit 1 }' "$manifest" -} - -for source_name in "${source_names[@]}"; do - source_asset="$source_dir/$source_name" - source_manifest="$source_asset.manifest.env" - verify_source_asset "$source_asset" - - destination_name="${source_name/-$source_label./-$destination_label.}" - destination_asset="$dist_dir/$destination_name" - source_sha256="$(sha256_file "$source_asset")" - target="$(manifest_value "$source_manifest" TARGET)" - linkage="$(manifest_value "$source_manifest" WEBGPU_LINKAGE)" - providers="$(manifest_value "$source_manifest" EXECUTION_PROVIDERS)" - ORT_COMMIT_OVERRIDE="$(manifest_value "$source_manifest" ORT_COMMIT)" - REPACKAGE_SOURCE_TAG="$source_tag" - REPACKAGE_SOURCE_ASSET="$source_name" - REPACKAGE_SOURCE_SHA256="$source_sha256" - - case "$source_name" in - *.aar) - cp "$source_asset" "$destination_asset" - [ "$(sha256_file "$destination_asset")" = "$source_sha256" ] || - die "Android archive changed while being copied: $source_name" - write_manifest "$destination_asset.manifest.env" "$target" "$linkage" "$providers" - ;; - *-ios-*.zip) - package_dir="$work_dir/ios-package" - source_package_dir="$work_dir/ios-source" - mkdir -p "$package_dir" "$source_package_dir" - unzip -q "$source_asset" -d "$source_package_dir" - cmp "$source_manifest" "$source_package_dir/manifest.env" || - die "source iOS internal and external manifests differ" - - require_dir "$source_package_dir/onnxruntime.xcframework" - static_library_root="$source_package_dir/static-lib" - if [ ! -d "$static_library_root" ]; then - static_library_root="$source_package_dir/onnxruntime.xcframework" - fi - cp "$source_package_dir/ONNXRUNTIME-LICENSE" "$package_dir/" - [ ! -f "$source_package_dir/ThirdPartyNotices.txt" ] || - cp "$source_package_dir/ThirdPartyNotices.txt" "$package_dir/" - [ ! -f "$source_package_dir/xcframework_info.json" ] || - cp "$source_package_dir/xcframework_info.json" "$package_dir/" - "$SCRIPT_DIR/create-ios-static-xcframework.sh" \ - "$source_package_dir/onnxruntime.xcframework" \ - "$static_library_root" \ - "$package_dir/onnxruntime.xcframework" - write_manifest "$package_dir/manifest.env" "$target" "$linkage" "$providers" - ( - cd "$package_dir" - COPYFILE_DISABLE=1 zip -qry "$destination_asset" . - ) - cp "$package_dir/manifest.env" "$destination_asset.manifest.env" - ;; - *.tar.gz) - package_dir="$work_dir/${source_name%.tar.gz}" - mkdir -p "$package_dir" - tar -xzf "$source_asset" -C "$package_dir" - cmp "$source_manifest" "$package_dir/manifest.env" || - die "source tar internal and external manifests differ: $source_name" - write_manifest "$package_dir/manifest.env" "$target" "$linkage" "$providers" - COPYFILE_DISABLE=1 tar -C "$package_dir" -czf "$destination_asset" . - cp "$package_dir/manifest.env" "$destination_asset.manifest.env" - ;; - *.zip) - package_dir="$work_dir/${source_name%.zip}" - mkdir -p "$package_dir" - unzip -q "$source_asset" -d "$package_dir" - cmp "$source_manifest" "$package_dir/manifest.env" || - die "source ZIP internal and external manifests differ: $source_name" - write_manifest "$package_dir/manifest.env" "$target" "$linkage" "$providers" - ( - cd "$package_dir" - COPYFILE_DISABLE=1 zip -qry "$destination_asset" . - ) - cp "$package_dir/manifest.env" "$destination_asset.manifest.env" - ;; - *) die "unsupported source asset: $source_name" ;; - esac - - write_checksum "$destination_asset" - log "repackaged $source_name as $destination_name" -done - -expected_file_count=$((expected_binary_count * 3)) -[ "$(find "$dist_dir" -maxdepth 1 -type f | wc -l | tr -d '[:space:]')" -eq "$expected_file_count" ] || - die "expected $expected_file_count repackaged asset files" -log "repackaged $expected_binary_count binary assets from $source_tag without native recompilation" diff --git a/scripts/resolve-release.sh b/scripts/resolve-release.sh new file mode 100755 index 0000000..2d37fbc --- /dev/null +++ b/scripts/resolve-release.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +die() { + printf 'error: %s\n' "$*" >&2 + exit 1 +} + +[ "$#" -eq 2 ] || die "usage: $0 " +ort_version="$1" +package_revision="$2" +[[ "$ort_version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || + die "ORT version must be MAJOR.MINOR.PATCH" +[[ "$package_revision" =~ ^[1-9][0-9]*$ ]] || + die "package revision must be a positive integer" +[ -n "${GITHUB_REPOSITORY:-}" ] || die "GITHUB_REPOSITORY is required" +[[ "$GITHUB_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || + die "invalid GITHUB_REPOSITORY" +command -v gh >/dev/null || die "missing command: gh" +command -v git >/dev/null || die "missing command: git" +command -v jq >/dev/null || die "missing command: jq" + +# shellcheck disable=SC1091 +source "$REPO_ROOT/build.env" +[ "$ORT_TELEMETRY" = disabled ] || die "ORT_TELEMETRY must be disabled" + +ort_ref="v$ort_version" +package_label="r$package_revision" +release_tag="ort-$ort_version-$package_label" + +upstream_release="$(gh api "repos/microsoft/onnxruntime/releases/tags/$ort_ref")" +jq --exit-status --arg ref "$ort_ref" \ + '.tag_name == $ref and .draft == false and .prerelease == false' \ + <<<"$upstream_release" >/dev/null || die "upstream release is not a published stable release" + +tag_refs="$(git ls-remote https://github.com/microsoft/onnxruntime.git \ + "refs/tags/$ort_ref" "refs/tags/$ort_ref^{}")" +direct_commit="$(awk -v ref="refs/tags/$ort_ref" '$2 == ref { print $1 }' <<<"$tag_refs")" +peeled_commit="$(awk -v ref="refs/tags/$ort_ref^{}" '$2 == ref { print $1 }' <<<"$tag_refs")" +ort_commit="${peeled_commit:-$direct_commit}" +[[ "$ort_commit" =~ ^[0-9a-f]{40}$ ]] || die "unable to resolve upstream tag $ort_ref" + +release_error="$(mktemp)" +if gh api "repos/$GITHUB_REPOSITORY/releases/tags/$release_tag" --silent 2>"$release_error"; then + die "release $release_tag already exists" +elif ! grep -Fq 'HTTP 404' "$release_error"; then + cat "$release_error" >&2 + die "unable to determine whether release $release_tag exists" +fi +target_tag_refs="$(git ls-remote "https://github.com/$GITHUB_REPOSITORY.git" "refs/tags/$release_tag")" +[ -z "$target_tag_refs" ] || die "tag $release_tag already exists" + +if [ "$package_revision" -gt 1 ]; then + previous_tag="ort-$ort_version-r$((package_revision - 1))" + gh api "repos/$GITHUB_REPOSITORY/releases/tags/$previous_tag" \ + --jq 'if .draft == false and .immutable == true then empty else error("previous release is not published and immutable") end' +fi + +build_env_sha256="$(sha256sum "$REPO_ROOT/build.env" | awk '{ print $1 }')" +printf 'ort_ref=%s\n' "$ort_ref" +printf 'ort_version=%s\n' "$ort_version" +printf 'ort_commit=%s\n' "$ort_commit" +printf 'ort_telemetry=%s\n' "$ORT_TELEMETRY" +printf 'python_version=%s\n' "$PYTHON_VERSION" +printf 'node_version=%s\n' "$NODE_VERSION" +printf 'package_channel=stable\n' +printf 'package_revision=%s\n' "$package_revision" +printf 'package_label=%s\n' "$package_label" +printf 'release_tag=%s\n' "$release_tag" +printf 'build_env_sha256=%s\n' "$build_env_sha256" diff --git a/scripts/test-windows-runtime.ps1 b/scripts/test-windows-runtime.ps1 index 7afa159..2b07d0e 100644 --- a/scripts/test-windows-runtime.ps1 +++ b/scripts/test-windows-runtime.ps1 @@ -2,10 +2,6 @@ $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $RepoRoot = Split-Path -Parent $PSScriptRoot -$versions = @{} -Get-Content (Join-Path $RepoRoot "versions.env") | ForEach-Object { - if ($_ -match '^([^#=]+)=(.*)$') { $versions[$Matches[1]] = $Matches[2] } -} $target = if ($args.Count -gt 0) { $args[0] } else { "" } if ($target -notin @("windows-x64", "windows-arm64")) { diff --git a/scripts/verify-release-candidate.sh b/scripts/verify-release-candidate.sh new file mode 100755 index 0000000..62583f8 --- /dev/null +++ b/scripts/verify-release-candidate.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +set -euo pipefail + +die() { + printf 'error: %s\n' "$*" >&2 + exit 1 +} + +[ "$#" -eq 4 ] || die "usage: $0 " +candidate_dir="$1" +build_run_id="$2" +build_run_attempt="$3" +build_commit="$4" +[[ "$build_run_id" =~ ^[1-9][0-9]*$ ]] || die "invalid build run ID" +[[ "$build_run_attempt" =~ ^[1-9][0-9]*$ ]] || die "invalid build run attempt" +[[ "$build_commit" =~ ^[0-9a-f]{40}$ ]] || die "invalid build commit" +[ -n "${GITHUB_REPOSITORY:-}" ] || die "GITHUB_REPOSITORY is required" +[ -d "$candidate_dir" ] || die "missing candidate directory: $candidate_dir" + +provenance="$candidate_dir/build-provenance.json" +checksums="$candidate_dir/SHA256SUMS" +[ -f "$provenance" ] || die "missing build provenance" +[ -f "$checksums" ] || die "missing SHA256SUMS" +[ "$(find "$candidate_dir" -maxdepth 1 -type f | wc -l | tr -d '[:space:]')" -eq 35 ] || + die "release candidate must contain exactly 35 files" + +jq --exit-status \ + --arg repository "$GITHUB_REPOSITORY" \ + --arg build_commit "$build_commit" \ + --argjson build_run_id "$build_run_id" \ + --argjson build_run_attempt "$build_run_attempt" ' + .github_repository == $repository and + .github_build_run_id == $build_run_id and + .github_build_run_attempt == $build_run_attempt and + .github_workflow_ref == ($repository + "/.github/workflows/build.yml@refs/heads/main") and + .packaging_commit == $build_commit and + .release_scope == "all" and + .ort_telemetry == "disabled" and + .package_channel == "stable" and + (.release_notes | type) == "string" and + (.release_notes | length) > 0 and + (.upstream_ort_version | test("^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$")) and + .upstream_ort_ref == ("v" + .upstream_ort_version) and + (.upstream_ort_commit | test("^[0-9a-f]{40}$")) and + (.build_env_sha256 | test("^[0-9a-f]{64}$")) and + (.package_revision | type) == "number" and + .package_revision >= 1 and + (.package_revision | floor) == .package_revision and + .package_label == ("r" + (.package_revision | tostring)) and + .release_tag == ("ort-" + .upstream_ort_version + "-" + .package_label) + ' "$provenance" >/dev/null || die "invalid build provenance" + +release_tag="$(jq -r .release_tag "$provenance")" +ort_ref="$(jq -r .upstream_ort_ref "$provenance")" +ort_version="$(jq -r .upstream_ort_version "$provenance")" +ort_commit="$(jq -r .upstream_ort_commit "$provenance")" +package_revision="$(jq -r .package_revision "$provenance")" +package_label="$(jq -r .package_label "$provenance")" +expected_build_env_sha256="$(jq -r .build_env_sha256 "$provenance")" +actual_build_env_sha256="$(git show "$build_commit:build.env" | sha256sum | awk '{ print $1 }')" +[ "$actual_build_env_sha256" = "$expected_build_env_sha256" ] || + die "build.env does not match the candidate provenance" + +expected_assets=( + "onnxruntime-coreml-ios-$ort_version-$package_label.zip" + "onnxruntime-coreml-macos-arm64-$ort_version-$package_label.tar.gz" + "onnxruntime-coreml-macos-x64-$ort_version-$package_label.tar.gz" + "onnxruntime-webgpu-android-$ort_version-$package_label.aar" + "onnxruntime-webgpu-android-arm64-v8a-$ort_version-$package_label.aar" + "onnxruntime-webgpu-android-armeabi-v7a-$ort_version-$package_label.aar" + "onnxruntime-webgpu-android-x86_64-$ort_version-$package_label.aar" + "onnxruntime-webgpu-linux-arm64-$ort_version-$package_label.tar.gz" + "onnxruntime-webgpu-linux-x64-$ort_version-$package_label.tar.gz" + "onnxruntime-webgpu-windows-arm64-$ort_version-$package_label.zip" + "onnxruntime-webgpu-windows-x64-$ort_version-$package_label.zip" +) + +for asset_name in "${expected_assets[@]}"; do + asset="$candidate_dir/$asset_name" + checksum="$asset.sha256" + manifest="$asset.manifest.env" + [ -f "$asset" ] || die "missing release asset: $asset_name" + [ -f "$checksum" ] || die "missing release checksum: $asset_name.sha256" + [ -f "$manifest" ] || die "missing release manifest: $asset_name.manifest.env" + + expected="$(tr -d '[:space:]' <"$checksum")" + [[ "$expected" =~ ^[0-9a-f]{64}$ ]] || die "invalid checksum: $asset_name.sha256" + actual="$(sha256sum "$asset" | awk '{ print $1 }')" + [ "$actual" = "$expected" ] || die "checksum mismatch: $asset_name" + grep -Fqx "$expected ./$asset_name" "$checksums" || die "SHA256SUMS is missing $asset_name" + grep -Fqx "ORT_REF=$ort_ref" "$manifest" || die "ORT_REF mismatch: $asset_name" + grep -Fqx "ORT_VERSION=$ort_version" "$manifest" || die "ORT_VERSION mismatch: $asset_name" + grep -Fqx "ORT_COMMIT=$ort_commit" "$manifest" || die "ORT_COMMIT mismatch: $asset_name" + grep -Fqx 'ORT_TELEMETRY=disabled' "$manifest" || die "telemetry mismatch: $asset_name" + grep -Fqx "PACKAGING_COMMIT=$build_commit" "$manifest" || die "packaging commit mismatch: $asset_name" + grep -Fqx 'PACKAGE_CHANNEL=stable' "$manifest" || die "package channel mismatch: $asset_name" + grep -Fqx "PACKAGE_REVISION=$package_revision" "$manifest" || die "package revision mismatch: $asset_name" + grep -Fqx "PACKAGE_LABEL=$package_label" "$manifest" || die "package label mismatch: $asset_name" + grep -Fqx 'ORT_CORE_INCLUDED=1' "$manifest" || die "missing ORT core marker: $asset_name" +done + +[ "$(wc -l <"$checksums" | tr -d '[:space:]')" -eq 11 ] || die "SHA256SUMS must contain 11 entries" +(cd "$candidate_dir" && sha256sum -c SHA256SUMS >/dev/null) + +printf 'release_tag=%s\n' "$release_tag" +printf 'ort_ref=%s\n' "$ort_ref" +printf 'ort_version=%s\n' "$ort_version" +printf 'ort_commit=%s\n' "$ort_commit" +printf 'package_revision=%s\n' "$package_revision" +printf 'package_label=%s\n' "$package_label" +printf 'packaging_commit=%s\n' "$build_commit"