mirror of
https://github.com/actions/setup-java.git
synced 2026-07-30 09:10:01 +08:00
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
This commit is contained in:
@@ -62,6 +62,7 @@ jobs:
|
|||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
java-version: '11'
|
java-version: '11'
|
||||||
cache: gradle
|
cache: gradle
|
||||||
|
cache-read-only: true
|
||||||
- name: Confirm that ~/.gradle/caches directory has been made
|
- name: Confirm that ~/.gradle/caches directory has been made
|
||||||
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||||
maven-save:
|
maven-save:
|
||||||
@@ -105,6 +106,7 @@ jobs:
|
|||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
java-version: '11'
|
java-version: '11'
|
||||||
cache: maven
|
cache: maven
|
||||||
|
cache-read-only: true
|
||||||
- name: Confirm that ~/.m2/repository directory has been made
|
- name: Confirm that ~/.m2/repository directory has been made
|
||||||
run: bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
run: bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||||
sbt-save:
|
sbt-save:
|
||||||
@@ -169,6 +171,7 @@ jobs:
|
|||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
java-version: '11'
|
java-version: '11'
|
||||||
cache: sbt
|
cache: sbt
|
||||||
|
cache-read-only: true
|
||||||
|
|
||||||
- name: Confirm that ~/Library/Caches/Coursier directory has been made
|
- name: Confirm that ~/Library/Caches/Coursier directory has been made
|
||||||
if: matrix.os == 'macos-15-intel'
|
if: matrix.os == 'macos-15-intel'
|
||||||
|
|||||||
@@ -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-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
|
#### 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.
|
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.
|
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.
|
**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
|
#### Caching gradle dependencies
|
||||||
|
|||||||
@@ -120,6 +120,49 @@ describe('cleanup', () => {
|
|||||||
await cleanup();
|
await cleanup();
|
||||||
expect(spyCacheSave).toHaveBeenCalled();
|
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<any>).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<any>).mockImplementation((name: string) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'cache':
|
||||||
|
return 'maven';
|
||||||
|
case 'cache-read-only':
|
||||||
|
return 'false';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await cleanup();
|
||||||
|
|
||||||
|
expect(spyCacheSave).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function resetState() {
|
function resetState() {
|
||||||
@@ -141,3 +184,18 @@ function createStateForSuccessfulRestore() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createStateForSuccessfulRestoreWithWrapper(packageManager: string) {
|
||||||
|
(core.getState as jest.Mock<any>).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 '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ inputs:
|
|||||||
cache-dependency-path:
|
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.'
|
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
|
required: false
|
||||||
|
cache-read-only:
|
||||||
|
description: 'Restore dependency caches without saving cache changes in the post action.'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
job-status:
|
job-status:
|
||||||
description: 'Workaround to pass job status to post job step. This variable is not intended for manual setting'
|
description: 'Workaround to pass job status to post job step. This variable is not intended for manual setting'
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
Vendored
+10
-2
@@ -97338,6 +97338,7 @@ const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
|
|||||||
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
|
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
|
||||||
const INPUT_CACHE = 'cache';
|
const INPUT_CACHE = 'cache';
|
||||||
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
|
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
|
||||||
|
const INPUT_CACHE_READ_ONLY = 'cache-read-only';
|
||||||
const INPUT_JOB_STATUS = 'job-status';
|
const INPUT_JOB_STATUS = 'job-status';
|
||||||
const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
|
const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
|
||||||
const M2_DIR = '.m2';
|
const M2_DIR = '.m2';
|
||||||
@@ -97365,7 +97366,7 @@ function getTempDir() {
|
|||||||
return tempDirectory;
|
return tempDirectory;
|
||||||
}
|
}
|
||||||
function util_getBooleanInput(inputName, defaultValue = false) {
|
function util_getBooleanInput(inputName, defaultValue = false) {
|
||||||
const inputValue = core.getInput(inputName);
|
const inputValue = getInput(inputName);
|
||||||
const normalizedValue = inputValue.trim().toLowerCase();
|
const normalizedValue = inputValue.trim().toLowerCase();
|
||||||
if (!normalizedValue) {
|
if (!normalizedValue) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@@ -98076,7 +98077,14 @@ async function removePrivateKeyFromKeychain() {
|
|||||||
async function cleanup_java_saveCache() {
|
async function cleanup_java_saveCache() {
|
||||||
const jobStatus = isJobStatusSuccess();
|
const jobStatus = isJobStatusSuccess();
|
||||||
const cache = getInput(INPUT_CACHE);
|
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
|
* The save process is best-effort, and it should not make the workflow fail
|
||||||
|
|||||||
Vendored
+1
@@ -72120,6 +72120,7 @@ const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
|
|||||||
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
|
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
|
||||||
const INPUT_CACHE = 'cache';
|
const INPUT_CACHE = 'cache';
|
||||||
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
|
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
|
||||||
|
const INPUT_CACHE_READ_ONLY = 'cache-read-only';
|
||||||
const constants_INPUT_JOB_STATUS = 'job-status';
|
const constants_INPUT_JOB_STATUS = 'job-status';
|
||||||
const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
|
const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
|
||||||
const M2_DIR = '.m2';
|
const M2_DIR = '.m2';
|
||||||
|
|||||||
+11
-2
@@ -1,7 +1,7 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import * as gpg from './gpg.js';
|
import * as gpg from './gpg.js';
|
||||||
import * as constants from './constants.js';
|
import * as constants from './constants.js';
|
||||||
import {isJobStatusSuccess} from './util.js';
|
import {getBooleanInput, isJobStatusSuccess} from './util.js';
|
||||||
import {save} from './cache.js';
|
import {save} from './cache.js';
|
||||||
import {fileURLToPath} from 'url';
|
import {fileURLToPath} from 'url';
|
||||||
|
|
||||||
@@ -28,7 +28,16 @@ async function removePrivateKeyFromKeychain() {
|
|||||||
async function saveCache() {
|
async function saveCache() {
|
||||||
const jobStatus = isJobStatusSuccess();
|
const jobStatus = isJobStatusSuccess();
|
||||||
const cache = core.getInput(constants.INPUT_CACHE);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
|
|||||||
|
|
||||||
export const INPUT_CACHE = 'cache';
|
export const INPUT_CACHE = 'cache';
|
||||||
export const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
|
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 INPUT_JOB_STATUS = 'job-status';
|
||||||
|
|
||||||
export const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
|
export const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
|
||||||
|
|||||||
Reference in New Issue
Block a user