mirror of
https://github.com/actions/setup-java.git
synced 2026-07-30 21:26:19 +00:00
Restore dependency and wrapper caches concurrently (#1174)
* Restore dependency and wrapper caches concurrently Run primary dependency and wrapper cache restores in parallel while preserving existing outputs and save semantics. Add unit and E2E coverage for concurrent restore behavior, wrapper cache validation, and additional-cache error handling. Include a manual benchmark workflow for baseline-vs-candidate restore timing comparisons across OSes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d43ec3dd-96c7-4eb3-909e-b8123cd12d3c * Address PR review comments Use env variables for cache-hit values in benchmark record steps to avoid expression expansion in run commands, and rename E2E restore step labels for clarity. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d43ec3dd-96c7-4eb3-909e-b8123cd12d3c * Stabilize wrapper cache restore checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d43ec3dd-96c7-4eb3-909e-b8123cd12d3c
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
name: Benchmark cache restore
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
baseline-ref:
|
||||
description: Git ref containing the sequential restore implementation
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
candidate-ref:
|
||||
description: Git ref containing the concurrent restore implementation (defaults to the dispatched ref)
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
warm-caches:
|
||||
name: Warm ${{ matrix.tool }} ${{ matrix.profile }} caches (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-latest]
|
||||
tool: [maven, gradle]
|
||||
profile: [small, large]
|
||||
steps:
|
||||
- name: Checkout benchmark workflow
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Checkout baseline
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
path: baseline
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.baseline-ref }}
|
||||
- name: Checkout candidate
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
path: candidate
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.candidate-ref || github.ref }}
|
||||
- name: Prepare benchmark inputs
|
||||
run: bash __tests__/benchmark-cache-restore.sh prepare "${{ matrix.tool }}" "${{ matrix.profile }}"
|
||||
- name: Prepare cache save
|
||||
uses: ./candidate
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
cache: ${{ matrix.tool }}
|
||||
cache-dependency-path: benchmark/${{ matrix.tool == 'maven' && 'pom.xml' || 'build.gradle' }}
|
||||
- name: Populate benchmark caches
|
||||
run: |
|
||||
bash __tests__/benchmark-cache-restore.sh reset "${{ matrix.tool }}"
|
||||
bash __tests__/benchmark-cache-restore.sh populate "${{ matrix.tool }}" "${{ matrix.profile }}"
|
||||
|
||||
benchmark:
|
||||
name: Benchmark ${{ matrix.tool }} ${{ matrix.profile }} (${{ matrix.os }})
|
||||
needs: warm-caches
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-latest]
|
||||
tool: [maven, gradle]
|
||||
profile: [small, large]
|
||||
steps:
|
||||
- name: Checkout benchmark workflow
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Checkout baseline
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
path: baseline
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.baseline-ref }}
|
||||
- name: Checkout candidate
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
path: candidate
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.candidate-ref || github.ref }}
|
||||
- name: Prepare benchmark inputs
|
||||
run: bash __tests__/benchmark-cache-restore.sh prepare "${{ matrix.tool }}" "${{ matrix.profile }}"
|
||||
|
||||
- name: Reset caches for baseline iteration 1
|
||||
run: bash __tests__/benchmark-cache-restore.sh reset "${{ matrix.tool }}"
|
||||
- name: Start baseline iteration 1 timer
|
||||
run: bash __tests__/benchmark-cache-restore.sh start "${{ matrix.tool }}"
|
||||
- name: Restore with baseline iteration 1
|
||||
id: baseline-1
|
||||
uses: ./baseline
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
cache: ${{ matrix.tool }}
|
||||
cache-dependency-path: benchmark/${{ matrix.tool == 'maven' && 'pom.xml' || 'build.gradle' }}
|
||||
cache-read-only: true
|
||||
- name: Record baseline iteration 1
|
||||
env:
|
||||
CACHE_HIT: ${{ steps.baseline-1.outputs.cache-hit }}
|
||||
run: bash __tests__/benchmark-cache-restore.sh record "${{ matrix.tool }}" "${{ matrix.os }}" "${{ matrix.profile }}" baseline 1 "$CACHE_HIT"
|
||||
|
||||
- name: Reset caches for candidate iteration 1
|
||||
run: bash __tests__/benchmark-cache-restore.sh reset "${{ matrix.tool }}"
|
||||
- name: Start candidate iteration 1 timer
|
||||
run: bash __tests__/benchmark-cache-restore.sh start "${{ matrix.tool }}"
|
||||
- name: Restore with candidate iteration 1
|
||||
id: candidate-1
|
||||
uses: ./candidate
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
cache: ${{ matrix.tool }}
|
||||
cache-dependency-path: benchmark/${{ matrix.tool == 'maven' && 'pom.xml' || 'build.gradle' }}
|
||||
cache-read-only: true
|
||||
- name: Record candidate iteration 1
|
||||
env:
|
||||
CACHE_HIT: ${{ steps.candidate-1.outputs.cache-hit }}
|
||||
run: bash __tests__/benchmark-cache-restore.sh record "${{ matrix.tool }}" "${{ matrix.os }}" "${{ matrix.profile }}" candidate 1 "$CACHE_HIT"
|
||||
|
||||
- name: Reset caches for candidate iteration 2
|
||||
run: bash __tests__/benchmark-cache-restore.sh reset "${{ matrix.tool }}"
|
||||
- name: Start candidate iteration 2 timer
|
||||
run: bash __tests__/benchmark-cache-restore.sh start "${{ matrix.tool }}"
|
||||
- name: Restore with candidate iteration 2
|
||||
id: candidate-2
|
||||
uses: ./candidate
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
cache: ${{ matrix.tool }}
|
||||
cache-dependency-path: benchmark/${{ matrix.tool == 'maven' && 'pom.xml' || 'build.gradle' }}
|
||||
cache-read-only: true
|
||||
- name: Record candidate iteration 2
|
||||
env:
|
||||
CACHE_HIT: ${{ steps.candidate-2.outputs.cache-hit }}
|
||||
run: bash __tests__/benchmark-cache-restore.sh record "${{ matrix.tool }}" "${{ matrix.os }}" "${{ matrix.profile }}" candidate 2 "$CACHE_HIT"
|
||||
|
||||
- name: Reset caches for baseline iteration 2
|
||||
run: bash __tests__/benchmark-cache-restore.sh reset "${{ matrix.tool }}"
|
||||
- name: Start baseline iteration 2 timer
|
||||
run: bash __tests__/benchmark-cache-restore.sh start "${{ matrix.tool }}"
|
||||
- name: Restore with baseline iteration 2
|
||||
id: baseline-2
|
||||
uses: ./baseline
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
cache: ${{ matrix.tool }}
|
||||
cache-dependency-path: benchmark/${{ matrix.tool == 'maven' && 'pom.xml' || 'build.gradle' }}
|
||||
cache-read-only: true
|
||||
- name: Record baseline iteration 2
|
||||
env:
|
||||
CACHE_HIT: ${{ steps.baseline-2.outputs.cache-hit }}
|
||||
run: bash __tests__/benchmark-cache-restore.sh record "${{ matrix.tool }}" "${{ matrix.os }}" "${{ matrix.profile }}" baseline 2 "$CACHE_HIT"
|
||||
|
||||
- name: Reset caches for baseline iteration 3
|
||||
run: bash __tests__/benchmark-cache-restore.sh reset "${{ matrix.tool }}"
|
||||
- name: Start baseline iteration 3 timer
|
||||
run: bash __tests__/benchmark-cache-restore.sh start "${{ matrix.tool }}"
|
||||
- name: Restore with baseline iteration 3
|
||||
id: baseline-3
|
||||
uses: ./baseline
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
cache: ${{ matrix.tool }}
|
||||
cache-dependency-path: benchmark/${{ matrix.tool == 'maven' && 'pom.xml' || 'build.gradle' }}
|
||||
cache-read-only: true
|
||||
- name: Record baseline iteration 3
|
||||
env:
|
||||
CACHE_HIT: ${{ steps.baseline-3.outputs.cache-hit }}
|
||||
run: bash __tests__/benchmark-cache-restore.sh record "${{ matrix.tool }}" "${{ matrix.os }}" "${{ matrix.profile }}" baseline 3 "$CACHE_HIT"
|
||||
|
||||
- name: Reset caches for candidate iteration 3
|
||||
run: bash __tests__/benchmark-cache-restore.sh reset "${{ matrix.tool }}"
|
||||
- name: Start candidate iteration 3 timer
|
||||
run: bash __tests__/benchmark-cache-restore.sh start "${{ matrix.tool }}"
|
||||
- name: Restore with candidate iteration 3
|
||||
id: candidate-3
|
||||
uses: ./candidate
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
cache: ${{ matrix.tool }}
|
||||
cache-dependency-path: benchmark/${{ matrix.tool == 'maven' && 'pom.xml' || 'build.gradle' }}
|
||||
cache-read-only: true
|
||||
- name: Record candidate iteration 3
|
||||
env:
|
||||
CACHE_HIT: ${{ steps.candidate-3.outputs.cache-hit }}
|
||||
run: bash __tests__/benchmark-cache-restore.sh record "${{ matrix.tool }}" "${{ matrix.os }}" "${{ matrix.profile }}" candidate 3 "$CACHE_HIT"
|
||||
|
||||
- name: Summarize benchmark
|
||||
run: bash __tests__/benchmark-cache-restore.sh summarize "${{ matrix.tool }}" "$GITHUB_STEP_SUMMARY"
|
||||
- name: Upload raw timings
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: cache-restore-${{ matrix.os }}-${{ matrix.tool }}-${{ matrix.profile }}
|
||||
path: .benchmark-results/timings.csv
|
||||
if-no-files-found: error
|
||||
@@ -42,7 +42,10 @@ jobs:
|
||||
# https://github.com/actions/cache/issues/454#issuecomment-840493935
|
||||
run: |
|
||||
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
|
||||
mkdir -p "$HOME/.gradle/wrapper/dists/setup-java-e2e"
|
||||
echo "gradle wrapper cache" > "$HOME/.gradle/wrapper/dists/setup-java-e2e/payload"
|
||||
bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||
bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
|
||||
gradle-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -65,6 +68,8 @@ jobs:
|
||||
cache-read-only: true
|
||||
- name: Confirm that ~/.gradle/caches directory has been made
|
||||
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||
- name: Confirm that the Gradle Wrapper cache has been restored
|
||||
run: bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
|
||||
maven-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -86,7 +91,10 @@ jobs:
|
||||
- name: Create files to cache
|
||||
run: |
|
||||
mvn verify -f __tests__/cache/maven/pom.xml
|
||||
mkdir -p "$HOME/.m2/wrapper/dists/setup-java-e2e"
|
||||
echo "maven wrapper cache" > "$HOME/.m2/wrapper/dists/setup-java-e2e/payload"
|
||||
bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||
bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
|
||||
maven-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -109,6 +117,8 @@ jobs:
|
||||
cache-read-only: true
|
||||
- name: Confirm that ~/.m2/repository directory has been made
|
||||
run: bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||
- name: Confirm that the Maven Wrapper cache has been restored
|
||||
run: bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
|
||||
sbt-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
@@ -206,7 +216,10 @@ jobs:
|
||||
# https://github.com/actions/cache/issues/454#issuecomment-840493935
|
||||
run: |
|
||||
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
|
||||
mkdir -p "$HOME/.gradle/wrapper/dists/setup-java-e2e-gradle1"
|
||||
echo "gradle wrapper cache gradle1" > "$HOME/.gradle/wrapper/dists/setup-java-e2e-gradle1/payload"
|
||||
bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||
bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
|
||||
gradle1-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -229,6 +242,8 @@ jobs:
|
||||
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
|
||||
- name: Confirm that ~/.gradle/caches directory has been made
|
||||
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||
- name: Confirm that the Gradle Wrapper cache has been restored
|
||||
run: bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
|
||||
gradle2-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -273,7 +288,10 @@ jobs:
|
||||
- name: Create files to cache
|
||||
run: |
|
||||
mvn verify -f __tests__/cache/maven/pom.xml
|
||||
mkdir -p "$HOME/.m2/wrapper/dists/setup-java-e2e-maven1"
|
||||
echo "maven wrapper cache maven1" > "$HOME/.m2/wrapper/dists/setup-java-e2e-maven1/payload"
|
||||
bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||
bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
|
||||
maven1-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -296,6 +314,8 @@ jobs:
|
||||
cache-dependency-path: __tests__/cache/maven/pom.xml
|
||||
- name: Confirm that ~/.m2/repository directory has been made
|
||||
run: bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||
- name: Confirm that the Maven Wrapper cache has been restored
|
||||
run: bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
|
||||
maven2-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
command=${1:?command is required}
|
||||
tool=${2:?tool is required}
|
||||
|
||||
case "$tool" in
|
||||
maven)
|
||||
dependency_cache="$HOME/.m2/repository"
|
||||
wrapper_cache="$HOME/.m2/wrapper/dists"
|
||||
dependency_file="benchmark/pom.xml"
|
||||
wrapper_file="benchmark/.mvn/wrapper/maven-wrapper.properties"
|
||||
;;
|
||||
gradle)
|
||||
dependency_cache="$HOME/.gradle/caches"
|
||||
wrapper_cache="$HOME/.gradle/wrapper"
|
||||
dependency_file="benchmark/build.gradle"
|
||||
wrapper_file="benchmark/gradle/wrapper/gradle-wrapper.properties"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported tool: $tool" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$command" in
|
||||
prepare)
|
||||
profile=${3:?profile is required}
|
||||
mkdir -p "$(dirname "$dependency_file")" "$(dirname "$wrapper_file")"
|
||||
printf '// setup-java cache benchmark v1: %s\n' "$profile" > "$dependency_file"
|
||||
printf '# setup-java cache benchmark v1: %s\n' "$profile" > "$wrapper_file"
|
||||
;;
|
||||
reset)
|
||||
rm -rf "$dependency_cache" "$wrapper_cache"
|
||||
;;
|
||||
populate)
|
||||
profile=${3:?profile is required}
|
||||
case "$profile" in
|
||||
small)
|
||||
dependency_megabytes=8
|
||||
wrapper_megabytes=2
|
||||
;;
|
||||
large)
|
||||
dependency_megabytes=128
|
||||
wrapper_megabytes=32
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported profile: $profile" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
mkdir -p "$dependency_cache/setup-java-benchmark"
|
||||
mkdir -p "$wrapper_cache/setup-java-benchmark"
|
||||
dd if=/dev/urandom \
|
||||
of="$dependency_cache/setup-java-benchmark/payload" \
|
||||
bs=1048576 count="$dependency_megabytes" 2>/dev/null
|
||||
dd if=/dev/urandom \
|
||||
of="$wrapper_cache/setup-java-benchmark/payload" \
|
||||
bs=1048576 count="$wrapper_megabytes" 2>/dev/null
|
||||
;;
|
||||
start)
|
||||
node -e "require('fs').writeFileSync('.benchmark-start', String(Date.now()))"
|
||||
;;
|
||||
record)
|
||||
os=${3:?os is required}
|
||||
profile=${4:?profile is required}
|
||||
implementation=${5:?implementation is required}
|
||||
iteration=${6:?iteration is required}
|
||||
cache_hit=${7:?cache-hit output is required}
|
||||
if [ "$cache_hit" != "true" ]; then
|
||||
echo "Expected an exact dependency-cache hit for $implementation" >&2
|
||||
exit 1
|
||||
fi
|
||||
test -f "$dependency_cache/setup-java-benchmark/payload"
|
||||
started=$(cat .benchmark-start)
|
||||
finished=$(node -e "process.stdout.write(String(Date.now()))")
|
||||
elapsed=$((finished - started))
|
||||
mkdir -p .benchmark-results
|
||||
printf '%s,%s,%s,%s,%s,%s\n' \
|
||||
"$os" "$tool" "$profile" "$implementation" "$iteration" "$elapsed" \
|
||||
>> .benchmark-results/timings.csv
|
||||
;;
|
||||
summarize)
|
||||
summary_file=${3:?summary file is required}
|
||||
results_file=".benchmark-results/timings.csv"
|
||||
node --input-type=module - "$results_file" "$summary_file" <<'NODE'
|
||||
import fs from 'node:fs';
|
||||
|
||||
const [, , resultsFile, summaryFile] = process.argv;
|
||||
const rows = fs
|
||||
.readFileSync(resultsFile, 'utf8')
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const [os, tool, profile, implementation, iteration, elapsed] =
|
||||
line.split(',');
|
||||
return {os, tool, profile, implementation, iteration, elapsed: +elapsed};
|
||||
});
|
||||
const average = implementation => {
|
||||
const values = rows
|
||||
.filter(row => row.implementation === implementation)
|
||||
.map(row => row.elapsed);
|
||||
if (values.length === 0) {
|
||||
throw new Error(`No ${implementation} benchmark results were recorded`);
|
||||
}
|
||||
return Math.round(values.reduce((sum, value) => sum + value, 0) / values.length);
|
||||
};
|
||||
const baseline = average('baseline');
|
||||
const candidate = average('candidate');
|
||||
const change = (((candidate - baseline) / baseline) * 100).toFixed(1);
|
||||
const {os, tool, profile} = rows[0];
|
||||
const lines = [
|
||||
`### ${tool} ${profile} cache restore on ${os}`,
|
||||
'',
|
||||
'| Implementation | Iteration | Wall time (ms) |',
|
||||
'| --- | ---: | ---: |',
|
||||
...rows.map(
|
||||
row =>
|
||||
`| ${row.implementation} | ${row.iteration} | ${row.elapsed} |`
|
||||
),
|
||||
`| **baseline average** | | **${baseline}** |`,
|
||||
`| **candidate average** | | **${candidate}** |`,
|
||||
'',
|
||||
`Candidate change from baseline: **${change}%**`,
|
||||
''
|
||||
];
|
||||
fs.appendFileSync(summaryFile, `${lines.join('\n')}\n`);
|
||||
NODE
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported command: $command" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -237,6 +237,83 @@ describe('dependency cache', () => {
|
||||
expect(spyGlobHashFiles).toHaveBeenCalledWith(
|
||||
'**/.mvn/wrapper/maven-wrapper.properties'
|
||||
);
|
||||
expect(spyInfo).toHaveBeenCalledWith(
|
||||
'maven-wrapper cache is not found'
|
||||
);
|
||||
});
|
||||
it('starts maven dependency and wrapper restores before either completes', async () => {
|
||||
createDirectory(join(workspace, '.mvn'));
|
||||
createDirectory(join(workspace, '.mvn', 'wrapper'));
|
||||
createFile(
|
||||
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
|
||||
);
|
||||
const dependencyRestore = deferred<string | undefined>();
|
||||
const wrapperRestore = deferred<string | undefined>();
|
||||
const bothRestoresStarted = deferred<void>();
|
||||
let restoreCount = 0;
|
||||
spyCacheRestore.mockImplementation((paths: string[]) => {
|
||||
restoreCount++;
|
||||
if (restoreCount === 2) {
|
||||
bothRestoresStarted.resolve();
|
||||
}
|
||||
return paths.includes(join(os.homedir(), '.m2', 'repository'))
|
||||
? dependencyRestore.promise
|
||||
: wrapperRestore.promise;
|
||||
});
|
||||
|
||||
const restorePromise = restore('maven', '');
|
||||
await bothRestoresStarted.promise;
|
||||
|
||||
expect(spyCacheRestore).toHaveBeenCalledTimes(2);
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-primary-key',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-primary-key-maven-wrapper',
|
||||
expect.any(String)
|
||||
);
|
||||
|
||||
wrapperRestore.resolve('maven-wrapper-hit');
|
||||
dependencyRestore.resolve('maven-dependency-hit');
|
||||
await restorePromise;
|
||||
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-matched-key-maven-wrapper',
|
||||
'maven-wrapper-hit'
|
||||
);
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-matched-key',
|
||||
'maven-dependency-hit'
|
||||
);
|
||||
expect(spySetOutput).toHaveBeenCalledWith('cache-hit', false);
|
||||
});
|
||||
it('propagates a wrapper restore failure after starting both restores', async () => {
|
||||
createDirectory(join(workspace, '.mvn'));
|
||||
createDirectory(join(workspace, '.mvn', 'wrapper'));
|
||||
createFile(
|
||||
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
|
||||
);
|
||||
const dependencyRestore = deferred<string | undefined>();
|
||||
const wrapperRestore = deferred<string | undefined>();
|
||||
const bothRestoresStarted = deferred<void>();
|
||||
let restoreCount = 0;
|
||||
spyCacheRestore.mockImplementation((paths: string[]) => {
|
||||
restoreCount++;
|
||||
if (restoreCount === 2) {
|
||||
bothRestoresStarted.resolve();
|
||||
}
|
||||
return paths.includes(join(os.homedir(), '.m2', 'repository'))
|
||||
? dependencyRestore.promise
|
||||
: wrapperRestore.promise;
|
||||
});
|
||||
|
||||
const restorePromise = restore('maven', '');
|
||||
await bothRestoresStarted.promise;
|
||||
wrapperRestore.reject(new Error('wrapper restore failed'));
|
||||
dependencyRestore.resolve(undefined);
|
||||
|
||||
await expect(restorePromise).rejects.toThrow('wrapper restore failed');
|
||||
});
|
||||
it('skips the maven wrapper cache when no wrapper properties exist', async () => {
|
||||
createFile(join(workspace, 'pom.xml'));
|
||||
@@ -333,6 +410,50 @@ describe('dependency cache', () => {
|
||||
'**/gradle-wrapper.properties'
|
||||
);
|
||||
});
|
||||
it('starts gradle dependency and wrapper restores before either completes', async () => {
|
||||
createFile(join(workspace, 'build.gradle'));
|
||||
createFile(join(workspace, 'gradle-wrapper.properties'));
|
||||
const dependencyRestore = deferred<string | undefined>();
|
||||
const wrapperRestore = deferred<string | undefined>();
|
||||
const bothRestoresStarted = deferred<void>();
|
||||
let restoreCount = 0;
|
||||
spyCacheRestore.mockImplementation((paths: string[]) => {
|
||||
restoreCount++;
|
||||
if (restoreCount === 2) {
|
||||
bothRestoresStarted.resolve();
|
||||
}
|
||||
return paths.includes(join(os.homedir(), '.gradle', 'caches'))
|
||||
? dependencyRestore.promise
|
||||
: wrapperRestore.promise;
|
||||
});
|
||||
|
||||
const restorePromise = restore('gradle', '');
|
||||
await bothRestoresStarted.promise;
|
||||
|
||||
expect(spyCacheRestore).toHaveBeenCalledTimes(2);
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-primary-key',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-primary-key-gradle-wrapper',
|
||||
expect.any(String)
|
||||
);
|
||||
|
||||
dependencyRestore.resolve('gradle-dependency-hit');
|
||||
wrapperRestore.resolve('gradle-wrapper-hit');
|
||||
await restorePromise;
|
||||
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-matched-key',
|
||||
'gradle-dependency-hit'
|
||||
);
|
||||
expect(spySaveState).toHaveBeenCalledWith(
|
||||
'cache-matched-key-gradle-wrapper',
|
||||
'gradle-wrapper-hit'
|
||||
);
|
||||
expect(spySetOutput).toHaveBeenCalledWith('cache-hit', false);
|
||||
});
|
||||
it('skips the gradle wrapper cache when no wrapper properties exist', async () => {
|
||||
createFile(join(workspace, 'build.gradle'));
|
||||
spyGlobHashFiles.mockImplementation((pattern: string) =>
|
||||
@@ -609,6 +730,36 @@ describe('dependency cache', () => {
|
||||
);
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
it('continues with primary cache save when additional cache save fails unexpectedly', async () => {
|
||||
createFile(join(workspace, 'pom.xml'));
|
||||
(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-maven-wrapper':
|
||||
return 'setup-java-maven-wrapper-key';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
spyCacheSave.mockImplementation((paths: string[], key: string) => {
|
||||
if (paths[0] === 'wrapper-path') {
|
||||
return Promise.reject(new Error('wrapper save exploded'));
|
||||
}
|
||||
return Promise.resolve(0);
|
||||
});
|
||||
|
||||
await expect(save('maven')).resolves.toBeUndefined();
|
||||
expect(spyWarning).toHaveBeenCalledWith(
|
||||
'Failed to save maven-wrapper cache: wrapper save exploded. Continuing with primary cache save.'
|
||||
);
|
||||
expect(spyCacheSave).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.m2', 'repository')],
|
||||
'setup-java-cache-primary-key'
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('for gradle', () => {
|
||||
it('uploads cache even if no build.gradle found', async () => {
|
||||
@@ -817,6 +968,16 @@ function createFile(path: string) {
|
||||
fs.writeFileSync(path, '');
|
||||
}
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void;
|
||||
let reject!: (reason?: unknown) => void;
|
||||
const promise = new Promise<T>((promiseResolve, promiseReject) => {
|
||||
resolve = promiseResolve;
|
||||
reject = promiseReject;
|
||||
});
|
||||
return {promise, resolve, reject};
|
||||
}
|
||||
|
||||
function createDirectory(path: string) {
|
||||
core.info(`created a directory at ${path}`);
|
||||
fs.mkdirSync(path);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
@@ -0,0 +1 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
|
||||
Vendored
+43
-18
@@ -97899,10 +97899,23 @@ async function computeAdditionalCacheKey(additionalCache) {
|
||||
*/
|
||||
async function restore(id, cacheDependencyPath) {
|
||||
const packageManager = findPackageManager(id);
|
||||
const primaryKey = await computeCacheKey(packageManager, cacheDependencyPath);
|
||||
const [primaryKey, preparedAdditionalCaches] = await Promise.all([
|
||||
computeCacheKey(packageManager, cacheDependencyPath),
|
||||
prepareAdditionalCaches(packageManager.additionalCaches ?? [])
|
||||
]);
|
||||
core.debug(`primary key is ${primaryKey}`);
|
||||
core.saveState(STATE_CACHE_PRIMARY_KEY, primaryKey);
|
||||
core.setOutput(STATE_CACHE_PRIMARY_KEY, primaryKey);
|
||||
for (const preparedCache of preparedAdditionalCaches) {
|
||||
core.debug(`${preparedCache.cache.name} primary key is ${preparedCache.primaryKey}`);
|
||||
core.saveState(additionalCachePrimaryKeyState(preparedCache.cache.name), preparedCache.primaryKey);
|
||||
}
|
||||
await Promise.all([
|
||||
restorePrimaryCache(packageManager, primaryKey),
|
||||
...preparedAdditionalCaches.map(preparedCache => restoreAdditionalCache(preparedCache))
|
||||
]);
|
||||
}
|
||||
async function restorePrimaryCache(packageManager, primaryKey) {
|
||||
// No "restoreKeys" is set, to start with a clear cache after dependency update (see https://github.com/actions/setup-java/issues/269)
|
||||
const matchedKey = await cache.restoreCache(packageManager.path, primaryKey);
|
||||
if (matchedKey) {
|
||||
@@ -97914,29 +97927,35 @@ async function restore(id, cacheDependencyPath) {
|
||||
core.setOutput('cache-hit', false);
|
||||
core.info(`${packageManager.id} cache is not found`);
|
||||
}
|
||||
for (const additionalCache of packageManager.additionalCaches ?? []) {
|
||||
await restoreAdditionalCache(additionalCache);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
|
||||
* keyed independently of the main dependency cache so that it survives changes
|
||||
* to volatile dependency files. Skips silently when the project does not use
|
||||
* the corresponding feature.
|
||||
* Compute keys for additional caches (e.g. build-tool wrapper distributions).
|
||||
* Additional caches without a matching configuration file are omitted.
|
||||
*/
|
||||
async function restoreAdditionalCache(additionalCache) {
|
||||
const primaryKey = await computeAdditionalCacheKey(additionalCache);
|
||||
if (!primaryKey) {
|
||||
core.debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
|
||||
return;
|
||||
}
|
||||
core.debug(`${additionalCache.name} primary key is ${primaryKey}`);
|
||||
core.saveState(additionalCachePrimaryKeyState(additionalCache.name), primaryKey);
|
||||
async function prepareAdditionalCaches(additionalCaches) {
|
||||
const preparedCaches = await Promise.all(additionalCaches.map(async (additionalCache) => {
|
||||
const primaryKey = await computeAdditionalCacheKey(additionalCache);
|
||||
if (!primaryKey) {
|
||||
core.debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
|
||||
return undefined;
|
||||
}
|
||||
return { cache: additionalCache, primaryKey };
|
||||
}));
|
||||
return preparedCaches.filter((preparedCache) => preparedCache !== undefined);
|
||||
}
|
||||
/**
|
||||
* Restore an additional cache keyed independently of the main dependency cache.
|
||||
*/
|
||||
async function restoreAdditionalCache(preparedCache) {
|
||||
const { cache: additionalCache, primaryKey } = preparedCache;
|
||||
const matchedKey = await cache.restoreCache(additionalCache.path, primaryKey);
|
||||
if (matchedKey) {
|
||||
core.saveState(additionalCacheMatchedKeyState(additionalCache.name), matchedKey);
|
||||
core.info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
|
||||
}
|
||||
else {
|
||||
core.info(`${additionalCache.name} cache is not found`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Save the dependency cache
|
||||
@@ -97948,7 +97967,13 @@ async function save(id) {
|
||||
// Inputs are re-evaluated before the post action, so we want the original key used for restore
|
||||
const primaryKey = getState(STATE_CACHE_PRIMARY_KEY);
|
||||
for (const additionalCache of packageManager.additionalCaches ?? []) {
|
||||
await saveAdditionalCache(packageManager, additionalCache);
|
||||
try {
|
||||
await saveAdditionalCache(packageManager, additionalCache);
|
||||
}
|
||||
catch (error) {
|
||||
const err = error;
|
||||
warning(`Failed to save ${additionalCache.name} cache: ${err.message}. Continuing with primary cache save.`);
|
||||
}
|
||||
}
|
||||
if (!primaryKey) {
|
||||
warning('Error retrieving key from state.');
|
||||
@@ -98030,7 +98055,7 @@ async function saveAdditionalCache(packageManager, additionalCache) {
|
||||
}
|
||||
else {
|
||||
if (isProbablyGradleDaemonProblem(packageManager, err)) {
|
||||
warning('Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.');
|
||||
warning(`Failed to save ${additionalCache.name} cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with \`--no-daemon\` option. Refer to https://github.com/actions/cache/issues/454 for details.`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
Vendored
+43
-18
@@ -129154,10 +129154,23 @@ async function computeAdditionalCacheKey(additionalCache) {
|
||||
*/
|
||||
async function restore(id, cacheDependencyPath) {
|
||||
const packageManager = findPackageManager(id);
|
||||
const primaryKey = await computeCacheKey(packageManager, cacheDependencyPath);
|
||||
const [primaryKey, preparedAdditionalCaches] = await Promise.all([
|
||||
computeCacheKey(packageManager, cacheDependencyPath),
|
||||
prepareAdditionalCaches(packageManager.additionalCaches ?? [])
|
||||
]);
|
||||
core_debug(`primary key is ${primaryKey}`);
|
||||
saveState(STATE_CACHE_PRIMARY_KEY, primaryKey);
|
||||
setOutput(STATE_CACHE_PRIMARY_KEY, primaryKey);
|
||||
for (const preparedCache of preparedAdditionalCaches) {
|
||||
core_debug(`${preparedCache.cache.name} primary key is ${preparedCache.primaryKey}`);
|
||||
saveState(additionalCachePrimaryKeyState(preparedCache.cache.name), preparedCache.primaryKey);
|
||||
}
|
||||
await Promise.all([
|
||||
restorePrimaryCache(packageManager, primaryKey),
|
||||
...preparedAdditionalCaches.map(preparedCache => restoreAdditionalCache(preparedCache))
|
||||
]);
|
||||
}
|
||||
async function restorePrimaryCache(packageManager, primaryKey) {
|
||||
// No "restoreKeys" is set, to start with a clear cache after dependency update (see https://github.com/actions/setup-java/issues/269)
|
||||
const matchedKey = await restoreCache(packageManager.path, primaryKey);
|
||||
if (matchedKey) {
|
||||
@@ -129169,29 +129182,35 @@ async function restore(id, cacheDependencyPath) {
|
||||
setOutput('cache-hit', false);
|
||||
info(`${packageManager.id} cache is not found`);
|
||||
}
|
||||
for (const additionalCache of packageManager.additionalCaches ?? []) {
|
||||
await restoreAdditionalCache(additionalCache);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
|
||||
* keyed independently of the main dependency cache so that it survives changes
|
||||
* to volatile dependency files. Skips silently when the project does not use
|
||||
* the corresponding feature.
|
||||
* Compute keys for additional caches (e.g. build-tool wrapper distributions).
|
||||
* Additional caches without a matching configuration file are omitted.
|
||||
*/
|
||||
async function restoreAdditionalCache(additionalCache) {
|
||||
const primaryKey = await computeAdditionalCacheKey(additionalCache);
|
||||
if (!primaryKey) {
|
||||
core_debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
|
||||
return;
|
||||
}
|
||||
core_debug(`${additionalCache.name} primary key is ${primaryKey}`);
|
||||
saveState(additionalCachePrimaryKeyState(additionalCache.name), primaryKey);
|
||||
async function prepareAdditionalCaches(additionalCaches) {
|
||||
const preparedCaches = await Promise.all(additionalCaches.map(async (additionalCache) => {
|
||||
const primaryKey = await computeAdditionalCacheKey(additionalCache);
|
||||
if (!primaryKey) {
|
||||
core_debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
|
||||
return undefined;
|
||||
}
|
||||
return { cache: additionalCache, primaryKey };
|
||||
}));
|
||||
return preparedCaches.filter((preparedCache) => preparedCache !== undefined);
|
||||
}
|
||||
/**
|
||||
* Restore an additional cache keyed independently of the main dependency cache.
|
||||
*/
|
||||
async function restoreAdditionalCache(preparedCache) {
|
||||
const { cache: additionalCache, primaryKey } = preparedCache;
|
||||
const matchedKey = await restoreCache(additionalCache.path, primaryKey);
|
||||
if (matchedKey) {
|
||||
saveState(additionalCacheMatchedKeyState(additionalCache.name), matchedKey);
|
||||
info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
|
||||
}
|
||||
else {
|
||||
info(`${additionalCache.name} cache is not found`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Save the dependency cache
|
||||
@@ -129203,7 +129222,13 @@ async function save(id) {
|
||||
// Inputs are re-evaluated before the post action, so we want the original key used for restore
|
||||
const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY);
|
||||
for (const additionalCache of packageManager.additionalCaches ?? []) {
|
||||
await saveAdditionalCache(packageManager, additionalCache);
|
||||
try {
|
||||
await saveAdditionalCache(packageManager, additionalCache);
|
||||
}
|
||||
catch (error) {
|
||||
const err = error;
|
||||
core.warning(`Failed to save ${additionalCache.name} cache: ${err.message}. Continuing with primary cache save.`);
|
||||
}
|
||||
}
|
||||
if (!primaryKey) {
|
||||
core.warning('Error retrieving key from state.');
|
||||
@@ -129285,7 +129310,7 @@ async function saveAdditionalCache(packageManager, additionalCache) {
|
||||
}
|
||||
else {
|
||||
if (isProbablyGradleDaemonProblem(packageManager, err)) {
|
||||
core.warning('Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.');
|
||||
core.warning(`Failed to save ${additionalCache.name} cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with \`--no-daemon\` option. Refer to https://github.com/actions/cache/issues/454 for details.`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
+70
-23
@@ -36,6 +36,11 @@ interface AdditionalCache {
|
||||
pattern: string[];
|
||||
}
|
||||
|
||||
interface PreparedAdditionalCache {
|
||||
cache: AdditionalCache;
|
||||
primaryKey: string;
|
||||
}
|
||||
|
||||
interface PackageManager {
|
||||
id: 'maven' | 'gradle' | 'sbt';
|
||||
/**
|
||||
@@ -189,11 +194,37 @@ async function computeAdditionalCacheKey(
|
||||
*/
|
||||
export async function restore(id: string, cacheDependencyPath: string) {
|
||||
const packageManager = findPackageManager(id);
|
||||
const primaryKey = await computeCacheKey(packageManager, cacheDependencyPath);
|
||||
const [primaryKey, preparedAdditionalCaches] = await Promise.all([
|
||||
computeCacheKey(packageManager, cacheDependencyPath),
|
||||
prepareAdditionalCaches(packageManager.additionalCaches ?? [])
|
||||
]);
|
||||
|
||||
core.debug(`primary key is ${primaryKey}`);
|
||||
core.saveState(STATE_CACHE_PRIMARY_KEY, primaryKey);
|
||||
core.setOutput(STATE_CACHE_PRIMARY_KEY, primaryKey);
|
||||
|
||||
for (const preparedCache of preparedAdditionalCaches) {
|
||||
core.debug(
|
||||
`${preparedCache.cache.name} primary key is ${preparedCache.primaryKey}`
|
||||
);
|
||||
core.saveState(
|
||||
additionalCachePrimaryKeyState(preparedCache.cache.name),
|
||||
preparedCache.primaryKey
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
restorePrimaryCache(packageManager, primaryKey),
|
||||
...preparedAdditionalCaches.map(preparedCache =>
|
||||
restoreAdditionalCache(preparedCache)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
async function restorePrimaryCache(
|
||||
packageManager: PackageManager,
|
||||
primaryKey: string
|
||||
) {
|
||||
// No "restoreKeys" is set, to start with a clear cache after dependency update (see https://github.com/actions/setup-java/issues/269)
|
||||
const matchedKey = await cache.restoreCache(packageManager.path, primaryKey);
|
||||
if (matchedKey) {
|
||||
@@ -204,32 +235,39 @@ export async function restore(id: string, cacheDependencyPath: string) {
|
||||
core.setOutput('cache-hit', false);
|
||||
core.info(`${packageManager.id} cache is not found`);
|
||||
}
|
||||
|
||||
for (const additionalCache of packageManager.additionalCaches ?? []) {
|
||||
await restoreAdditionalCache(additionalCache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
|
||||
* keyed independently of the main dependency cache so that it survives changes
|
||||
* to volatile dependency files. Skips silently when the project does not use
|
||||
* the corresponding feature.
|
||||
* Compute keys for additional caches (e.g. build-tool wrapper distributions).
|
||||
* Additional caches without a matching configuration file are omitted.
|
||||
*/
|
||||
async function restoreAdditionalCache(additionalCache: AdditionalCache) {
|
||||
const primaryKey = await computeAdditionalCacheKey(additionalCache);
|
||||
if (!primaryKey) {
|
||||
core.debug(
|
||||
`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
core.debug(`${additionalCache.name} primary key is ${primaryKey}`);
|
||||
core.saveState(
|
||||
additionalCachePrimaryKeyState(additionalCache.name),
|
||||
primaryKey
|
||||
async function prepareAdditionalCaches(
|
||||
additionalCaches: AdditionalCache[]
|
||||
): Promise<PreparedAdditionalCache[]> {
|
||||
const preparedCaches = await Promise.all(
|
||||
additionalCaches.map(async additionalCache => {
|
||||
const primaryKey = await computeAdditionalCacheKey(additionalCache);
|
||||
if (!primaryKey) {
|
||||
core.debug(
|
||||
`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return {cache: additionalCache, primaryKey};
|
||||
})
|
||||
);
|
||||
|
||||
return preparedCaches.filter(
|
||||
(preparedCache): preparedCache is PreparedAdditionalCache =>
|
||||
preparedCache !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore an additional cache keyed independently of the main dependency cache.
|
||||
*/
|
||||
async function restoreAdditionalCache(preparedCache: PreparedAdditionalCache) {
|
||||
const {cache: additionalCache, primaryKey} = preparedCache;
|
||||
const matchedKey = await cache.restoreCache(additionalCache.path, primaryKey);
|
||||
if (matchedKey) {
|
||||
core.saveState(
|
||||
@@ -237,6 +275,8 @@ async function restoreAdditionalCache(additionalCache: AdditionalCache) {
|
||||
matchedKey
|
||||
);
|
||||
core.info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
|
||||
} else {
|
||||
core.info(`${additionalCache.name} cache is not found`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +292,14 @@ export async function save(id: string) {
|
||||
const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY);
|
||||
|
||||
for (const additionalCache of packageManager.additionalCaches ?? []) {
|
||||
await saveAdditionalCache(packageManager, additionalCache);
|
||||
try {
|
||||
await saveAdditionalCache(packageManager, additionalCache);
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
core.warning(
|
||||
`Failed to save ${additionalCache.name} cache: ${err.message}. Continuing with primary cache save.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!primaryKey) {
|
||||
@@ -360,7 +407,7 @@ async function saveAdditionalCache(
|
||||
} else {
|
||||
if (isProbablyGradleDaemonProblem(packageManager, err)) {
|
||||
core.warning(
|
||||
'Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.'
|
||||
`Failed to save ${additionalCache.name} cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with \`--no-daemon\` option. Refer to https://github.com/actions/cache/issues/454 for details.`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user