From bcd3ba3d321929a5a2532eec5a79b9eca32027e6 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 29 Jul 2026 10:17:23 -0400 Subject: [PATCH 1/3] Reduce change-time Java E2E matrix (#1170) Run a representative smoke matrix on pull requests and main while reserving the exhaustive compatibility matrix for scheduled, manual, and release-branch runs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 235c81b4-58ae-494e-9907-e19c841c6a53 --- .github/workflows/e2e-smoke.yml | 63 ++++++++++++++++++++++++++++++ .github/workflows/e2e-versions.yml | 4 -- 2 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/e2e-smoke.yml diff --git a/.github/workflows/e2e-smoke.yml b/.github/workflows/e2e-smoke.yml new file mode 100644 index 000000000..65314acb9 --- /dev/null +++ b/.github/workflows/e2e-smoke.yml @@ -0,0 +1,63 @@ +name: Validate Java e2e smoke + +on: + push: + branches: + - main + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + +permissions: + contents: read + +jobs: + setup-java: + name: ${{ matrix.distribution }} ${{ matrix.version }} (${{ matrix.java-package }}) - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + distribution: temurin + version: '11' + java-package: jdk + - os: windows-latest + distribution: temurin + version: '17' + java-package: jdk + - os: ubuntu-latest + distribution: temurin + version: '21' + java-package: jdk + - os: ubuntu-latest + distribution: zulu + version: '17' + java-package: jre + - os: ubuntu-latest + distribution: liberica + version: '21' + java-package: jdk+fx + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + - name: setup-java + uses: ./ + id: setup-java + with: + java-version: ${{ matrix.version }} + java-package: ${{ matrix.java-package }} + distribution: ${{ matrix.distribution }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Verify Java + env: + JAVA_VERSION: ${{ matrix.version }} + JAVA_PATH: ${{ steps.setup-java.outputs.path }} + run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH" + shell: bash diff --git a/.github/workflows/e2e-versions.yml b/.github/workflows/e2e-versions.yml index 47f93d9fd..197a13c15 100644 --- a/.github/workflows/e2e-versions.yml +++ b/.github/workflows/e2e-versions.yml @@ -3,13 +3,9 @@ name: Validate Java e2e on: push: branches: - - main - releases/* paths-ignore: - '**.md' - pull_request: - paths-ignore: - - '**.md' schedule: - cron: '0 */12 * * *' workflow_dispatch: From 62f345fa337c49a87dbab87c134fae4b4d55377e Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 29 Jul 2026 10:20:14 -0400 Subject: [PATCH 2/3] Add read-only dependency cache mode (#1169) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b3f6f152-8ac4-4c29-b04a-acac8e100777 --- .github/workflows/e2e-cache.yml | 3 ++ README.md | 50 ++++++++++++++++++++++++++++ __tests__/cleanup-java.test.ts | 58 +++++++++++++++++++++++++++++++++ action.yml | 4 +++ dist/cleanup/index.js | 12 +++++-- dist/setup/index.js | 1 + src/cleanup-java.ts | 13 ++++++-- src/constants.ts | 1 + 8 files changed, 138 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml index 07a1c644d..a60fde6da 100644 --- a/.github/workflows/e2e-cache.yml +++ b/.github/workflows/e2e-cache.yml @@ -62,6 +62,7 @@ jobs: distribution: 'adopt' java-version: '11' cache: gradle + cache-read-only: true - name: Confirm that ~/.gradle/caches directory has been made run: bash __tests__/check-dir.sh "$HOME/.gradle/caches" maven-save: @@ -105,6 +106,7 @@ jobs: distribution: 'adopt' java-version: '11' cache: maven + cache-read-only: true - name: Confirm that ~/.m2/repository directory has been made run: bash __tests__/check-dir.sh "$HOME/.m2/repository" sbt-save: @@ -169,6 +171,7 @@ jobs: distribution: 'adopt' java-version: '11' cache: sbt + cache-read-only: true - name: Confirm that ~/Library/Caches/Coursier directory has been made if: matrix.os == 'macos-15-intel' diff --git a/README.md b/README.md index bebb93861..27e7de466 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ For more details, see the full release notes on the [releases page](https://git - `cache-dependency-path`: The path to a dependency file: pom.xml, build.gradle, build.sbt, etc. This option can be used with the `cache` option. If this option is omitted, the action searches for the dependency file in the entire repository. This option supports wildcards and a list of file names for caching multiple dependencies. + - `cache-read-only`: Restore dependency caches without saving changes in the post action. Defaults to `false`. Use this for pull requests, merge queues, short-lived branches, and fan-out jobs that should consume caches populated by a default-branch or seed job. + #### Maven options The action has a bunch of inputs to generate maven's [settings.xml](https://maven.apache.org/settings.html) on the fly and pass the values to Apache Maven GPG Plugin as well as Apache Maven Toolchains. See [advanced usage](docs/advanced-usage.md) for more. @@ -192,6 +194,54 @@ The workflow output `cache-primary-key` exposes the primary cache key computed b The cache input is optional, and caching is turned off by default. +Set `cache-read-only: true` to restore the main dependency cache and any Maven +or Gradle wrapper cache without archiving or uploading changes after the job. +For example, a workflow can allow only the default branch to write caches while +pull requests, merge queues, and short-lived branches remain read-only: + +```yaml +- uses: actions/setup-java@v6 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + cache-read-only: ${{ github.ref != 'refs/heads/main' }} +``` + +For a fan-out matrix, use one seed job to populate a complete cache and make +every matrix job a read-only consumer. The seed and consumers must use the same +runner OS and cache dependency inputs so they compute the same key: + +```yaml +jobs: + seed-cache: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + - run: mvn dependency:go-offline dependency:resolve-plugins + + build: + needs: seed-cache + runs-on: ubuntu-latest + strategy: + matrix: + goal: [test, verify, package] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + cache-read-only: true + - run: mvn ${{ matrix.goal }} +``` + **Maven Wrapper:** when `cache: 'maven'` is enabled, the action also caches and restores the Maven Wrapper distribution downloaded to `~/.m2/wrapper/dists` (in addition to the local repository), so wrapper-based (`./mvnw`) builds don't re-download the Maven distribution. The wrapper distribution is stored in a **separate** cache entry keyed only on `**/.mvn/wrapper/maven-wrapper.properties`, so it stays cached across the frequent `pom.xml` changes that rotate the main dependency cache key. #### Caching gradle dependencies diff --git a/__tests__/cleanup-java.test.ts b/__tests__/cleanup-java.test.ts index 65b754980..8a4c04585 100644 --- a/__tests__/cleanup-java.test.ts +++ b/__tests__/cleanup-java.test.ts @@ -120,6 +120,49 @@ describe('cleanup', () => { await cleanup(); expect(spyCacheSave).toHaveBeenCalled(); }); + + it.each(['maven', 'gradle', 'sbt'])( + 'does not save the %s cache in read-only mode', + async packageManager => { + createStateForSuccessfulRestoreWithWrapper(packageManager); + (core.getInput as jest.Mock).mockImplementation((name: string) => { + switch (name) { + case 'cache': + return packageManager; + case 'cache-read-only': + return 'true'; + default: + return ''; + } + }); + + await cleanup(); + + expect(spyCacheSave).not.toHaveBeenCalled(); + expect(core.getState).not.toHaveBeenCalled(); + expect(spyInfo).toHaveBeenCalledWith( + 'Cache saving is skipped because cache-read-only is enabled.' + ); + } + ); + + it('saves the cache when read-only mode is explicitly disabled', async () => { + spyCacheSave.mockResolvedValue(0); + (core.getInput as jest.Mock).mockImplementation((name: string) => { + switch (name) { + case 'cache': + return 'maven'; + case 'cache-read-only': + return 'false'; + default: + return ''; + } + }); + + await cleanup(); + + expect(spyCacheSave).toHaveBeenCalled(); + }); }); function resetState() { @@ -141,3 +184,18 @@ function createStateForSuccessfulRestore() { } }); } + +function createStateForSuccessfulRestoreWithWrapper(packageManager: string) { + (core.getState as jest.Mock).mockImplementation((name: any) => { + switch (name) { + case 'cache-primary-key': + return 'setup-java-cache-primary-key'; + case 'cache-matched-key': + return 'setup-java-cache-matched-key'; + case `cache-primary-key-${packageManager}-wrapper`: + return `setup-java-${packageManager}-wrapper-primary-key`; + default: + return ''; + } + }); +} diff --git a/action.yml b/action.yml index d980a9dd2..78ccfbce1 100644 --- a/action.yml +++ b/action.yml @@ -87,6 +87,10 @@ inputs: cache-dependency-path: description: 'The path to a dependency file: pom.xml, build.gradle, build.sbt, etc. This option can be used with the `cache` option. If this option is omitted, the action searches for the dependency file in the entire repository. This option supports wildcards and a list of file names for caching multiple dependencies.' required: false + cache-read-only: + description: 'Restore dependency caches without saving cache changes in the post action.' + required: false + default: false job-status: description: 'Workaround to pass job status to post job step. This variable is not intended for manual setting' required: false diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index b2fa7b013..5ef71f669 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -97338,6 +97338,7 @@ const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE'; const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg'; const INPUT_CACHE = 'cache'; const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; +const INPUT_CACHE_READ_ONLY = 'cache-read-only'; const INPUT_JOB_STATUS = 'job-status'; const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; const M2_DIR = '.m2'; @@ -97365,7 +97366,7 @@ function getTempDir() { return tempDirectory; } function util_getBooleanInput(inputName, defaultValue = false) { - const inputValue = core.getInput(inputName); + const inputValue = getInput(inputName); const normalizedValue = inputValue.trim().toLowerCase(); if (!normalizedValue) { return defaultValue; @@ -98076,7 +98077,14 @@ async function removePrivateKeyFromKeychain() { async function cleanup_java_saveCache() { const jobStatus = isJobStatusSuccess(); const cache = getInput(INPUT_CACHE); - return jobStatus && cache ? save(cache) : Promise.resolve(); + if (!jobStatus || !cache) { + return; + } + if (util_getBooleanInput(INPUT_CACHE_READ_ONLY, false)) { + info('Cache saving is skipped because cache-read-only is enabled.'); + return; + } + await save(cache); } /** * The save process is best-effort, and it should not make the workflow fail diff --git a/dist/setup/index.js b/dist/setup/index.js index 09aad7f94..51ae9a2d3 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -72120,6 +72120,7 @@ const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE'; const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg'; const INPUT_CACHE = 'cache'; const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; +const INPUT_CACHE_READ_ONLY = 'cache-read-only'; const constants_INPUT_JOB_STATUS = 'job-status'; const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; const M2_DIR = '.m2'; diff --git a/src/cleanup-java.ts b/src/cleanup-java.ts index 5503e26e6..0848e4178 100644 --- a/src/cleanup-java.ts +++ b/src/cleanup-java.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core'; import * as gpg from './gpg.js'; import * as constants from './constants.js'; -import {isJobStatusSuccess} from './util.js'; +import {getBooleanInput, isJobStatusSuccess} from './util.js'; import {save} from './cache.js'; import {fileURLToPath} from 'url'; @@ -28,7 +28,16 @@ async function removePrivateKeyFromKeychain() { async function saveCache() { const jobStatus = isJobStatusSuccess(); const cache = core.getInput(constants.INPUT_CACHE); - return jobStatus && cache ? save(cache) : Promise.resolve(); + if (!jobStatus || !cache) { + return; + } + + if (getBooleanInput(constants.INPUT_CACHE_READ_ONLY, false)) { + core.info('Cache saving is skipped because cache-read-only is enabled.'); + return; + } + + await save(cache); } /** diff --git a/src/constants.ts b/src/constants.ts index e1688db4a..81b29861e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -38,6 +38,7 @@ export const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg'; export const INPUT_CACHE = 'cache'; export const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; +export const INPUT_CACHE_READ_ONLY = 'cache-read-only'; export const INPUT_JOB_STATUS = 'job-status'; export const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; From ec4dbbe20d7628bf9e1fb7775821babf2a31deca Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 29 Jul 2026 10:41:44 -0400 Subject: [PATCH 3/3] Test Temurin 25 on hosted runners (#1172) * Test Temurin 25 on hosted runners Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9def8ac6-e148-4a8e-bb50-a3ee1948fc52 * Recommend Temurin for hosted runners Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9def8ac6-e148-4a8e-bb50-a3ee1948fc52 * Clarify hosted Temurin guidance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9def8ac6-e148-4a8e-bb50-a3ee1948fc52 * Test downloaded Microsoft JDKs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9def8ac6-e148-4a8e-bb50-a3ee1948fc52 --------- Copilot-Session: 9def8ac6-e148-4a8e-bb50-a3ee1948fc52 --- .github/workflows/e2e-smoke.yml | 24 ++++++++++++++++++++++++ README.md | 2 ++ docs/advanced-usage.md | 4 ++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-smoke.yml b/.github/workflows/e2e-smoke.yml index 65314acb9..fed3adf72 100644 --- a/.github/workflows/e2e-smoke.yml +++ b/.github/workflows/e2e-smoke.yml @@ -33,6 +33,30 @@ jobs: distribution: temurin version: '21' java-package: jdk + - os: macos-latest + distribution: temurin + version: '25' + java-package: jdk + - os: windows-latest + distribution: temurin + version: '25' + java-package: jdk + - os: ubuntu-latest + distribution: temurin + version: '25' + java-package: jdk + - os: macos-latest + distribution: microsoft + version: '25' + java-package: jdk + - os: windows-latest + distribution: microsoft + version: '25' + java-package: jdk + - os: ubuntu-latest + distribution: microsoft + version: '25' + java-package: jdk - os: ubuntu-latest distribution: zulu version: '17' diff --git a/README.md b/README.md index 27e7de466..38ecc35f3 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,8 @@ In the basic examples above, the `check-latest` flag defaults to `false`. When s If `check-latest` is set to `true`, the action first checks if the cached version is the latest one. If the locally cached version is not the most up-to-date, the latest version of Java will be downloaded. Set `check-latest` to `true` if you want the most up-to-date version of Java to always be used. Setting `check-latest` to `true` has performance implications as downloading versions of Java is slower than using cached versions. +[GitHub-hosted runners](https://github.com/actions/runner-images) include Eclipse Temurin JDKs in their tool cache. Selecting Eclipse Temurin (`distribution: 'temurin'`) can save setup time by using a pre-installed JDK instead of downloading one. See the installed Java versions for [Ubuntu](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#java), [Windows](https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md#java), and [macOS](https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#java). + For Java distributions that are not cached on Hosted images, `check-latest` always behaves as `true` and downloads Java on the fly. Check out [Hosted Tool Cache](docs/advanced-usage.md#Hosted-Tool-Cache) for more details about pre-cached Java versions. diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 9c4ef5de2..1a429ad67 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -890,9 +890,9 @@ See the help docs on [Publishing a Package with Gradle](https://help.github.com/ ## Hosted Tool Cache GitHub Hosted Runners have a tool cache that comes with some Java versions pre-installed. This tool cache helps speed up runs and tool setup by not requiring any new downloads. There is an environment variable called `RUNNER_TOOL_CACHE` on each runner that describes the location of this tools cache and this is where you can find the pre-installed versions of Java. `setup-java` works by taking a specific version of Java in this tool cache and adding it to PATH if the version, architecture and distribution match. -Currently, LTS versions of Eclipse Temurin (`temurin`) are cached on the GitHub Hosted Runners. +Currently, LTS versions of Eclipse Temurin (`temurin`) are cached on GitHub-hosted runners. Using a cached version avoids downloading a JDK. -The tools cache gets updated on a weekly basis. For information regarding locally cached versions of Java on GitHub hosted runners, check out [GitHub Actions Virtual Environments](https://github.com/actions/virtual-environments). +The tools cache gets updated on a weekly basis. See the installed Java versions for [Ubuntu](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#java), [Windows](https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md#java), and [macOS](https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#java). ## Modifying Maven Toolchains The `setup-java` action generates a basic [Maven Toolchains declaration](https://maven.apache.org/guides/mini/guide-using-toolchains.html) for specified Java versions by either creating a minimal toolchains file or extending an existing declaration with the additional JDKs.