From 86072ec91a95e7ca7cabc7fba4ac4d3dc2ed58cd Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:44:38 +0000 Subject: [PATCH] Cachefixing test (#5793) Co-authored-by: Claude Haiku 4.5 --- .github/workflows/PR-Auto-Deploy-V2.yml | 2 + .../workflows/PR-Demo-Comment-with-react.yml | 2 + .github/workflows/build.yml | 71 +++++++++++++++++-- .github/workflows/deploy-on-v2-commit.yml | 4 ++ .github/workflows/multiOSReleases.yml | 10 +++ .github/workflows/push-docker.yml | 22 ++++-- .github/workflows/tauri-build.yml | 3 +- .github/workflows/testdriver.yml | 2 + .../software/common/util/FileToPdf.java | 6 +- .../exception/GlobalExceptionHandler.java | 14 ++-- docker/embedded/Dockerfile | 34 +++++---- docker/embedded/Dockerfile.fat | 35 +++++---- docker/embedded/Dockerfile.ultra-lite | 33 +++++---- testing/test.sh | 30 +++++--- 14 files changed, 194 insertions(+), 74 deletions(-) diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index 1745411d8..6b4054696 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -231,6 +231,8 @@ jobs: context: . file: ./docker/embedded/Dockerfile push: true + cache-from: type=gha,scope=stirling-pdf-latest + cache-to: type=gha,mode=max,scope=stirling-pdf-latest tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} build-args: VERSION_TAG=v2-alpha platforms: linux/amd64 diff --git a/.github/workflows/PR-Demo-Comment-with-react.yml b/.github/workflows/PR-Demo-Comment-with-react.yml index 06454ece2..a1f5171bb 100644 --- a/.github/workflows/PR-Demo-Comment-with-react.yml +++ b/.github/workflows/PR-Demo-Comment-with-react.yml @@ -190,6 +190,8 @@ jobs: context: . file: ./docker/embedded/Dockerfile push: true + cache-from: type=gha,scope=stirling-pdf-latest + cache-to: type=gha,mode=max,scope=stirling-pdf-latest tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }} build-args: VERSION_TAG=alpha platforms: linux/amd64 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca482df72..bfa0e2225 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,10 +68,20 @@ jobs: java-version: ${{ matrix.jdk-version }} distribution: "temurin" + - name: Cache Gradle dependency artifacts + uses: actions/cache@v4 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches/modules-2/files-2.1 + ~/.gradle/caches/modules-2/metadata-2.* + key: gradle-deps-${{ runner.os }}-jdk-${{ matrix.jdk-version }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} + - name: Setup Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 with: gradle-version: 8.14 + cache-disabled: true - name: Build with Gradle and spring security ${{ matrix.spring-security }} run: ./gradlew build -PnoSpotless @@ -143,10 +153,20 @@ jobs: java-version: "25" distribution: "temurin" + - name: Cache Gradle dependency artifacts + uses: actions/cache@v4 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches/modules-2/files-2.1 + ~/.gradle/caches/modules-2/metadata-2.* + key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} + - name: Setup Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 with: gradle-version: 8.14 + cache-disabled: true - name: Generate OpenAPI documentation run: ./gradlew :stirling-pdf:generateOpenApiDocs @@ -215,10 +235,20 @@ jobs: java-version: "25" distribution: "temurin" + - name: Cache Gradle dependency artifacts + uses: actions/cache@v4 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches/modules-2/files-2.1 + ~/.gradle/caches/modules-2/metadata-2.* + key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} + - name: Setup Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 with: gradle-version: 8.14 + cache-disabled: true - name: check the licenses for compatibility # NOTE: --no-parallel is intentional here. Running the checkLicense task in parallel with other @@ -259,6 +289,8 @@ jobs: runs-on: ubuntu-latest permissions: + actions: write + contents: read checks: write steps: @@ -276,14 +308,28 @@ jobs: java-version: "25" distribution: "temurin" + - name: Cache Gradle dependency artifacts + uses: actions/cache@v4 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches/modules-2/files-2.1 + ~/.gradle/caches/modules-2/metadata-2.* + key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} + - name: Setup Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 with: gradle-version: 8.14 + cache-disabled: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + # Expose ACTIONS_RUNTIME_TOKEN / ACTIONS_RESULTS_URL for docker buildx type=gha cache backend. + - name: Expose GitHub runtime for Buildx cache + uses: crazy-max/ghaction-github-runtime@v3 + - name: Install Docker Compose run: | sudo curl -SL "https://github.com/docker/compose/releases/download/v2.39.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose @@ -338,12 +384,15 @@ jobs: fail-fast: false matrix: include: - # - docker-rev: docker/embedded/Dockerfile - # artifact-suffix: Dockerfile + - docker-rev: docker/embedded/Dockerfile + artifact-suffix: Dockerfile + cache-scope: stirling-pdf-latest - docker-rev: docker/embedded/Dockerfile.ultra-lite artifact-suffix: Dockerfile.ultra-lite - # - docker-rev: docker/embedded/Dockerfile.fat - # artifact-suffix: Dockerfile.fat + cache-scope: stirling-pdf-ultra-lite + - docker-rev: docker/embedded/Dockerfile.fat + artifact-suffix: Dockerfile.fat + cache-scope: stirling-pdf-fat steps: - name: Harden Runner uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 @@ -366,10 +415,20 @@ jobs: java-version: "25" distribution: "temurin" + - name: Cache Gradle dependency artifacts + uses: actions/cache@v4 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches/modules-2/files-2.1 + ~/.gradle/caches/modules-2/metadata-2.* + key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} + - name: Setup Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 with: gradle-version: 8.14 + cache-disabled: true - name: Build application run: ./gradlew build @@ -394,8 +453,8 @@ jobs: context: . file: ./${{ matrix.docker-rev }} push: false - cache-from: type=gha,scope=${{ matrix.artifact-suffix }} - cache-to: type=gha,mode=max,scope=${{ matrix.artifact-suffix }} + cache-from: type=gha,scope=${{ matrix.cache-scope }} + cache-to: type=gha,mode=max,scope=${{ matrix.cache-scope }} platforms: linux/amd64,linux/arm64/v8 provenance: true sbom: true diff --git a/.github/workflows/deploy-on-v2-commit.yml b/.github/workflows/deploy-on-v2-commit.yml index aa09435ee..156c4e00b 100644 --- a/.github/workflows/deploy-on-v2-commit.yml +++ b/.github/workflows/deploy-on-v2-commit.yml @@ -97,6 +97,8 @@ jobs: context: . file: ./docker/frontend/Dockerfile push: true + cache-from: type=gha,scope=stirling-v2-frontend + cache-to: type=gha,mode=max,scope=stirling-v2-frontend tags: | ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-latest @@ -110,6 +112,8 @@ jobs: context: . file: ./docker/backend/Dockerfile push: true + cache-from: type=gha,scope=stirling-v2-backend + cache-to: type=gha,mode=max,scope=stirling-v2-backend tags: | ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-latest diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index adc5f3f5e..83245f5bd 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -48,6 +48,16 @@ jobs: java-version: "25" distribution: "temurin" + - name: Cache Gradle dependencies + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + - name: Setup Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 with: diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index 3146ebb0e..7c0883569 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -45,6 +45,16 @@ jobs: java-version: "25" distribution: "temurin" + - name: Cache Gradle dependencies + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + - name: Setup Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 with: @@ -116,8 +126,8 @@ jobs: context: . file: ./docker/embedded/Dockerfile push: true - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=stirling-pdf-latest + cache-to: type=gha,mode=max,scope=stirling-pdf-latest tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} @@ -162,8 +172,8 @@ jobs: context: . file: ./docker/embedded/Dockerfile.fat push: true - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=stirling-pdf-fat + cache-to: type=gha,mode=max,scope=stirling-pdf-fat tags: ${{ steps.meta-fat.outputs.tags }} labels: ${{ steps.meta-fat.outputs.labels }} build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} @@ -206,8 +216,8 @@ jobs: context: . file: ./docker/embedded/Dockerfile.ultra-lite push: true - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=stirling-pdf-ultra-lite + cache-to: type=gha,mode=max,scope=stirling-pdf-ultra-lite tags: ${{ steps.meta-lite.outputs.tags }} labels: ${{ steps.meta-lite.outputs.labels }} build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index 4d0cb5e8c..5ec256d57 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -15,6 +15,7 @@ on: - linux pull_request: branches: [main, V2-tauri-windows] + types: [opened, reopened, synchronize, ready_for_review] paths: - "frontend/src-tauri/**" - "frontend/src/desktop/**" @@ -23,8 +24,6 @@ on: - "frontend/package-lock.json" - "frontend/vite.config.ts" - ".github/workflows/tauri-build.yml" - push: - branches: [main] permissions: contents: read diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml index c3e8d4dc2..27862ada7 100644 --- a/.github/workflows/testdriver.yml +++ b/.github/workflows/testdriver.yml @@ -72,6 +72,8 @@ jobs: context: . file: ./docker/embedded/Dockerfile push: true + cache-from: type=gha,scope=stirling-pdf-latest + cache-to: type=gha,mode=max,scope=stirling-pdf-latest tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }} build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} platforms: linux/amd64 diff --git a/app/common/src/main/java/stirling/software/common/util/FileToPdf.java b/app/common/src/main/java/stirling/software/common/util/FileToPdf.java index 1b94b7c74..69a99e50b 100644 --- a/app/common/src/main/java/stirling/software/common/util/FileToPdf.java +++ b/app/common/src/main/java/stirling/software/common/util/FileToPdf.java @@ -100,10 +100,12 @@ public class FileToPdf { while (entry != null) { Path filePath = tempUnzippedDir.getPath().resolve(sanitizeZipFilename(entry.getName())); - Path normalizedTargetDir = tempUnzippedDir.getPath().toAbsolutePath().normalize(); + Path normalizedTargetDir = + tempUnzippedDir.getPath().toAbsolutePath().normalize(); Path normalizedFilePath = filePath.toAbsolutePath().normalize(); if (!normalizedFilePath.startsWith(normalizedTargetDir)) { - throw new IOException("Zip entry path escapes target directory: " + entry.getName()); + throw new IOException( + "Zip entry path escapes target directory: " + entry.getName()); } if (!entry.isDirectory()) { Files.createDirectories(filePath.getParent()); diff --git a/app/core/src/main/java/stirling/software/SPDF/exception/GlobalExceptionHandler.java b/app/core/src/main/java/stirling/software/SPDF/exception/GlobalExceptionHandler.java index 6eac7f71e..f7e35c0d6 100644 --- a/app/core/src/main/java/stirling/software/SPDF/exception/GlobalExceptionHandler.java +++ b/app/core/src/main/java/stirling/software/SPDF/exception/GlobalExceptionHandler.java @@ -23,14 +23,14 @@ import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.NoHandlerFoundException; +import com.fasterxml.jackson.databind.ObjectMapper; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import com.fasterxml.jackson.databind.ObjectMapper; - import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.ExceptionUtils.*; import stirling.software.common.util.RegexPatternUtils; @@ -874,10 +874,16 @@ public class GlobalExceptionHandler { errorMap.put("type", "about:blank"); errorMap.put("title", "Not Acceptable"); errorMap.put("status", 406); - errorMap.put("detail", "The requested resource could not be returned in an acceptable format. Error responses are returned as JSON."); + errorMap.put( + "detail", + "The requested resource could not be returned in an acceptable format. Error responses are returned as JSON."); errorMap.put("instance", request.getRequestURI()); errorMap.put("timestamp", Instant.now().toString()); - errorMap.put("hints", java.util.Arrays.asList("Error responses are always returned as application/json or application/problem+json", "Set Accept header to include application/json for proper error handling")); + errorMap.put( + "hints", + java.util.Arrays.asList( + "Error responses are always returned as application/json or application/problem+json", + "Set Accept header to include application/json for proper error handling")); String errorJson = mapper.writeValueAsString(errorMap); response.getWriter().write(errorJson); diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile index 84e66f44e..9e3ca902e 100644 --- a/docker/embedded/Dockerfile +++ b/docker/embedded/Dockerfile @@ -4,7 +4,8 @@ FROM ubuntu:noble AS calibre-build ARG CALIBRE_VERSION=9.3.1 -RUN set -eux; \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates curl xz-utils libnss3 libfontconfig1 \ @@ -185,12 +186,13 @@ RUN set -eux; \ # Build the Java application and frontend. FROM gradle:9.3.1-jdk25 AS app-build -RUN apt-get update \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && update-ca-certificates \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ - && apt-get clean && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* # JDK 25+: --add-exports is no longer accepted via JAVA_TOOL_OPTIONS; use JDK_JAVA_OPTIONS instead ENV JDK_JAVA_OPTIONS="--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ @@ -232,11 +234,13 @@ RUN java -Djarmode=tools -jar app.jar extract --layers --destination /layers # Build Ghostscript 10.06.0 from source in an isolated stage (avoids library conflicts). FROM ubuntu:noble AS gs-build ARG GS_VERSION=10.06.0 -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential curl ca-certificates libfontconfig1-dev && \ - rm -rf /var/lib/apt/lists/* && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/tmp/gs-build \ + apt-get update && apt-get install -y --no-install-recommends \ + build-essential curl ca-certificates libfontconfig1-dev && rm -rf /var/lib/apt/lists/* && \ GS_TAG="gs$(printf '%s' "${GS_VERSION}" | tr -d '.')" && \ - curl -fsSL "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/${GS_TAG}/ghostscript-${GS_VERSION}.tar.gz" | tar xz && \ + cd /tmp/gs-build && \ + (test -d "ghostscript-${GS_VERSION}" || curl -fsSL "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/${GS_TAG}/ghostscript-${GS_VERSION}.tar.gz" | tar xz) && \ cd "ghostscript-${GS_VERSION}" && \ ./configure \ --prefix=/usr/local \ @@ -252,19 +256,22 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ FROM ubuntu:noble AS pdf-tools-build ARG QPDF_VERSION=12.3.2 ARG IM_VERSION=7.1.2-13 -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/tmp/pdf-tools-build \ + apt-get update && apt-get install -y --no-install-recommends \ build-essential cmake libssl-dev libjpeg-dev zlib1g-dev curl ca-certificates pkg-config \ libpng-dev libtiff-dev libwebp-dev libxml2-dev libfreetype6-dev liblcms2-dev libzip-dev liblqr-1-0-dev \ - libltdl-dev libtool && \ + libltdl-dev libtool && rm -rf /var/lib/apt/lists/* && \ + cd /tmp/pdf-tools-build && \ # Build QPDF - curl -fsSL "https://github.com/qpdf/qpdf/releases/download/v${QPDF_VERSION}/qpdf-${QPDF_VERSION}.tar.gz" | tar xz && \ + (test -d "qpdf-${QPDF_VERSION}" || curl -fsSL "https://github.com/qpdf/qpdf/releases/download/v${QPDF_VERSION}/qpdf-${QPDF_VERSION}.tar.gz" | tar xz) && \ cd "qpdf-${QPDF_VERSION}" && \ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DALLOW_CRYPTO_OPENSSL=ON -DDEFAULT_CRYPTO=openssl && \ cmake --build build --parallel "$(nproc)" && \ cmake --install build && \ cd .. && \ # Build ImageMagick 7 - curl -fsSL "https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IM_VERSION}.tar.gz" | tar xz && \ + (test -d "ImageMagick-${IM_VERSION}" || curl -fsSL "https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IM_VERSION}.tar.gz" | tar xz) && \ cd "ImageMagick-${IM_VERSION}" && \ ./configure --prefix=/usr/local --with-modules --with-perl=no --with-magick-plus-plus=no --with-quantum-depth=16 --disable-static --enable-shared && \ make -j"$(nproc)" && \ @@ -289,7 +296,8 @@ ENV DEBIAN_FRONTEND=noninteractive \ ARG UNOSERVER_VERSION=3.6 -RUN set -eux; \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + set -eux; \ apt-get update; \ # Add LibreOffice Fresh PPA for latest version (26.2.x) apt-get install -y --no-install-recommends software-properties-common; \ @@ -351,8 +359,6 @@ RUN set -eux; \ \ # Cleanup stage. \ - # apt clean needed without cache mounts - apt-get clean; \ rm -rf /var/lib/apt/lists/*; \ \ # Docs / man / info / icons / themes / GUI assets (headless server) diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat index 2573a1fd5..a6002f6d1 100644 --- a/docker/embedded/Dockerfile.fat +++ b/docker/embedded/Dockerfile.fat @@ -5,7 +5,8 @@ FROM ubuntu:noble AS calibre-build ARG CALIBRE_VERSION=9.3.1 -RUN set -eux; \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates curl xz-utils libnss3 libfontconfig1 \ @@ -184,12 +185,13 @@ RUN set -eux; \ # Build the Java application and frontend. FROM gradle:9.3.1-jdk25 AS app-build -RUN apt-get update \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ && update-ca-certificates \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ - && apt-get clean && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* # JDK 25+: --add-exports is no longer accepted via JAVA_TOOL_OPTIONS; use JDK_JAVA_OPTIONS instead ENV JDK_JAVA_OPTIONS="--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ @@ -228,7 +230,8 @@ FROM ubuntu:noble AS python-build ARG UNOSERVER_VERSION=3.6 ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ python3 python3-venv python3-dev \ python3-packaging \ build-essential \ @@ -249,11 +252,13 @@ RUN --mount=type=cache,target=/root/.cache/pip \ # Build Ghostscript 10.06.0 from source in an isolated stage (avoids library conflicts). FROM ubuntu:noble AS gs-build ARG GS_VERSION=10.06.0 -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential curl ca-certificates libfontconfig1-dev && \ - rm -rf /var/lib/apt/lists/* && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/tmp/gs-build \ + apt-get update && apt-get install -y --no-install-recommends \ + build-essential curl ca-certificates libfontconfig1-dev && rm -rf /var/lib/apt/lists/* && \ GS_TAG="gs$(printf '%s' "${GS_VERSION}" | tr -d '.')" && \ - curl -fsSL "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/${GS_TAG}/ghostscript-${GS_VERSION}.tar.gz" | tar xz && \ + cd /tmp/gs-build && \ + (test -d "ghostscript-${GS_VERSION}" || curl -fsSL "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/${GS_TAG}/ghostscript-${GS_VERSION}.tar.gz" | tar xz) && \ cd "ghostscript-${GS_VERSION}" && \ ./configure \ --prefix=/usr/local \ @@ -269,19 +274,22 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ FROM ubuntu:noble AS pdf-tools-build ARG QPDF_VERSION=12.3.2 ARG IM_VERSION=7.1.2-13 -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/tmp/pdf-tools-build \ + apt-get update && apt-get install -y --no-install-recommends \ build-essential cmake libssl-dev libjpeg-dev zlib1g-dev curl ca-certificates pkg-config \ libpng-dev libtiff-dev libwebp-dev libxml2-dev libfreetype6-dev liblcms2-dev libzip-dev liblqr-1-0-dev \ - libltdl-dev libtool && \ + libltdl-dev libtool && rm -rf /var/lib/apt/lists/* && \ + cd /tmp/pdf-tools-build && \ # Build QPDF - curl -fsSL "https://github.com/qpdf/qpdf/releases/download/v${QPDF_VERSION}/qpdf-${QPDF_VERSION}.tar.gz" | tar xz && \ + (test -d "qpdf-${QPDF_VERSION}" || curl -fsSL "https://github.com/qpdf/qpdf/releases/download/v${QPDF_VERSION}/qpdf-${QPDF_VERSION}.tar.gz" | tar xz) && \ cd "qpdf-${QPDF_VERSION}" && \ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DALLOW_CRYPTO_OPENSSL=ON -DDEFAULT_CRYPTO=openssl && \ cmake --build build --parallel "$(nproc)" && \ cmake --install build && \ cd .. && \ # Build ImageMagick 7 - curl -fsSL "https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IM_VERSION}.tar.gz" | tar xz && \ + (test -d "ImageMagick-${IM_VERSION}" || curl -fsSL "https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IM_VERSION}.tar.gz" | tar xz) && \ cd "ImageMagick-${IM_VERSION}" && \ ./configure --prefix=/usr/local --with-modules --with-perl=no --with-magick-plus-plus=no --with-quantum-depth=16 --disable-static --enable-shared && \ make -j"$(nproc)" && \ @@ -307,7 +315,6 @@ ENV DEBIAN_FRONTEND=noninteractive \ ARG UNOSERVER_VERSION=3.6 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/root/.cache/pip \ set -eux; \ apt-get update; \ @@ -354,8 +361,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ \ # Cleanup stage. \ - # apt clean not strictly needed with cache mounts, but good for reducing layer metadata - apt-get clean; \ rm -rf /var/lib/apt/lists/*; \ \ # Docs / man / info / icons / themes / GUI assets (headless server) diff --git a/docker/embedded/Dockerfile.ultra-lite b/docker/embedded/Dockerfile.ultra-lite index 4a15c48cf..d79747631 100644 --- a/docker/embedded/Dockerfile.ultra-lite +++ b/docker/embedded/Dockerfile.ultra-lite @@ -5,13 +5,13 @@ FROM gradle:9.3.1-jdk25 AS build # Install Node.js and npm for frontend build -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ curl \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && npm --version \ && node --version \ - && apt-get clean \ && rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -65,18 +65,6 @@ LABEL org.opencontainers.image.title="Stirling-PDF Ultra-Lite" \ org.opencontainers.image.version="${VERSION_TAG}" \ org.opencontainers.image.keywords="PDF, manipulation, ultra-lite, API, Spring Boot, React" -# Copy scripts -COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh -COPY scripts/installFonts.sh /scripts/installFonts.sh -COPY scripts/stirling-diagnostics.sh /scripts/stirling-diagnostics.sh - -# Copy built JARs from build stage -# Use numeric UID:GID (1000:1000) since the named user doesn't exist yet at COPY time -COPY --from=build --chown=1000:1000 \ - /app/app/core/build/libs/*.jar /app.jar -COPY --from=build --chown=1000:1000 \ - /app/build/libs/restart-helper.jar /restart-helper.jar - # Environment Variables # NOTE: Memory flags (InitialRAMPercentage, MaxRAMPercentage, MaxMetaspaceSize) # are computed dynamically by init-without-ocr.sh based on container memory limits. @@ -96,7 +84,6 @@ ENV VERSION_TAG=$VERSION_TAG \ ENDPOINTS_GROUPS_TO_REMOVE=CLI # Install minimal dependencies -# --chown on COPY above removes need to chown JARs here RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ @@ -111,10 +98,22 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a su-exec && \ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf /tmp/stirling-pdf/heap_dumps && \ mkdir -p /usr/share/fonts/opentype/noto && \ - chmod +x /scripts/*.sh && \ # User permissions addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline /tmp/stirling-pdf + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /configs /customFiles /pipeline /tmp/stirling-pdf + +# Copy scripts and built artifacts after OS package layer to maximize cache reuse. +COPY --chown=1000:1000 scripts/init-without-ocr.sh /scripts/init-without-ocr.sh +COPY --chown=1000:1000 scripts/installFonts.sh /scripts/installFonts.sh +COPY --chown=1000:1000 scripts/stirling-diagnostics.sh /scripts/stirling-diagnostics.sh + +# Copy built JARs from build stage +COPY --from=build --chown=1000:1000 \ + /app/app/core/build/libs/*.jar /app.jar +COPY --from=build --chown=1000:1000 \ + /app/build/libs/restart-helper.jar /restart-helper.jar + +RUN chmod +x /scripts/*.sh EXPOSE 8080/tcp diff --git a/testing/test.sh b/testing/test.sh index 877a2a06c..8c92336ff 100644 --- a/testing/test.sh +++ b/testing/test.sh @@ -421,7 +421,7 @@ main() { should_run_test "Stirling-PDF-Ultra-Lite-Version-Check"; then export DISABLE_ADDITIONAL_FEATURES=true - if ! ./gradlew clean build; then + if ! ./gradlew clean build -PnoSpotless; then echo "Gradle build failed with security disabled, exiting script." exit 1 fi @@ -431,11 +431,18 @@ main() { EXPECTED_VERSION=$(get_expected_version) echo "Expected version: $EXPECTED_VERSION" - # Build Ultra-Lite image with embedded frontend (GHCR tag, matching docker-compose-latest-ultra-lite.yml) + # Build Ultra-Lite image with embedded frontend (matching docker-compose-latest-ultra-lite.yml) echo "Building ultra-lite image for tests that require it..." - docker build --build-arg VERSION_TAG=alpha \ + if [ -n "${ACTIONS_RUNTIME_TOKEN}" ] && { [ -n "${ACTIONS_RESULTS_URL}" ] || [ -n "${ACTIONS_CACHE_URL}" ]; }; then + DOCKER_CACHE_ARGS_ULTRA_LITE="--cache-from type=gha,scope=stirling-pdf-ultra-lite --cache-to type=gha,mode=max,scope=stirling-pdf-ultra-lite" + else + DOCKER_CACHE_ARGS_ULTRA_LITE="" + fi + docker buildx build --build-arg VERSION_TAG=alpha \ -t docker.stirlingpdf.com/stirlingtools/stirling-pdf:ultra-lite \ - -f ./docker/embedded/Dockerfile.ultra-lite . + -f ./docker/embedded/Dockerfile.ultra-lite \ + --load \ + ${DOCKER_CACHE_ARGS_ULTRA_LITE} . else echo "Skipping ultra-lite image build - no ultra-lite tests in rerun list" fi @@ -482,7 +489,7 @@ main() { should_run_test "Stirling-PDF-Fat-Disable-Endpoints-Version-Check"; then export DISABLE_ADDITIONAL_FEATURES=false - if ! ./gradlew clean build; then + if ! ./gradlew clean build -PnoSpotless; then echo "Gradle build failed with security enabled, exiting script." exit 1 fi @@ -491,11 +498,18 @@ main() { EXPECTED_VERSION=$(get_expected_version) echo "Expected version with security enabled: $EXPECTED_VERSION" - # Build Fat (Security) image with embedded frontend for GHCR tag used in all 'fat' compose files + # Build Fat (Security) image with embedded frontend (matching all 'fat' compose files) echo "Building fat image for tests that require it..." - docker build --no-cache --pull --build-arg VERSION_TAG=alpha \ + if [ -n "${ACTIONS_RUNTIME_TOKEN}" ] && { [ -n "${ACTIONS_RESULTS_URL}" ] || [ -n "${ACTIONS_CACHE_URL}" ]; }; then + DOCKER_CACHE_ARGS_FAT="--cache-from type=gha,scope=stirling-pdf-fat --cache-to type=gha,mode=max,scope=stirling-pdf-fat" + else + DOCKER_CACHE_ARGS_FAT="" + fi + docker buildx build --build-arg VERSION_TAG=alpha \ -t docker.stirlingpdf.com/stirlingtools/stirling-pdf:fat \ - -f ./docker/embedded/Dockerfile.fat . + -f ./docker/embedded/Dockerfile.fat \ + --load \ + ${DOCKER_CACHE_ARGS_FAT} . else echo "Skipping fat image build - no fat tests in rerun list" fi