mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
# Description of Changes This PR refactors Gradle caching across the GitHub Actions workflows to improve cache reuse, reduce dependency resolution overhead, and shorten CI execution times. ### What was changed - Replaced multiple `gradle/actions/setup-gradle` steps with a unified `actions/cache`-based Gradle User Home cache strategy. - Standardized cache paths across workflows to include: - `~/.gradle/caches` - `~/.gradle/wrapper` - Introduced consistent cache keys using: - Runner OS - Runner architecture - JDK version - Hashes of Gradle wrapper, version catalog, Gradle build files, and project build scripts. - Added restore keys to maximize cache hit rates across similar environments. - Added a new **`gradle-cache-prime`** job in the main build workflow that: - Restores or creates the shared Gradle cache. - Resolves backend dependencies before downstream jobs execute. - Makes the populated cache available to subsequent jobs. - Updated workflow dependencies so Gradle-based jobs wait for the cache priming job before execution. - Simplified and unified Gradle cache handling across numerous CI workflows, including backend builds, OpenAPI generation, database migration tests, Docker tests, Tauri builds, Swagger generation, enterprise builds, release workflows, and license generation. - Updated workflow comments to reflect the new caching strategy and shared cache behavior. ### Why the change was made The previous workflows used a mixture of Gradle setup actions and partial dependency caches, leading to duplicated dependency downloads, inconsistent cache behavior, and longer CI runtimes. Consolidating all workflows onto a shared Gradle User Home cache with a dedicated cache priming job improves cache reuse, reduces unnecessary dependency resolution, and makes CI execution more consistent. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
346 lines
13 KiB
YAML
346 lines
13 KiB
YAML
name: Build and Test Workflow
|
|
|
|
# Top-level PR / merge-queue gate. Detects which paths changed and dispatches
|
|
# to the dedicated reusable workflows under .github/workflows/. Each child
|
|
# workflow keeps its own setup/teardown so this file stays a routing layer.
|
|
#
|
|
# The final `all-checks-passed` job is the single status check that branch
|
|
# protection should require — it succeeds only if every required upstream
|
|
# job either succeeded or was legitimately skipped by its path filter.
|
|
|
|
on:
|
|
pull_request:
|
|
branches: ["main"]
|
|
merge_group:
|
|
branches: ["main"]
|
|
workflow_dispatch:
|
|
|
|
# cancel in-progress jobs if a new job is triggered
|
|
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
|
|
# or a pull request is updated.
|
|
# It helps to save resources and time by ensuring that only the latest commit is built and tested
|
|
# This is particularly useful for long-running jobs that may take a while to complete.
|
|
# The `group` is set to a combination of the workflow name, event name, and branch name.
|
|
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
|
|
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
files-changed:
|
|
name: detect what files changed
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 3
|
|
outputs:
|
|
build: ${{ steps.changes.outputs.build }}
|
|
project: ${{ steps.changes.outputs.project }}
|
|
openapi: ${{ steps.changes.outputs.openapi }}
|
|
frontend: ${{ steps.changes.outputs.frontend }}
|
|
docker-base: ${{ steps.changes.outputs.docker-base }}
|
|
dockerfiles: ${{ steps.changes.outputs.dockerfiles }}
|
|
tauri: ${{ steps.changes.outputs.tauri }}
|
|
engine: ${{ steps.changes.outputs.engine }}
|
|
generated-models: ${{ steps.changes.outputs.generated-models }}
|
|
proprietary: ${{ steps.changes.outputs.proprietary }}
|
|
steps:
|
|
- name: Harden the runner (Audit all outbound calls)
|
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Check for file changes
|
|
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
|
|
id: changes
|
|
with:
|
|
filters: .github/config/.files.yaml
|
|
|
|
gradle-cache-prime:
|
|
name: Prime shared Gradle cache
|
|
needs: [files-changed]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Harden the runner (Audit all outbound calls)
|
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- name: Set up JDK 25
|
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
|
with:
|
|
java-version: "25"
|
|
distribution: "temurin"
|
|
- name: Cache Gradle User Home
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
|
|
restore-keys: |
|
|
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
|
|
gradle-${{ runner.os }}-${{ runner.arch }}-
|
|
- name: Resolve backend dependencies
|
|
run: ./gradlew :stirling-pdf:classes -PnoSpotless --no-daemon
|
|
env:
|
|
STIRLING_FLAVOR: saas
|
|
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
|
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
|
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
|
|
|
|
build:
|
|
needs: [files-changed, gradle-cache-prime]
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
security-events: write
|
|
pull-requests: write
|
|
uses: ./.github/workflows/backend-build.yml
|
|
secrets: inherit
|
|
|
|
db-migration-test:
|
|
# Boots the current bootJar against H2 fixtures captured from past
|
|
# releases (v2.0.0 / v2.5.0 / v2.10.0) and verifies admin login still
|
|
# works after Hibernate's ddl-auto=update migrates the schema. Gated on
|
|
# the `project` filter so doc-only PRs skip this ~5-minute job.
|
|
if: needs.files-changed.outputs.project == 'true'
|
|
needs: [files-changed, gradle-cache-prime]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/db-migration-test.yml
|
|
secrets: inherit
|
|
|
|
check-generateOpenApiDocs:
|
|
if: needs.files-changed.outputs.openapi == 'true'
|
|
needs: [files-changed, gradle-cache-prime]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/check-openapi.yml
|
|
secrets: inherit
|
|
|
|
frontend-validation:
|
|
if: needs.files-changed.outputs.frontend == 'true'
|
|
needs: [files-changed]
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
uses: ./.github/workflows/frontend-validation.yml
|
|
secrets: inherit
|
|
|
|
# Advisory: deliberately NOT in all-checks-passed. It reports on the stories a
|
|
# branch touches so a regression is visible in review, but a browser scan is
|
|
# too new here to block merges on. Promote it once its pass/fail proves stable.
|
|
frontend-a11y:
|
|
if: needs.files-changed.outputs.frontend == 'true'
|
|
needs: [files-changed]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/frontend-a11y.yml
|
|
secrets: inherit
|
|
|
|
playwright-e2e:
|
|
if: needs.files-changed.outputs.frontend == 'true'
|
|
needs: [files-changed]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/e2e-stubbed.yml
|
|
secrets: inherit
|
|
|
|
playwright-e2e-live:
|
|
if: needs.files-changed.outputs.frontend == 'true'
|
|
needs: [files-changed, gradle-cache-prime]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/e2e-live.yml
|
|
secrets: inherit
|
|
|
|
playwright-e2e-enterprise:
|
|
if: needs.files-changed.outputs.proprietary == 'true'
|
|
needs: [files-changed, gradle-cache-prime]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/build-enterprise.yml
|
|
secrets: inherit
|
|
|
|
check-licence:
|
|
if: needs.files-changed.outputs.build == 'true'
|
|
needs: [files-changed, build, gradle-cache-prime]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/check-licence.yml
|
|
secrets: inherit
|
|
|
|
docker-compose-tests:
|
|
if: needs.files-changed.outputs.project == 'true'
|
|
needs: [files-changed, gradle-cache-prime]
|
|
permissions:
|
|
actions: write
|
|
contents: read
|
|
checks: write
|
|
uses: ./.github/workflows/docker-compose-tests.yml
|
|
secrets: inherit
|
|
with:
|
|
docker-base-changed: ${{ needs.files-changed.outputs.docker-base }}
|
|
|
|
test-build-docker-images:
|
|
if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true'
|
|
needs: [files-changed, build, check-generateOpenApiDocs, check-licence, gradle-cache-prime]
|
|
permissions:
|
|
contents: read
|
|
packages: read
|
|
uses: ./.github/workflows/test-build-docker.yml
|
|
secrets: inherit
|
|
with:
|
|
docker-base-changed: ${{ needs.files-changed.outputs.docker-base }}
|
|
dockerfiles-changed: ${{ needs.files-changed.outputs.dockerfiles }}
|
|
|
|
tauri-build:
|
|
if: needs.files-changed.outputs.tauri == 'true'
|
|
needs: [files-changed]
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
uses: ./.github/workflows/tauri-build.yml
|
|
secrets: inherit
|
|
# PR smoke build: macOS + Windows (the platforms our developers use).
|
|
# sign: true only reaches macOS - tauri-build's per-platform gate keeps
|
|
# Windows/Linux signing on main, and an unsigned .dmg cannot be opened.
|
|
# The full signed multi-OS matrix runs on release;
|
|
# nightly still warms the Rust cache with all-OS defaults.
|
|
with:
|
|
platform: windows-macos
|
|
sign: true
|
|
|
|
ai-engine:
|
|
if: needs.files-changed.outputs.engine == 'true'
|
|
needs: [files-changed]
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
uses: ./.github/workflows/ai-engine.yml
|
|
secrets: inherit
|
|
|
|
# The generated frontend types and engine tool models are both derived from
|
|
# the Java OpenAPI spec. This job regenerates and diffs them; it boots the
|
|
# backend, so it is gated on the narrow generated-models filter (spec source,
|
|
# generators, generated files, generation tasks) rather than the broad
|
|
# frontend filter, so a CSS-only PR does not pay for a backend build.
|
|
generated-models:
|
|
if: needs.files-changed.outputs.generated-models == 'true'
|
|
needs: [files-changed, gradle-cache-prime]
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
uses: ./.github/workflows/check-generated-models.yml
|
|
secrets: inherit
|
|
|
|
pre-commit:
|
|
needs: [files-changed]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/pre_commit.yml
|
|
secrets: inherit
|
|
|
|
dependency-review:
|
|
needs: [files-changed]
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/dependency-review.yml
|
|
secrets: inherit
|
|
|
|
# Coverage aggregate: merges the JUnit + e2e:live + cucumber .exec
|
|
# artifacts produced by the jobs above into one report, plus pulls
|
|
# in vitest + Playwright frontend coverage for the per-area matrix.
|
|
# `if: always()` so a producer failing partway still gets credit
|
|
# for whatever did record. Advisory only - intentionally NOT in
|
|
# all-checks-passed, so a flaky aggregate run never blocks merging.
|
|
coverage-aggregate:
|
|
if: always()
|
|
needs:
|
|
- build
|
|
- playwright-e2e-live
|
|
- docker-compose-tests
|
|
- frontend-validation
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/coverage-aggregate.yml
|
|
secrets: inherit
|
|
with:
|
|
frontend-validation-result: ${{ needs.frontend-validation.result }}
|
|
playwright-e2e-live-result: ${{ needs.playwright-e2e-live.result }}
|
|
|
|
# Single status check that branch protection should mark as required.
|
|
# Succeeds when every upstream job is either `success` or `skipped` (path-
|
|
# gated jobs that didn't apply this run). Any `failure` or `cancelled`
|
|
# result fails the gate. `if: always()` ensures the gate evaluates even
|
|
# when an upstream job fails.
|
|
all-checks-passed:
|
|
name: All checks passed
|
|
if: always()
|
|
needs:
|
|
- files-changed
|
|
- build
|
|
- db-migration-test
|
|
- check-generateOpenApiDocs
|
|
- frontend-validation
|
|
- playwright-e2e
|
|
- playwright-e2e-live
|
|
- playwright-e2e-enterprise
|
|
- check-licence
|
|
- docker-compose-tests
|
|
- test-build-docker-images
|
|
- tauri-build
|
|
- ai-engine
|
|
- generated-models
|
|
- pre-commit
|
|
- dependency-review
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Harden the runner (Audit all outbound calls)
|
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Verify every required job passed (or was legitimately skipped)
|
|
env:
|
|
RESULTS: |
|
|
files-changed=${{ needs.files-changed.result }}
|
|
build=${{ needs.build.result }}
|
|
db-migration-test=${{ needs.db-migration-test.result }}
|
|
check-generateOpenApiDocs=${{ needs.check-generateOpenApiDocs.result }}
|
|
frontend-validation=${{ needs.frontend-validation.result }}
|
|
playwright-e2e=${{ needs.playwright-e2e.result }}
|
|
playwright-e2e-live=${{ needs.playwright-e2e-live.result }}
|
|
playwright-e2e-enterprise=${{ needs.playwright-e2e-enterprise.result }}
|
|
check-licence=${{ needs.check-licence.result }}
|
|
docker-compose-tests=${{ needs.docker-compose-tests.result }}
|
|
test-build-docker-images=${{ needs.test-build-docker-images.result }}
|
|
tauri-build=${{ needs.tauri-build.result }}
|
|
ai-engine=${{ needs.ai-engine.result }}
|
|
generated-models=${{ needs.generated-models.result }}
|
|
pre-commit=${{ needs.pre-commit.result }}
|
|
dependency-review=${{ needs.dependency-review.result }}
|
|
run: |
|
|
ok=true
|
|
while IFS='=' read -r name result; do
|
|
[ -z "$name" ] && continue
|
|
case "$result" in
|
|
success|skipped) printf ' %-30s %s\n' "$name" "$result" ;;
|
|
*) printf '✗ %-30s %s\n' "$name" "$result"; ok=false ;;
|
|
esac
|
|
done <<< "$RESULTS"
|
|
if [ "$ok" != "true" ]; then
|
|
echo ""
|
|
echo "One or more required checks failed or were cancelled."
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
echo "All required checks passed."
|