| #<!-- |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| #--> |
| # Validates the Apache release pipeline on every PR: builds the real release |
| # artifacts (git archive, sdist, wheel) using the release script with |
| # --skip-signing, runs Apache RAT on the source tarball, checks that |
| # LICENSE-wheel still covers the npm packages bundled into the compiled UI, |
| # then installs the wheel into a fresh venv outside the source tree and |
| # smoke-tests the server. |
| # |
| # This is designed to catch the class of bugs that have broken recent RCs: |
| # license/header issues (RAT), a LICENSE-wheel that no longer matches the |
| # bundled UI dependencies, examples missing from the wheel (smoke test), |
| # and general "voter tries to install this and it breaks" failures. |
| |
| name: Release Validation |
| |
| on: |
| push: |
| branches: |
| - main |
| tags: |
| - 'v*.*.*-incubating-RC*' |
| pull_request: |
| types: [opened, synchronize, reopened] |
| schedule: |
| # Weekly run against main: catches dependency breakage between releases. |
| - cron: '0 9 * * 1' |
| workflow_dispatch: |
| |
| concurrency: |
| group: release-validation-${{ github.ref }} |
| cancel-in-progress: true |
| |
| permissions: |
| contents: read |
| |
| jobs: |
| check-paths: |
| name: "Release Validation / check-paths" |
| runs-on: ubuntu-latest |
| timeout-minutes: 5 |
| outputs: |
| should_run: ${{ steps.check.outputs.should_run }} |
| steps: |
| - uses: actions/checkout@v7 |
| with: |
| fetch-depth: 0 |
| - id: check |
| run: | |
| if [ "${{ github.event_name }}" != "pull_request" ]; then |
| echo "should_run=true" >> "$GITHUB_OUTPUT" |
| exit 0 |
| fi |
| CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) |
| # If any changed file is outside docs/ and website/, run the full validation |
| if echo "$CHANGED" | grep -qvE '^(docs/|website/)'; then |
| echo "should_run=true" >> "$GITHUB_OUTPUT" |
| else |
| echo "should_run=false" >> "$GITHUB_OUTPUT" |
| fi |
| |
| build-artifacts: |
| name: "Release Validation / build-artifacts" |
| needs: check-paths |
| if: needs.check-paths.outputs.should_run == 'true' |
| runs-on: ubuntu-latest |
| timeout-minutes: 20 |
| outputs: |
| version: ${{ steps.version.outputs.version }} |
| steps: |
| - uses: actions/checkout@v7 |
| |
| - uses: actions/setup-python@v7 |
| with: |
| python-version: '3.12' |
| cache: pip |
| |
| - uses: actions/setup-node@v7 |
| with: |
| node-version: '20' |
| cache: npm |
| cache-dependency-path: telemetry/ui/package-lock.json |
| |
| - uses: actions/setup-java@v6 |
| with: |
| distribution: temurin |
| java-version: '17' |
| |
| - name: Install system deps |
| run: sudo apt-get install -y --no-install-recommends graphviz |
| |
| - name: Install Python build deps |
| run: pip install flit twine jinja2 |
| |
| - name: Cache Apache RAT |
| id: cache-rat |
| uses: actions/cache@v6 |
| with: |
| path: ~/.cache/apache-rat |
| key: apache-rat-0.18 |
| |
| - name: Download Apache RAT if not cached |
| if: steps.cache-rat.outputs.cache-hit != 'true' |
| run: | |
| mkdir -p ~/.cache/apache-rat |
| JAR="$HOME/.cache/apache-rat/apache-rat-0.18.jar" |
| curl -fL -o "$JAR" \ |
| https://repo1.maven.org/maven2/org/apache/rat/apache-rat/0.18/apache-rat-0.18.jar |
| # Verify integrity: SHA256 computed from the official Maven Central download |
| # and cross-checked against Maven Central's published SHA1. |
| echo "fe513ddd10cdc07e965ba430f2c093d8745ff24a0fb54efe0933653752c53301 $JAR" \ |
| | sha256sum --check |
| |
| - name: Extract version |
| id: version |
| run: | |
| VERSION=$(python -c 'import re; print(re.search(r"version\s*=\s*\"([^\"]+)\"", open("pyproject.toml").read()).group(1))') |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" |
| echo "BURR_VERSION=$VERSION" >> "$GITHUB_ENV" |
| |
| - name: Build release artifacts (no signing, no upload) |
| run: | |
| python scripts/apache_release.py all "$BURR_VERSION" 0 ci-runner \ |
| --skip-signing --no-upload |
| |
| - name: Verify all 3 artifacts exist |
| run: | |
| test -f "dist/apache-burr-${BURR_VERSION}-incubating-src.tar.gz" |
| test -f "dist/apache-burr-${BURR_VERSION}-incubating-sdist.tar.gz" |
| test -f "dist/apache_burr-${BURR_VERSION}-py3-none-any.whl" |
| |
| # RAT excludes burr/tracking/server/build/**, so it never inspects the |
| # compiled UI -- the only content the wheel adds over the sdist. This |
| # step covers that gap: it re-derives the bundled npm packages from a |
| # sourcemap build and fails if LICENSE-wheel no longer matches, which |
| # is what silently drifts on a dependency bump. |
| - name: Check LICENSE-wheel covers the bundled UI dependencies |
| run: python scripts/generate_wheel_license.py --check |
| |
| - name: Run Apache RAT on source and sdist tarballs |
| run: | |
| python scripts/verify_apache_artifacts.py licenses \ |
| --rat-jar ~/.cache/apache-rat/apache-rat-0.18.jar \ |
| --artifacts-dir dist |
| |
| - name: Upload release artifacts |
| uses: actions/upload-artifact@v7 |
| with: |
| name: release-artifacts |
| path: | |
| dist/*.tar.gz |
| dist/*.whl |
| dist/*.sha512 |
| dist/rat-report-*.xml |
| dist/rat-report-*.txt |
| retention-days: 14 |
| |
| install-and-smoke: |
| name: "Release Validation / install-and-smoke" |
| needs: [check-paths, build-artifacts] |
| if: needs.check-paths.outputs.should_run == 'true' |
| runs-on: ubuntu-latest |
| timeout-minutes: 10 |
| strategy: |
| fail-fast: false |
| matrix: |
| # 3.9 is skipped because burr/cli/__main__.py uses PEP 604 union syntax |
| # (dict | None) which requires Python 3.10+. Tracked separately. |
| python-version: ['3.10', '3.11', '3.12'] |
| steps: |
| - uses: actions/checkout@v7 |
| |
| - name: Set up Python ${{ matrix.python-version }} |
| uses: actions/setup-python@v7 |
| with: |
| python-version: ${{ matrix.python-version }} |
| |
| - name: Download release artifacts |
| uses: actions/download-artifact@v8 |
| with: |
| name: release-artifacts |
| path: dist |
| |
| - name: Run smoke test |
| env: |
| BURR_VERSION: ${{ needs.build-artifacts.outputs.version }} |
| run: | |
| python scripts/ci_smoke_server.py \ |
| --wheel "dist/apache_burr-${BURR_VERSION}-py3-none-any.whl" |
| |
| - name: Upload smoke workspace on failure |
| if: failure() |
| uses: actions/upload-artifact@v7 |
| with: |
| name: smoke-workspace-${{ matrix.python-version }} |
| path: /tmp/burr-smoke-* |
| retention-days: 7 |
| if-no-files-found: ignore |
| |
| # Installs the wheel without any optional extras ([learn], etc.) and imports |
| # core symbols. Catches accidental leakage of optional dependencies into core |
| # code — a bare `pip install apache-burr` user would hit an ImportError that |
| # the [learn] smoke test would never see. |
| bare-install: |
| name: "Release Validation / bare-install" |
| needs: [check-paths, build-artifacts] |
| if: needs.check-paths.outputs.should_run == 'true' |
| runs-on: ubuntu-latest |
| timeout-minutes: 10 |
| steps: |
| - uses: actions/checkout@v7 |
| |
| - uses: actions/setup-python@v7 |
| with: |
| python-version: '3.12' |
| |
| - name: Download release artifacts |
| uses: actions/download-artifact@v8 |
| with: |
| name: release-artifacts |
| path: dist |
| |
| - name: Install wheel without optional extras |
| env: |
| BURR_VERSION: ${{ needs.build-artifacts.outputs.version }} |
| run: | |
| pip install "dist/apache_burr-${BURR_VERSION}-py3-none-any.whl" |
| |
| - name: Verify core imports succeed without optional dependencies |
| run: | |
| python -c " |
| import burr |
| from burr.core import ApplicationBuilder, State |
| from burr.core.action import action |
| print('Core imports OK') |
| " |
| |
| # Extracts the sdist tarball, rebuilds the wheel from it (including the |
| # frontend npm build), then compares the resulting wheel's file contents |
| # against the release wheel using content hashes. Catches cases where the |
| # sdist is missing files that the direct wheel build includes. |
| sdist-wheel-equivalence: |
| name: "Release Validation / sdist-wheel-equivalence" |
| needs: [check-paths, build-artifacts] |
| if: needs.check-paths.outputs.should_run == 'true' |
| runs-on: ubuntu-latest |
| timeout-minutes: 25 |
| steps: |
| - uses: actions/checkout@v7 |
| |
| - uses: actions/setup-python@v7 |
| with: |
| python-version: '3.12' |
| cache: pip |
| |
| - uses: actions/setup-node@v7 |
| with: |
| node-version: '20' |
| cache: npm |
| cache-dependency-path: telemetry/ui/package-lock.json |
| |
| - uses: actions/setup-java@v6 |
| with: |
| distribution: temurin |
| java-version: '17' |
| |
| - name: Install system deps |
| run: sudo apt-get install -y --no-install-recommends graphviz |
| |
| - name: Install Python build deps |
| run: pip install flit twine jinja2 |
| |
| - name: Download release artifacts |
| uses: actions/download-artifact@v8 |
| with: |
| name: release-artifacts |
| path: dist |
| |
| - name: Extract sdist and build wheel from it |
| env: |
| BURR_VERSION: ${{ needs.build-artifacts.outputs.version }} |
| run: | |
| mkdir -p /tmp/sdist-extract /tmp/sdist-wheel |
| tar -xzf "dist/apache-burr-${BURR_VERSION}-incubating-sdist.tar.gz" \ |
| -C /tmp/sdist-extract |
| # Find the single top-level directory the tarball extracted into |
| SDIST_ROOT=$(find /tmp/sdist-extract -maxdepth 1 -mindepth 1 -type d | head -1) |
| cd "$SDIST_ROOT" |
| # Build wheel from within the extracted sdist. The sdist contains the |
| # React frontend source (telemetry/ui/) but not the compiled output, |
| # so the full npm build runs here — same as the original build. |
| python scripts/apache_release.py wheel "$BURR_VERSION" 0 \ |
| --skip-signing --output-dir /tmp/sdist-wheel |
| |
| - name: Compare sdist-built wheel against release wheel |
| env: |
| BURR_VERSION: ${{ needs.build-artifacts.outputs.version }} |
| run: | |
| python scripts/verify_apache_artifacts.py compare-wheels \ |
| "dist/apache_burr-${BURR_VERSION}-py3-none-any.whl" \ |
| "/tmp/sdist-wheel/apache_burr-${BURR_VERSION}-py3-none-any.whl" |
| |
| # Single stable required-check name. Always runs (if: always()) so it produces |
| # a definite SUCCESS or FAILURE — never SKIPPED. Branch protection in |
| # .asf.yaml requires this context, not the underlying jobs, so path-filtered |
| # docs/website PRs (where the upstream jobs are skipped) still go green here. |
| summary: |
| name: "Release Validation / summary" |
| needs: [check-paths, build-artifacts, install-and-smoke, bare-install, sdist-wheel-equivalence] |
| if: always() |
| runs-on: ubuntu-latest |
| timeout-minutes: 2 |
| steps: |
| - name: Verdict |
| env: |
| CHECK_PATHS: ${{ needs.check-paths.result }} |
| BUILD_ARTIFACTS: ${{ needs.build-artifacts.result }} |
| INSTALL_AND_SMOKE: ${{ needs.install-and-smoke.result }} |
| BARE_INSTALL: ${{ needs.bare-install.result }} |
| SDIST_WHEEL_EQUIV: ${{ needs.sdist-wheel-equivalence.result }} |
| run: | |
| echo "check-paths: $CHECK_PATHS" |
| echo "build-artifacts: $BUILD_ARTIFACTS" |
| echo "install-and-smoke: $INSTALL_AND_SMOKE" |
| echo "bare-install: $BARE_INSTALL" |
| echo "sdist-wheel-equivalence: $SDIST_WHEEL_EQUIV" |
| # Pass if every needed job is success or skipped; fail if any |
| # failed or was cancelled. |
| for r in "$CHECK_PATHS" "$BUILD_ARTIFACTS" "$INSTALL_AND_SMOKE" "$BARE_INSTALL" "$SDIST_WHEEL_EQUIV"; do |
| case "$r" in |
| success|skipped) ;; |
| *) echo "::error::Release Validation failed (one or more jobs not success/skipped)"; exit 1 ;; |
| esac |
| done |
| echo "Release Validation: all upstream jobs success or skipped." |