Compare commits

...

6 Commits

Author SHA1 Message Date
Bruno Borges
6937f5eb31 Centralize OS/architecture capability validation (#1178)
* Centralize platform capability validation

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e

* Address PR review feedback

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e

* Regenerate dist after platform validation updates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e
2026-07-29 15:46:29 -04:00
Bruno Borges
0b56831a10 Add dependency cache path overrides (#1175)
* Add dependency cache path overrides

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dd650d36-9c97-4ca4-9ec8-39b37f99a07c

* Clarify supported dependency cache managers

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dd650d36-9c97-4ca4-9ec8-39b37f99a07c

* Fix custom cache path CI checks

Align the custom cache save and restore key inputs and use the workflow hash to avoid a previously populated cache entry. Rebuild the distribution bundles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dd650d36-9c97-4ca4-9ec8-39b37f99a07c

---------

Copilot-Session: dd650d36-9c97-4ca4-9ec8-39b37f99a07c
2026-07-29 14:43:55 -04:00
Bruno Borges
9f43141311 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
2026-07-29 13:41:24 -04:00
Bruno Borges
ec4dbbe20d 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
2026-07-29 10:41:44 -04:00
Bruno Borges
62f345fa33 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
2026-07-29 10:20:14 -04:00
Bruno Borges
bcd3ba3d32 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
2026-07-29 10:17:23 -04:00
31 changed files with 2177 additions and 301 deletions

View File

@@ -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

View File

@@ -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:
@@ -62,8 +65,11 @@ 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"
- 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:
@@ -85,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:
@@ -105,8 +114,11 @@ 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"
- 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:
@@ -169,6 +181,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'
@@ -203,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:
@@ -226,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:
@@ -270,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:
@@ -293,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:
@@ -312,7 +335,9 @@ jobs:
distribution: 'adopt'
java-version: '11'
cache: maven
cache-dependency-path: __tests__/cache/maven2/pom.xml
cache-dependency-path: |
__tests__/cache/maven2/pom.xml
README.md
- name: Confirm that ~/.m2/repository directory has not been made
run: bash __tests__/check-dir.sh "$HOME/.m2/repository" absent
sbt1-save:
@@ -423,3 +448,50 @@ jobs:
- name: Confirm that ~/.cache/coursier directory has not been made
if: matrix.os == 'ubuntu-22.04'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" absent
custom-maven-path-save:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with a custom Maven cache path
uses: ./
with:
distribution: 'adopt'
java-version: '11'
cache: maven
cache-dependency-path: |
__tests__/cache/maven2/pom.xml
.github/workflows/e2e-cache.yml
cache-path: |
${{ runner.temp }}/setup-java-custom-maven-repository
!${{ runner.temp }}/setup-java-custom-maven-repository/**/*.lastUpdated
- name: Populate the custom Maven repository
run: |
mvn -Dmaven.repo.local="$RUNNER_TEMP/setup-java-custom-maven-repository" verify -f __tests__/cache/maven2/pom.xml
touch "$RUNNER_TEMP/setup-java-custom-maven-repository/setup-java-cache-path-marker"
bash __tests__/check-dir.sh "$RUNNER_TEMP/setup-java-custom-maven-repository"
custom-maven-path-restore:
runs-on: ubuntu-latest
needs: custom-maven-path-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with a custom Maven cache path
uses: ./
with:
distribution: 'adopt'
java-version: '11'
cache: maven
cache-dependency-path: |
__tests__/cache/maven2/pom.xml
.github/workflows/e2e-cache.yml
cache-path: |
${{ runner.temp }}/setup-java-custom-maven-repository
!${{ runner.temp }}/setup-java-custom-maven-repository/**/*.lastUpdated
cache-read-only: true
- name: Confirm that the custom Maven repository has been restored
run: test -f "$RUNNER_TEMP/setup-java-custom-maven-repository/setup-java-cache-path-marker"

87
.github/workflows/e2e-smoke.yml vendored Normal file
View File

@@ -0,0 +1,87 @@
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: 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'
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

View File

@@ -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:
@@ -499,6 +495,25 @@ jobs:
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash
setup-java-unsupported-platform:
name: Reject unsupported Oracle x86 on Linux
runs-on: ubuntu-latest
steps:
- *checkout_step
- name: Attempt unsupported setup
id: unsupported-setup
continue-on-error: true
uses: ./
with:
distribution: oracle
java-version: '21'
architecture: x86
- name: Verify setup was rejected
if: always()
env:
SETUP_OUTCOME: ${{ steps.unsupported-setup.outcome }}
run: test "$SETUP_OUTCOME" = failure
setup-java-version-both-version-inputs-presents:
name: ${{ matrix.distribution }} version (should be from input) - ${{ matrix.os }}
runs-on: ${{ matrix.os }}

View File

@@ -50,7 +50,7 @@ For more details, see the full release notes on the [releases page](https://git
- `java-package`: The packaging variant of the chosen distribution. Possible values across all distributions are `jdk`, `jre`, `jdk+fx`, `jre+fx`, `jdk+crac`, `jre+crac`, `jdk+jmods`, `jdk+jcef`, `jre+jcef`, `jdk+ft`, and `jre+ft`. Supported values vary by distribution; see the [package compatibility table](docs/advanced-usage.md#package-compatibility). Default value: `jdk`.
- `architecture`: The target architecture of the package. Possible values: `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`. Default value: Derived from the runner machine.
- `architecture`: The target architecture of the package. Canonical values are `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, and `s390x`; the aliases `ia32`, `amd64`, `arm`, and `arm64` normalize to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Default value: Derived from the runner machine.
- `jdk-file`: If a use-case requires a custom distribution setup-java uses the compressed JDK from the location pointed by this input and will take care of the installation and caching on the VM. Note: `distribution` must be set to 'jdkfile' (case-sensitive; all lowercase) when using this option. (The camelCase `jdkFile` input is still accepted as a deprecated alias and may be removed in a future release.)
@@ -72,6 +72,10 @@ 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-path`: The dependency cache path to use instead of the default path for the package manager selected by `cache`. This option supports a list of paths and exclusion patterns. The build tool must be configured to use the same location.
- `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.
@@ -186,12 +190,88 @@ The action has a built-in functionality for caching and restoring dependencies.
When the option `cache-dependency-path` is specified, the hash is based on the matching file. This option supports wildcards and a list of file names, and is especially useful for monorepos.
Use `cache-path` to replace the selected package manager's default dependency
cache paths. Each non-empty line is passed to `actions/cache`, including
supported exclusion patterns. `setup-java` does not configure the build tool,
so the build must use the same paths:
```yaml
- uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '25'
cache: 'maven'
cache-path: |
/custom/maven/repository
!/custom/maven/repository/**/*.lastUpdated
- run: mvn -Dmaven.repo.local=/custom/maven/repository verify
```
`cache-path` does not change the cache key. The key continues to use the runner
OS, architecture, selected package manager, and dependency-file hash described
above. Jobs intended to share a cache key must therefore use the same
`cache-path` values so that they restore and save the same filesystem
locations.
The Maven and Gradle wrapper caches remain at their documented default paths
and are managed independently of `cache-path`. For advanced keying, fallback
keys, or cache topologies that do not map to one package manager's dependency
paths, use [`actions/cache`](https://github.com/actions/cache) directly.
The workflow output `cache-hit` is set to indicate if an exact match was found for the key [as actions/cache does](https://github.com/actions/cache/tree/main#outputs).
The workflow output `cache-primary-key` exposes the primary cache key computed by the action for the configured build tool. It is useful for composing with [`actions/cache`](https://github.com/actions/cache) or [`actions/cache/restore`](https://github.com/actions/cache/tree/main/restore) in later steps or dependent jobs that need to reuse the exact same key. It is empty when caching is not enabled or when caching is skipped (for example, when the cache service is unavailable).
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
@@ -275,6 +355,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.

View File

@@ -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

View File

@@ -224,10 +224,10 @@ describe('dependency cache', () => {
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
);
await restore('maven', '');
await restore('maven', '', ['/custom/maven/repository']);
// Main dependency cache no longer carries the wrapper dists path.
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')],
['/custom/maven/repository'],
expect.any(String)
);
expect(spyCacheRestore).toHaveBeenCalledWith(
@@ -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'));
@@ -317,10 +394,10 @@ describe('dependency cache', () => {
it('restores the gradle wrapper distribution cache independently of the main cache', async () => {
createFile(join(workspace, 'build.gradle'));
await restore('gradle', '');
await restore('gradle', '', ['/custom/gradle/caches']);
// Main dependency cache no longer carries the wrapper path.
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.gradle', 'caches')],
['/custom/gradle/caches'],
expect.any(String)
);
// Wrapper distribution is restored on its own, keyed only on the
@@ -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) =>
@@ -455,6 +576,34 @@ describe('dependency cache', () => {
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
});
});
describe('cache-path', () => {
it.each([
['maven', ['/custom/maven/repository']],
['gradle', ['/custom/gradle/caches']],
[
'sbt',
[
'/custom/ivy/cache',
'/custom/coursier/cache',
'!/custom/ivy/cache/*.lock'
]
]
])(
'restores and persists custom paths for %s',
async (packageManager, cachePaths) => {
await restore(packageManager, '', cachePaths);
expect(spyCacheRestore).toHaveBeenCalledWith(
cachePaths,
expect.any(String)
);
expect(spySaveState).toHaveBeenCalledWith(
'cache-paths',
JSON.stringify(cachePaths)
);
}
);
});
});
describe('save', () => {
let spyCacheSave: any;
@@ -504,6 +653,42 @@ describe('dependency cache', () => {
);
});
it.each([
['maven', ['/custom/maven/repository']],
['gradle', ['/custom/gradle/caches']],
[
'sbt',
[
'/custom/ivy/cache',
'/custom/coursier/cache',
'!/custom/ivy/cache/*.lock'
]
]
])(
'saves the persisted custom paths for %s',
async (packageManager, cachePaths) => {
(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-paths':
return JSON.stringify(cachePaths);
default:
return '';
}
});
await save(packageManager);
expect(spyCacheSave).toHaveBeenCalledWith(
cachePaths,
'setup-java-cache-primary-key'
);
}
);
describe('for maven', () => {
it('uploads cache even if no pom.xml found', async () => {
createStateForMissingBuildFile();
@@ -609,6 +794,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 +1032,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);

View File

@@ -0,0 +1 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip

View File

@@ -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

View File

@@ -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<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() {
@@ -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 '';
}
});
}

View File

@@ -260,6 +260,7 @@ describe('getAvailableVersions', () => {
it.each([
['amd64', 'x64'],
['arm', 'arm'],
['arm64', 'aarch64']
])(
'defaults to os.arch(): %s mapped to distro arch: %s',

View File

@@ -299,6 +299,19 @@ describe('getAvailableVersions', () => {
expect(availableVersion.url).toBe(expectedLink);
}
);
it('keeps the canonical ARM runner value separate from the vendor value', () => {
jest.spyOn(os, 'arch').mockReturnValue('arm');
const distribution = new CorrettoDistribution({
version: '11',
architecture: '',
packageType: 'jdk',
checkLatest: false
});
expect(distribution['architecture']).toBe('armv7');
expect(distribution['distributionArchitecture']()).toBe('arm');
});
});
const mockPlatform = (

View File

@@ -4,6 +4,20 @@ import {
JAVA_PACKAGE_CAPABILITIES,
JavaDistribution
} from '../../src/distributions/package-types.js';
import os from 'os';
import {validateJavaPlatform} from '../../src/distributions/platform-types.js';
import {normalizeArchitecture} from '../../src/distributions/platform-types.js';
const supportedDistributionsOnCurrentPlatform = Object.values(
JavaDistribution
).filter(distributionName => {
try {
validateJavaPlatform(distributionName, process.platform, 'x64', '25');
return distributionName !== JavaDistribution.JdkFile;
} catch {
return false;
}
});
const installerOptions = (packageType: string, version = '25') => ({
version,
@@ -13,41 +27,29 @@ const installerOptions = (packageType: string, version = '25') => ({
});
describe('getJavaDistribution', () => {
it.each([
'adopt',
'adopt-hotspot',
'adopt-openj9',
'temurin',
'zulu',
'liberica',
'liberica-nik',
'microsoft',
'semeru',
'corretto',
'oracle',
'dragonwell',
'sapmachine',
'graalvm',
'graalvm-community',
'jetbrains',
'kona',
'oracle-openjdk'
])('uses the shared retrying HTTP client for %s', distributionName => {
const distribution = getJavaDistribution(distributionName, {
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
it.each(supportedDistributionsOnCurrentPlatform)(
'uses the shared retrying HTTP client for %s',
distributionName => {
const distribution = getJavaDistribution(distributionName, {
version: '25',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
expect(distribution).not.toBeNull();
expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient);
});
expect(distribution).not.toBeNull();
expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient);
}
);
it.each(
Object.entries(JAVA_PACKAGE_CAPABILITIES).flatMap(
([distributionName, packageTypes]) =>
packageTypes.map(packageType => [distributionName, packageType])
supportedDistributionsOnCurrentPlatform.includes(
distributionName as JavaDistribution
) || distributionName === JavaDistribution.JdkFile
? packageTypes.map(packageType => [distributionName, packageType])
: []
)
)('accepts %s with java-package %s', (distributionName, packageType) => {
expect(
@@ -111,4 +113,36 @@ describe('getJavaDistribution', () => {
)
).toBeNull();
});
it.each([
['amd64', 'x64'],
['ia32', 'x86'],
['arm64', 'aarch64']
])('passes normalized architecture %s as %s', (input, expected) => {
const normalized = getJavaDistribution(JavaDistribution.JdkFile, {
...installerOptions('jdk'),
architecture: input
});
expect(normalized!['architecture']).toBe(expected);
});
it('uses the runner architecture when the input is empty', () => {
const distribution = getJavaDistribution(JavaDistribution.Temurin, {
...installerOptions('jdk'),
architecture: ''
});
const expected = normalizeArchitecture(os.arch());
expect(distribution!['architecture']).toBe(expected);
});
it('rejects an unsupported combination before creating an HTTP client', () => {
expect(() =>
getJavaDistribution(JavaDistribution.Oracle, {
...installerOptions('jdk'),
architecture: 'x86'
})
).toThrow(/does not support operating system/);
});
});

View File

@@ -291,6 +291,7 @@ describe('getAvailableVersions', () => {
it.each([
['amd64', 'x64'],
['arm', 'arm'],
['arm64', 'aarch64']
])(
'defaults to os.arch(): %s mapped to distro arch: %s',

View File

@@ -0,0 +1,136 @@
import fs from 'fs';
import path from 'path';
import {
JAVA_PLATFORM_CAPABILITIES,
normalizeArchitecture,
validateJavaPlatform
} from '../src/distributions/platform-types.js';
import {JavaDistribution} from '../src/distributions/package-types.js';
describe('Java platform capabilities', () => {
it('declares a capability for every distribution', () => {
expect(Object.keys(JAVA_PLATFORM_CAPABILITIES).sort()).toEqual(
Object.values(JavaDistribution).sort()
);
});
it.each([
['x64', 'x64'],
['amd64', 'x64'],
['x86', 'x86'],
['ia32', 'x86'],
['arm', 'armv7'],
['aarch64', 'aarch64'],
['arm64', 'aarch64'],
['ppc64le', 'ppc64le'],
['s390x', 's390x']
])('normalizes architecture %s to %s', (input, expected) => {
expect(normalizeArchitecture(input)).toBe(expected);
});
it('uses the normalized architecture for validation', () => {
expect(validateJavaPlatform('microsoft', 'linux', 'arm64', '25')).toBe(
'aarch64'
);
});
it('rejects OS-specific restrictions with a consistent diagnostic', () => {
expect(() =>
validateJavaPlatform('oracle', 'win32', 'arm64', '21')
).toThrow(
"Distribution 'oracle' does not support operating system 'windows' with architecture 'aarch64' for Java version '21'. Supported combinations: linux (x64, aarch64); macos (x64, aarch64); windows (x64)."
);
});
it('rejects version-dependent architecture restrictions', () => {
expect(() =>
validateJavaPlatform('corretto', 'linux', 'x86', '17')
).toThrow(/x86 \(<12\)/);
expect(() =>
validateJavaPlatform('corretto', 'linux', 'x86', '17.0.2.8.1')
).toThrow(/x86 \(<12\)/);
expect(validateJavaPlatform('corretto', 'linux', 'x86', '11')).toBe('x86');
});
it.each(['corretto', 'kona'])(
'rejects Windows aarch64 for %s',
distributionName => {
expect(() =>
validateJavaPlatform(distributionName, 'win32', 'arm64', '21')
).toThrow(/does not support operating system 'windows'/);
}
);
it('keeps Adopt HotSpot aliases aligned with the Temurin-first resolver', () => {
expect(validateJavaPlatform('adopt', 'darwin', 'arm64', '21')).toBe(
'aarch64'
);
expect(validateJavaPlatform('adopt-hotspot', 'win32', 'arm64', '21')).toBe(
'aarch64'
);
expect(() =>
validateJavaPlatform('adopt-openj9', 'darwin', 'arm64', '16')
).toThrow(/does not support operating system 'macos'/);
});
it('allows local archives on any platform and architecture', () => {
expect(validateJavaPlatform('jdkfile', 'aix', 'mips64', '21')).toBe(
'mips64'
);
});
it('keeps the documented architecture contract aligned', () => {
const repositoryRoot = process.cwd();
const readRepositoryFile = (filePath: string) =>
fs.readFileSync(path.join(repositoryRoot, filePath), 'utf8');
for (const filePath of ['action.yml', 'README.md']) {
const content = readRepositoryFile(filePath);
for (const architecture of [
'x86',
'x64',
'armv7',
'aarch64',
'ppc64le',
'ppc64',
's390x'
]) {
expect(content).toContain(architecture);
}
}
});
it.each(Object.entries(JAVA_PLATFORM_CAPABILITIES))(
'keeps the advanced compatibility table aligned for %s',
(distributionName, capability) => {
const advancedUsage = fs.readFileSync(
path.join(process.cwd(), 'docs/advanced-usage.md'),
'utf8'
);
const compatibilityTable = advancedUsage.slice(
advancedUsage.indexOf('## Platform and architecture compatibility')
);
const compatibilityRow = compatibilityTable
.split('\n')
.find(
line =>
line.startsWith('|') && line.includes(`\`${distributionName}\``)
);
expect(compatibilityRow).toBeDefined();
if (!('platforms' in capability)) {
expect(compatibilityRow).toContain('Any');
return;
}
const architectures = new Set(
Object.values(capability.platforms)
.flat()
.map(item => (typeof item === 'string' ? item : item.architecture))
);
for (const architecture of architectures) {
expect(compatibilityRow).toContain(`\`${architecture}\``);
}
}
);
});

View File

@@ -301,6 +301,10 @@ describe('setup action orchestration', () => {
inputs.set('cache', 'maven');
inputs.set('cache-dependency-path', '**/pom.xml');
multilineInputs.set('java-version', ['21']);
multilineInputs.set('cache-path', [
'/custom/maven/repository',
'!/custom/maven/repository/excluded'
]);
const setupJava = jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
@@ -312,7 +316,10 @@ describe('setup action orchestration', () => {
expect(problemMatcher.configureProblemMatcher).toHaveBeenCalledWith(
expect.stringMatching(/\.github[/\\]java\.json$/)
);
expect(cache.restore).toHaveBeenCalledWith('maven', '**/pom.xml');
expect(cache.restore).toHaveBeenCalledWith('maven', '**/pom.xml', [
'/custom/maven/repository',
'!/custom/maven/repository/excluded'
]);
expect(
(toolchains.configureToolchains as jest.Mock).mock.invocationCallOrder[0]
).toBeLessThan(

View File

@@ -17,7 +17,7 @@ inputs:
required: false
default: 'jdk'
architecture:
description: "The architecture of the package (defaults to the action runner's architecture)"
description: "The architecture of the package (`x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, or `s390x`). Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Defaults to the action runner's architecture."
required: false
jdk-file:
description: 'Path to where the compressed JDK is located'
@@ -87,6 +87,13 @@ 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-path:
description: 'The path to cache instead of the default dependency cache path for the selected package manager. This option can be used with the `cache` option and supports a list of paths and exclusion patterns.'
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

102
dist/cleanup/index.js vendored
View File

@@ -97338,6 +97338,8 @@ 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_PATH = 'cache-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 +97367,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;
@@ -97770,6 +97772,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
const STATE_CACHE_PRIMARY_KEY = 'cache-primary-key';
const STATE_CACHE_PATHS = 'cache-paths';
const CACHE_MATCHED_KEY = 'cache-matched-key';
const CACHE_KEY_PREFIX = 'setup-java';
const supportedPackageManager = [
@@ -97851,6 +97854,21 @@ function findPackageManager(id) {
}
return packageManager;
}
function resolveCachePaths(packageManager, cachePaths) {
return cachePaths.length > 0 ? cachePaths : packageManager.path;
}
function getCachePathsFromState(packageManager) {
const cachePathsState = getState(STATE_CACHE_PATHS);
if (!cachePathsState) {
return packageManager.path;
}
const cachePaths = JSON.parse(cachePathsState);
if (!Array.isArray(cachePaths) ||
!cachePaths.every(cachePath => typeof cachePath === 'string')) {
throw new Error('Invalid cache paths retrieved from state.');
}
return cachePaths;
}
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
@@ -97893,17 +97911,33 @@ async function computeAdditionalCacheKey(additionalCache) {
}
/**
* Restore the dependency cache
* @param id ID of the package manager, should be "maven" or "gradle"
* @param id ID of the package manager, should be "maven", "gradle", or "sbt"
* @param cacheDependencyPath The path to a dependency file
* @param cachePaths Paths to cache instead of the package manager defaults
*/
async function restore(id, cacheDependencyPath) {
async function restore(id, cacheDependencyPath, cachePaths = []) {
const packageManager = findPackageManager(id);
const primaryKey = await computeCacheKey(packageManager, cacheDependencyPath);
const resolvedCachePaths = resolveCachePaths(packageManager, cachePaths);
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.saveState(STATE_CACHE_PATHS, JSON.stringify(resolvedCachePaths));
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, resolvedCachePaths, primaryKey),
...preparedAdditionalCaches.map(preparedCache => restoreAdditionalCache(preparedCache))
]);
}
async function restorePrimaryCache(packageManager, cachePaths, 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);
const matchedKey = await cache.restoreCache(cachePaths, primaryKey);
if (matchedKey) {
core.saveState(CACHE_MATCHED_KEY, matchedKey);
core.setOutput('cache-hit', matchedKey === primaryKey);
@@ -97913,29 +97947,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
@@ -97943,11 +97983,18 @@ async function restoreAdditionalCache(additionalCache) {
*/
async function save(id) {
const packageManager = findPackageManager(id);
const cachePaths = getCachePathsFromState(packageManager);
const matchedKey = getState(CACHE_MATCHED_KEY);
// 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.');
@@ -97959,7 +98006,7 @@ async function save(id) {
return;
}
try {
const cacheId = await cache_saveCache(packageManager.path, primaryKey);
const cacheId = await cache_saveCache(cachePaths, primaryKey);
if (cacheId === -1) {
// saveCache returns -1 without throwing when the cache was not saved,
// e.g. a reserve collision or a read-only token (fork PR). @actions/cache
@@ -98029,7 +98076,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;
}
@@ -98076,7 +98123,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

626
dist/setup/index.js vendored
View File

@@ -12040,7 +12040,7 @@ exports.NodeListStaticImpl = NodeListStaticImpl;
/***/ }),
/***/ 9875:
/***/ 2256:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
@@ -13485,7 +13485,7 @@ const NodeListImpl_1 = __nccwpck_require__(5788);
Object.defineProperty(exports, "NodeList", ({ enumerable: true, get: function () { return NodeListImpl_1.NodeListImpl; } }));
const NodeListStaticImpl_1 = __nccwpck_require__(7654);
Object.defineProperty(exports, "NodeListStatic", ({ enumerable: true, get: function () { return NodeListStaticImpl_1.NodeListStaticImpl; } }));
const NonDocumentTypeChildNodeImpl_1 = __nccwpck_require__(9875);
const NonDocumentTypeChildNodeImpl_1 = __nccwpck_require__(2256);
const NonElementParentNodeImpl_1 = __nccwpck_require__(5325);
const ParentNodeImpl_1 = __nccwpck_require__(1824);
const ProcessingInstructionImpl_1 = __nccwpck_require__(2755);
@@ -72120,6 +72120,8 @@ 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_PATH = 'cache-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';
@@ -129025,6 +129027,7 @@ async function writeToolchainsFileToDisk(directory, settings) {
const STATE_CACHE_PRIMARY_KEY = 'cache-primary-key';
const STATE_CACHE_PATHS = 'cache-paths';
const CACHE_MATCHED_KEY = 'cache-matched-key';
const CACHE_KEY_PREFIX = 'setup-java';
const supportedPackageManager = [
@@ -129106,6 +129109,21 @@ function findPackageManager(id) {
}
return packageManager;
}
function resolveCachePaths(packageManager, cachePaths) {
return cachePaths.length > 0 ? cachePaths : packageManager.path;
}
function getCachePathsFromState(packageManager) {
const cachePathsState = core.getState(STATE_CACHE_PATHS);
if (!cachePathsState) {
return packageManager.path;
}
const cachePaths = JSON.parse(cachePathsState);
if (!Array.isArray(cachePaths) ||
!cachePaths.every(cachePath => typeof cachePath === 'string')) {
throw new Error('Invalid cache paths retrieved from state.');
}
return cachePaths;
}
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
@@ -129148,17 +129166,33 @@ async function computeAdditionalCacheKey(additionalCache) {
}
/**
* Restore the dependency cache
* @param id ID of the package manager, should be "maven" or "gradle"
* @param id ID of the package manager, should be "maven", "gradle", or "sbt"
* @param cacheDependencyPath The path to a dependency file
* @param cachePaths Paths to cache instead of the package manager defaults
*/
async function restore(id, cacheDependencyPath) {
async function restore(id, cacheDependencyPath, cachePaths = []) {
const packageManager = findPackageManager(id);
const primaryKey = await computeCacheKey(packageManager, cacheDependencyPath);
const resolvedCachePaths = resolveCachePaths(packageManager, cachePaths);
const [primaryKey, preparedAdditionalCaches] = await Promise.all([
computeCacheKey(packageManager, cacheDependencyPath),
prepareAdditionalCaches(packageManager.additionalCaches ?? [])
]);
core_debug(`primary key is ${primaryKey}`);
saveState(STATE_CACHE_PRIMARY_KEY, primaryKey);
saveState(STATE_CACHE_PATHS, JSON.stringify(resolvedCachePaths));
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, resolvedCachePaths, primaryKey),
...preparedAdditionalCaches.map(preparedCache => restoreAdditionalCache(preparedCache))
]);
}
async function restorePrimaryCache(packageManager, cachePaths, 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);
const matchedKey = await restoreCache(cachePaths, primaryKey);
if (matchedKey) {
saveState(CACHE_MATCHED_KEY, matchedKey);
setOutput('cache-hit', matchedKey === primaryKey);
@@ -129168,29 +129202,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
@@ -129198,11 +129238,18 @@ async function restoreAdditionalCache(additionalCache) {
*/
async function save(id) {
const packageManager = findPackageManager(id);
const cachePaths = getCachePathsFromState(packageManager);
const matchedKey = core.getState(CACHE_MATCHED_KEY);
// 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.');
@@ -129214,7 +129261,7 @@ async function save(id) {
return;
}
try {
const cacheId = await cache.saveCache(packageManager.path, primaryKey);
const cacheId = await cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) {
// saveCache returns -1 without throwing when the cache was not saved,
// e.g. a reserve collision or a read-only token (fork PR). @actions/cache
@@ -129284,7 +129331,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;
}
@@ -129463,6 +129510,350 @@ async function verifyChecksum(filePath, checksum, context) {
}
}
;// CONCATENATED MODULE: ./src/distributions/package-types.ts
var JavaDistribution;
(function (JavaDistribution) {
JavaDistribution["Adopt"] = "adopt";
JavaDistribution["AdoptHotspot"] = "adopt-hotspot";
JavaDistribution["AdoptOpenJ9"] = "adopt-openj9";
JavaDistribution["Temurin"] = "temurin";
JavaDistribution["Zulu"] = "zulu";
JavaDistribution["Liberica"] = "liberica";
JavaDistribution["LibericaNik"] = "liberica-nik";
JavaDistribution["JdkFile"] = "jdkfile";
JavaDistribution["Microsoft"] = "microsoft";
JavaDistribution["Semeru"] = "semeru";
JavaDistribution["Corretto"] = "corretto";
JavaDistribution["Oracle"] = "oracle";
JavaDistribution["Dragonwell"] = "dragonwell";
JavaDistribution["SapMachine"] = "sapmachine";
JavaDistribution["GraalVM"] = "graalvm";
JavaDistribution["GraalVMCommunity"] = "graalvm-community";
JavaDistribution["JetBrains"] = "jetbrains";
JavaDistribution["Kona"] = "kona";
JavaDistribution["OracleOpenJdk"] = "oracle-openjdk";
})(JavaDistribution || (JavaDistribution = {}));
const JAVA_PACKAGE_CAPABILITIES = {
[JavaDistribution.Adopt]: ['jdk', 'jre'],
[JavaDistribution.AdoptHotspot]: ['jdk', 'jre'],
[JavaDistribution.AdoptOpenJ9]: ['jdk', 'jre'],
[JavaDistribution.Temurin]: ['jdk', 'jre', 'jdk+jmods'],
[JavaDistribution.Zulu]: [
'jdk',
'jre',
'jdk+fx',
'jre+fx',
'jdk+crac',
'jre+crac'
],
[JavaDistribution.Liberica]: ['jdk', 'jre', 'jdk+fx', 'jre+fx'],
[JavaDistribution.LibericaNik]: ['jdk', 'jdk+fx'],
[JavaDistribution.JdkFile]: ['jdk'],
[JavaDistribution.Microsoft]: ['jdk'],
[JavaDistribution.Semeru]: ['jdk', 'jre'],
[JavaDistribution.Corretto]: ['jdk', 'jre'],
[JavaDistribution.Oracle]: ['jdk'],
[JavaDistribution.Dragonwell]: ['jdk'],
[JavaDistribution.SapMachine]: ['jdk', 'jre'],
[JavaDistribution.GraalVM]: ['jdk'],
[JavaDistribution.GraalVMCommunity]: ['jdk'],
[JavaDistribution.JetBrains]: [
'jdk',
'jre',
'jdk+jcef',
'jre+jcef',
'jdk+ft',
'jre+ft'
],
[JavaDistribution.Kona]: ['jdk'],
[JavaDistribution.OracleOpenJdk]: ['jdk']
};
function validateJavaPackage(distributionName, packageType, version) {
if (!isJavaDistribution(distributionName)) {
return;
}
const supportedPackages = JAVA_PACKAGE_CAPABILITIES[distributionName];
if (!supportedPackages.includes(packageType)) {
throw createUnsupportedPackageError(distributionName, packageType, supportedPackages);
}
if (distributionName === JavaDistribution.Temurin &&
packageType === 'jdk+jmods' &&
!canResolveTemurinJmods(version)) {
throw createUnsupportedPackageError(distributionName, packageType, supportedPackages, `Package 'jdk+jmods' requires Java 24 or later; requested version '${version}'.`);
}
}
function isJavaDistribution(value) {
return Object.prototype.hasOwnProperty.call(JAVA_PACKAGE_CAPABILITIES, value);
}
function canResolveTemurinJmods(version) {
const normalizedVersion = version.trim().toLowerCase();
if (normalizedVersion === 'latest') {
return true;
}
let normalizedRange = normalizedVersion
.replace(/-ea$/, '')
.replace('-ea.', '+');
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
normalizedRange = convertVersionToSemver(normalizedRange);
}
if (!semver_default().validRange(normalizedRange)) {
// JavaBase owns general version validation and its targeted error messages.
return true;
}
return semver_default().intersects(normalizedRange, '>=24.0.0', {
includePrerelease: true
});
}
function createUnsupportedPackageError(distributionName, packageType, supportedPackages, detail) {
const message = [
`Java package '${packageType}' is not supported for distribution '${distributionName}'.`,
`Supported package types: ${supportedPackages.join(', ')}.`
];
if (detail) {
message.push(detail);
}
return new Error(message.join(' '));
}
;// CONCATENATED MODULE: ./src/distributions/platform-types.ts
const X64_ARM64 = ['x64', 'aarch64'];
const X64_X86 = ['x64', 'x86'];
const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x'];
const JAVA_PLATFORM_CAPABILITIES = {
[JavaDistribution.Adopt]: {
platforms: {
linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<18' }],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.AdoptHotspot]: {
platforms: {
linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<18' }],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.AdoptOpenJ9]: {
platforms: {
linux: STANDARD_LINUX,
macos: ['x64'],
windows: X64_X86
}
},
[JavaDistribution.Temurin]: {
platforms: {
linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<18' }],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.Zulu]: {
platforms: {
linux: ['x64', 'x86', 'armv7', 'aarch64'],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.Liberica]: {
platforms: {
linux: ['x64', 'x86', 'armv7', 'aarch64', 'ppc64le'],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64'],
solaris: ['x64']
}
},
[JavaDistribution.LibericaNik]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.JdkFile]: {
unrestricted: true
},
[JavaDistribution.Microsoft]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.Semeru]: {
platforms: {
linux: ['x64', 'x86', 'ppc64le', 'ppc64', 's390x', 'aarch64'],
macos: X64_ARM64,
windows: ['x64', 'aarch64']
}
},
[JavaDistribution.Corretto]: {
platforms: {
linux: [
'x64',
{ architecture: 'x86', versionRange: '<12' },
{ architecture: 'armv7', versionRange: '11' },
'aarch64'
],
macos: X64_ARM64,
windows: ['x64', { architecture: 'x86', versionRange: '<12' }]
}
},
[JavaDistribution.Oracle]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.Dragonwell]: {
platforms: {
linux: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.SapMachine]: {
platforms: {
linux: ['x64', 'aarch64', 'ppc64le'],
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.GraalVM]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.GraalVMCommunity]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.JetBrains]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.Kona]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.OracleOpenJdk]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
}
};
const ARCHITECTURE_ALIASES = {
amd64: 'x64',
arm: 'armv7',
ia32: 'x86',
arm64: 'aarch64'
};
const CANONICAL_ARCHITECTURES = [
'x86',
'x64',
'armv7',
'aarch64',
'ppc64le',
'ppc64',
's390x'
];
const PLATFORM_ALIASES = {
darwin: 'macos',
linux: 'linux',
sunos: 'solaris',
win32: 'windows'
};
function normalizeArchitecture(architecture) {
const trimmedArchitecture = architecture.trim();
const normalizedArchitecture = trimmedArchitecture.toLowerCase();
return (ARCHITECTURE_ALIASES[normalizedArchitecture] ??
(CANONICAL_ARCHITECTURES.includes(normalizedArchitecture)
? normalizedArchitecture
: trimmedArchitecture));
}
function normalizePlatform(platform) {
return PLATFORM_ALIASES[platform];
}
function validateJavaPlatform(distributionName, platform, architecture, version) {
const normalizedArchitecture = normalizeArchitecture(architecture);
if (!platform_types_isJavaDistribution(distributionName)) {
return normalizedArchitecture;
}
const capability = JAVA_PLATFORM_CAPABILITIES[distributionName];
if ('unrestricted' in capability && capability.unrestricted === true) {
return normalizedArchitecture;
}
const normalizedPlatform = normalizePlatform(platform);
const architectures = normalizedPlatform
? capability.platforms[normalizedPlatform]
: undefined;
const supported = architectures?.some(item => {
const architectureCapability = typeof item === 'string' ? { architecture: item } : item;
return (architectureCapability.architecture === normalizedArchitecture &&
(!('versionRange' in architectureCapability) ||
isVersionCompatible(version, architectureCapability.versionRange)));
});
if (!supported) {
throw new Error(`Distribution '${distributionName}' does not support operating system '${normalizedPlatform ?? platform}' with architecture '${normalizedArchitecture}' for Java version '${version}'. Supported combinations: ${formatSupportedCombinations(capability)}.`);
}
return normalizedArchitecture;
}
function platform_types_isJavaDistribution(value) {
return Object.prototype.hasOwnProperty.call(JAVA_PLATFORM_CAPABILITIES, value);
}
function isVersionCompatible(version, supportedRange) {
let normalizedVersion = version.trim().toLowerCase();
if (normalizedVersion === 'latest') {
return true;
}
if (/^\d+(\.\d+){3,}$/.test(normalizedVersion)) {
normalizedVersion = normalizeExtendedVersionToSemver(normalizedVersion);
}
const requestedRange = semver_default().validRange(normalizedVersion.replace(/-ea$/, ''));
const capabilityRange = semver_default().validRange(supportedRange);
if (!requestedRange || !capabilityRange) {
return true;
}
function normalizeExtendedVersionToSemver(version) {
const versionParts = version.split('.');
const mainVersion = versionParts.slice(0, 3).join('.');
if (versionParts.length > 3) {
return `${mainVersion}+${versionParts.slice(3).join('.')}`;
}
return version;
}
return semver_default().intersects(requestedRange, capabilityRange, {
includePrerelease: true
});
}
function formatSupportedCombinations(capability) {
return Object.entries(capability.platforms)
.map(([platform, architectures]) => {
const values = architectures.map(item => typeof item === 'string'
? item
: `${item.architecture} (${item.versionRange})`);
return `${platform} (${values.join(', ')})`;
})
.join('; ');
}
;// CONCATENATED MODULE: ./src/distributions/base-installer.ts
@@ -129475,6 +129866,7 @@ async function verifyChecksum(filePath, checksum, context) {
class JavaBase {
distribution;
http;
@@ -129496,7 +129888,7 @@ class JavaBase {
stable: this.stable,
latest: this.latest
} = this.normalizeVersion(installerOptions.version));
this.architecture = installerOptions.architecture || external_os_default().arch();
this.architecture = normalizeArchitecture(installerOptions.architecture || external_os_default().arch());
this.packageType = installerOptions.packageType;
this.checkLatest = installerOptions.checkLatest;
this.forceDownload = installerOptions.forceDownload ?? false;
@@ -129820,22 +130212,7 @@ class JavaBase {
exportVariable(`JAVA_HOME_${majorVersion}_${this.architecture.toUpperCase()}`, toolPath);
}
distributionArchitecture() {
// default mappings of config architectures to distribution architectures
// override if a distribution uses any different names; see liberica for an example
// node's os.arch() - which this defaults to - can return any of:
// 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', and 'x64'
// so we need to map these to java distribution architectures
// 'amd64' is included here too b/c it's a common alias for 'x64' people might use explicitly
switch (this.architecture) {
case 'amd64':
return 'x64';
case 'ia32':
return 'x86';
case 'arm64':
return 'aarch64';
default:
return this.architecture;
}
return this.architecture;
}
}
@@ -130055,6 +130432,8 @@ class ZuluDistribution extends JavaBase {
// would let a 32-bit request resolve to a 64-bit JDK. Use "i686" to
// target only genuine 32-bit builds, matching the legacy API behavior.
return 'i686';
case 'armv7':
return 'arm';
case 'aarch64':
case 'arm64':
return 'aarch64';
@@ -130301,6 +130680,10 @@ class TemurinDistribution extends JavaBase {
return process.platform;
}
}
distributionArchitecture() {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}
;// CONCATENATED MODULE: ./src/distributions/adopt/installer.ts
@@ -130479,6 +130862,10 @@ class AdoptDistribution extends JavaBase {
return process.platform;
}
}
distributionArchitecture() {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}
;// CONCATENATED MODULE: ./src/distributions/liberica/installer.ts
@@ -131147,6 +131534,10 @@ class CorrettoDistribution extends JavaBase {
return process.platform;
}
}
distributionArchitecture() {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
getCorrettoVersion(resource) {
const regex = /(\d+.+)\//;
const match = regex.exec(resource);
@@ -132329,113 +132720,6 @@ class OpenJdkDistribution extends JavaBase {
}
}
;// CONCATENATED MODULE: ./src/distributions/package-types.ts
var JavaDistribution;
(function (JavaDistribution) {
JavaDistribution["Adopt"] = "adopt";
JavaDistribution["AdoptHotspot"] = "adopt-hotspot";
JavaDistribution["AdoptOpenJ9"] = "adopt-openj9";
JavaDistribution["Temurin"] = "temurin";
JavaDistribution["Zulu"] = "zulu";
JavaDistribution["Liberica"] = "liberica";
JavaDistribution["LibericaNik"] = "liberica-nik";
JavaDistribution["JdkFile"] = "jdkfile";
JavaDistribution["Microsoft"] = "microsoft";
JavaDistribution["Semeru"] = "semeru";
JavaDistribution["Corretto"] = "corretto";
JavaDistribution["Oracle"] = "oracle";
JavaDistribution["Dragonwell"] = "dragonwell";
JavaDistribution["SapMachine"] = "sapmachine";
JavaDistribution["GraalVM"] = "graalvm";
JavaDistribution["GraalVMCommunity"] = "graalvm-community";
JavaDistribution["JetBrains"] = "jetbrains";
JavaDistribution["Kona"] = "kona";
JavaDistribution["OracleOpenJdk"] = "oracle-openjdk";
})(JavaDistribution || (JavaDistribution = {}));
const JAVA_PACKAGE_CAPABILITIES = {
[JavaDistribution.Adopt]: ['jdk', 'jre'],
[JavaDistribution.AdoptHotspot]: ['jdk', 'jre'],
[JavaDistribution.AdoptOpenJ9]: ['jdk', 'jre'],
[JavaDistribution.Temurin]: ['jdk', 'jre', 'jdk+jmods'],
[JavaDistribution.Zulu]: [
'jdk',
'jre',
'jdk+fx',
'jre+fx',
'jdk+crac',
'jre+crac'
],
[JavaDistribution.Liberica]: ['jdk', 'jre', 'jdk+fx', 'jre+fx'],
[JavaDistribution.LibericaNik]: ['jdk', 'jdk+fx'],
[JavaDistribution.JdkFile]: ['jdk'],
[JavaDistribution.Microsoft]: ['jdk'],
[JavaDistribution.Semeru]: ['jdk', 'jre'],
[JavaDistribution.Corretto]: ['jdk', 'jre'],
[JavaDistribution.Oracle]: ['jdk'],
[JavaDistribution.Dragonwell]: ['jdk'],
[JavaDistribution.SapMachine]: ['jdk', 'jre'],
[JavaDistribution.GraalVM]: ['jdk'],
[JavaDistribution.GraalVMCommunity]: ['jdk'],
[JavaDistribution.JetBrains]: [
'jdk',
'jre',
'jdk+jcef',
'jre+jcef',
'jdk+ft',
'jre+ft'
],
[JavaDistribution.Kona]: ['jdk'],
[JavaDistribution.OracleOpenJdk]: ['jdk']
};
function validateJavaPackage(distributionName, packageType, version) {
if (!isJavaDistribution(distributionName)) {
return;
}
const supportedPackages = JAVA_PACKAGE_CAPABILITIES[distributionName];
if (!supportedPackages.includes(packageType)) {
throw createUnsupportedPackageError(distributionName, packageType, supportedPackages);
}
if (distributionName === JavaDistribution.Temurin &&
packageType === 'jdk+jmods' &&
!canResolveTemurinJmods(version)) {
throw createUnsupportedPackageError(distributionName, packageType, supportedPackages, `Package 'jdk+jmods' requires Java 24 or later; requested version '${version}'.`);
}
}
function isJavaDistribution(value) {
return Object.prototype.hasOwnProperty.call(JAVA_PACKAGE_CAPABILITIES, value);
}
function canResolveTemurinJmods(version) {
const normalizedVersion = version.trim().toLowerCase();
if (normalizedVersion === 'latest') {
return true;
}
let normalizedRange = normalizedVersion
.replace(/-ea$/, '')
.replace('-ea.', '+');
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
normalizedRange = convertVersionToSemver(normalizedRange);
}
if (!semver_default().validRange(normalizedRange)) {
// JavaBase owns general version validation and its targeted error messages.
return true;
}
return semver_default().intersects(normalizedRange, '>=24.0.0', {
includePrerelease: true
});
}
function createUnsupportedPackageError(distributionName, packageType, supportedPackages, detail) {
const message = [
`Java package '${packageType}' is not supported for distribution '${distributionName}'.`,
`Supported package types: ${supportedPackages.join(', ')}.`
];
if (detail) {
message.push(detail);
}
return new Error(message.join(' '));
}
;// CONCATENATED MODULE: ./src/distributions/distribution-factory.ts
@@ -132454,46 +132738,53 @@ function createUnsupportedPackageError(distributionName, packageType, supportedP
function getJavaDistribution(distributionName, installerOptions, jdkFile) {
validateJavaPackage(distributionName, installerOptions.packageType, installerOptions.version);
const architecture = validateJavaPlatform(distributionName, process.platform, installerOptions.architecture || external_os_default().arch(), installerOptions.version);
const normalizedInstallerOptions = {
...installerOptions,
architecture
};
switch (distributionName) {
case JavaDistribution.JdkFile:
return new LocalDistribution(installerOptions, jdkFile);
return new LocalDistribution(normalizedInstallerOptions, jdkFile);
case JavaDistribution.Adopt:
case JavaDistribution.AdoptHotspot:
return new AdoptDistribution(installerOptions, AdoptImplementation.Hotspot);
return new AdoptDistribution(normalizedInstallerOptions, AdoptImplementation.Hotspot);
case JavaDistribution.AdoptOpenJ9:
return new AdoptDistribution(installerOptions, AdoptImplementation.OpenJ9);
return new AdoptDistribution(normalizedInstallerOptions, AdoptImplementation.OpenJ9);
case JavaDistribution.Temurin:
return new TemurinDistribution(installerOptions, TemurinImplementation.Hotspot);
return new TemurinDistribution(normalizedInstallerOptions, TemurinImplementation.Hotspot);
case JavaDistribution.Zulu:
return new ZuluDistribution(installerOptions);
return new ZuluDistribution(normalizedInstallerOptions);
case JavaDistribution.Liberica:
return new LibericaDistributions(installerOptions);
return new LibericaDistributions(normalizedInstallerOptions);
case JavaDistribution.LibericaNik:
return new LibericaNikDistributions(installerOptions);
return new LibericaNikDistributions(normalizedInstallerOptions);
case JavaDistribution.Microsoft:
return new MicrosoftDistributions(installerOptions);
return new MicrosoftDistributions(normalizedInstallerOptions);
case JavaDistribution.Semeru:
return new SemeruDistribution(installerOptions);
return new SemeruDistribution(normalizedInstallerOptions);
case JavaDistribution.Corretto:
return new CorrettoDistribution(installerOptions);
return new CorrettoDistribution(normalizedInstallerOptions);
case JavaDistribution.Oracle:
return new OracleDistribution(installerOptions);
return new OracleDistribution(normalizedInstallerOptions);
case JavaDistribution.Dragonwell:
return new DragonwellDistribution(installerOptions);
return new DragonwellDistribution(normalizedInstallerOptions);
case JavaDistribution.SapMachine:
return new SapMachineDistribution(installerOptions);
return new SapMachineDistribution(normalizedInstallerOptions);
case JavaDistribution.GraalVM:
return new GraalVMDistribution(installerOptions);
return new GraalVMDistribution(normalizedInstallerOptions);
case JavaDistribution.GraalVMCommunity:
return new GraalVMCommunityDistribution(installerOptions);
return new GraalVMCommunityDistribution(normalizedInstallerOptions);
case JavaDistribution.JetBrains:
return new JetBrainsDistribution(installerOptions);
return new JetBrainsDistribution(normalizedInstallerOptions);
case JavaDistribution.Kona:
return new KonaDistribution(installerOptions);
return new KonaDistribution(normalizedInstallerOptions);
case JavaDistribution.OracleOpenJdk:
return new OpenJdkDistribution(installerOptions);
return new OpenJdkDistribution(normalizedInstallerOptions);
default:
return null;
}
@@ -132582,6 +132873,7 @@ async function run() {
const jdkFile = getJdkFileInput();
const cache = getInput(INPUT_CACHE);
const cacheDependencyPath = getInput(INPUT_CACHE_DEPENDENCY_PATH);
const cachePath = getMultilineInput(INPUT_CACHE_PATH);
const checkLatest = util_getBooleanInput(INPUT_CHECK_LATEST, false);
const forceDownload = util_getBooleanInput(INPUT_FORCE_DOWNLOAD, false);
const setDefault = util_getBooleanInput(INPUT_SET_DEFAULT, true);
@@ -132650,7 +132942,7 @@ async function run() {
await configureAuthentication();
configureMavenArgs();
if (cache && isCacheFeatureAvailable()) {
await restore(cache, cacheDependencyPath);
await restore(cache, cacheDependencyPath, cachePath);
}
}
catch (error) {

View File

@@ -484,6 +484,38 @@ jobs:
> which provides purpose-built caching (see the
> [setup-gradle documentation](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md)).
## Platform and architecture compatibility
The `architecture` input is normalized before setup-java checks the tool cache
or contacts a vendor. `amd64`, `ia32`, `arm`, and `arm64` are accepted aliases
for `x64`, `x86`, `armv7`, and `aarch64`. The table lists the combinations
setup-java validates up front; an individual Java patch release can still be
absent from a vendor catalog.
| Distribution | Linux | macOS | Windows | Other / version restrictions |
| --- | --- | --- | --- | --- |
| `temurin` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Linux `armv7` is available through Java 17. |
| `adopt`, `adopt-hotspot` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | HotSpot requests try Temurin before the archived catalog; Linux `armv7` is available through Java 17. |
| `adopt-openj9` | `x64`, `x86`, `aarch64`, `ppc64le`, `s390x` | `x64` | `x64`, `x86` | Uses the archived AdoptOpenJDK OpenJ9 catalog. |
| `zulu` | `x64`, `x86`, `armv7`, `aarch64` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | |
| `liberica` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Solaris: `x64`. |
| `liberica-nik` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | |
| `microsoft` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | |
| `semeru` | `x64`, `x86`, `ppc64le`, `ppc64`, `s390x`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | |
| `corretto` | `x64`, `x86`, `armv7`, `aarch64` | `x64`, `aarch64` | `x64`, `x86` | `x86` is limited to Java 11 or earlier; Linux `armv7` is available for Java 11. |
| `oracle` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | |
| `oracle-openjdk` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | |
| `dragonwell` | `x64`, `aarch64` | — | `x64` | |
| `sapmachine` | `x64`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `aarch64` | |
| `graalvm`, `graalvm-community` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | |
| `jetbrains` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | |
| `kona` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | |
| `jdkfile` | Any | Any | Any | Local archives are not restricted because setup-java does not inspect their contents. |
Unsupported combinations fail with a platform-capability error before a cache
lookup or vendor request. A supported combination can still produce a
version-not-found error when the requested release was not published.
## Installing custom Java architecture
```yaml
@@ -890,9 +922,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.

View File

@@ -9,6 +9,7 @@ import * as core from '@actions/core';
import * as glob from '@actions/glob';
const STATE_CACHE_PRIMARY_KEY = 'cache-primary-key';
const STATE_CACHE_PATHS = 'cache-paths';
const CACHE_MATCHED_KEY = 'cache-matched-key';
const CACHE_KEY_PREFIX = 'setup-java';
@@ -36,6 +37,11 @@ interface AdditionalCache {
pattern: string[];
}
interface PreparedAdditionalCache {
cache: AdditionalCache;
primaryKey: string;
}
interface PackageManager {
id: 'maven' | 'gradle' | 'sbt';
/**
@@ -131,6 +137,29 @@ function findPackageManager(id: string): PackageManager {
return packageManager;
}
function resolveCachePaths(
packageManager: PackageManager,
cachePaths: string[]
): string[] {
return cachePaths.length > 0 ? cachePaths : packageManager.path;
}
function getCachePathsFromState(packageManager: PackageManager): string[] {
const cachePathsState = core.getState(STATE_CACHE_PATHS);
if (!cachePathsState) {
return packageManager.path;
}
const cachePaths: unknown = JSON.parse(cachePathsState);
if (
!Array.isArray(cachePaths) ||
!cachePaths.every(cachePath => typeof cachePath === 'string')
) {
throw new Error('Invalid cache paths retrieved from state.');
}
return cachePaths;
}
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
@@ -184,18 +213,52 @@ async function computeAdditionalCacheKey(
/**
* Restore the dependency cache
* @param id ID of the package manager, should be "maven" or "gradle"
* @param id ID of the package manager, should be "maven", "gradle", or "sbt"
* @param cacheDependencyPath The path to a dependency file
* @param cachePaths Paths to cache instead of the package manager defaults
*/
export async function restore(id: string, cacheDependencyPath: string) {
export async function restore(
id: string,
cacheDependencyPath: string,
cachePaths: string[] = []
) {
const packageManager = findPackageManager(id);
const primaryKey = await computeCacheKey(packageManager, cacheDependencyPath);
const resolvedCachePaths = resolveCachePaths(packageManager, cachePaths);
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.saveState(STATE_CACHE_PATHS, JSON.stringify(resolvedCachePaths));
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, resolvedCachePaths, primaryKey),
...preparedAdditionalCaches.map(preparedCache =>
restoreAdditionalCache(preparedCache)
)
]);
}
async function restorePrimaryCache(
packageManager: PackageManager,
cachePaths: string[],
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);
const matchedKey = await cache.restoreCache(cachePaths, primaryKey);
if (matchedKey) {
core.saveState(CACHE_MATCHED_KEY, matchedKey);
core.setOutput('cache-hit', matchedKey === primaryKey);
@@ -204,32 +267,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 +307,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`);
}
}
@@ -246,13 +318,21 @@ async function restoreAdditionalCache(additionalCache: AdditionalCache) {
*/
export async function save(id: string) {
const packageManager = findPackageManager(id);
const cachePaths = getCachePathsFromState(packageManager);
const matchedKey = core.getState(CACHE_MATCHED_KEY);
// 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 as Error;
core.warning(
`Failed to save ${additionalCache.name} cache: ${err.message}. Continuing with primary cache save.`
);
}
}
if (!primaryKey) {
@@ -266,7 +346,7 @@ export async function save(id: string) {
return;
}
try {
const cacheId = await cache.saveCache(packageManager.path, primaryKey);
const cacheId = await cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) {
// saveCache returns -1 without throwing when the cache was not saved,
// e.g. a reserve collision or a read-only token (fork PR). @actions/cache
@@ -360,7 +440,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;

View File

@@ -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);
}
/**

View File

@@ -38,6 +38,8 @@ 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_PATH = 'cache-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';

View File

@@ -260,4 +260,9 @@ export class AdoptDistribution extends JavaBase {
return process.platform;
}
}
protected distributionArchitecture(): string {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}

View File

@@ -20,6 +20,7 @@ import {MACOS_JAVA_CONTENT_POSTFIX} from '../constants.js';
import {RetryingHttpClient} from '../retrying-http-client.js';
import os from 'os';
import {expectedDigestLength, verifyChecksum} from '../checksum.js';
import {normalizeArchitecture} from './platform-types.js';
export abstract class JavaBase {
protected http: httpm.HttpClient;
@@ -45,7 +46,9 @@ export abstract class JavaBase {
stable: this.stable,
latest: this.latest
} = this.normalizeVersion(installerOptions.version));
this.architecture = installerOptions.architecture || os.arch();
this.architecture = normalizeArchitecture(
installerOptions.architecture || os.arch()
);
this.packageType = installerOptions.packageType;
this.checkLatest = installerOptions.checkLatest;
this.forceDownload = installerOptions.forceDownload ?? false;
@@ -451,22 +454,6 @@ export abstract class JavaBase {
}
protected distributionArchitecture(): string {
// default mappings of config architectures to distribution architectures
// override if a distribution uses any different names; see liberica for an example
// node's os.arch() - which this defaults to - can return any of:
// 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', and 'x64'
// so we need to map these to java distribution architectures
// 'amd64' is included here too b/c it's a common alias for 'x64' people might use explicitly
switch (this.architecture) {
case 'amd64':
return 'x64';
case 'ia32':
return 'x86';
case 'arm64':
return 'aarch64';
default:
return this.architecture;
}
return this.architecture;
}
}

View File

@@ -194,6 +194,11 @@ export class CorrettoDistribution extends JavaBase {
}
}
protected distributionArchitecture(): string {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
private getCorrettoVersion(resource: string): string {
const regex = /(\d+.+)\//;
const match = regex.exec(resource);

View File

@@ -23,6 +23,8 @@ import {JetBrainsDistribution} from './jetbrains/installer.js';
import {KonaDistribution} from './kona/installer.js';
import {OpenJdkDistribution} from './openjdk/installer.js';
import {JavaDistribution, validateJavaPackage} from './package-types.js';
import os from 'os';
import {validateJavaPlatform} from './platform-types.js';
export function getJavaDistribution(
distributionName: string,
@@ -34,54 +36,64 @@ export function getJavaDistribution(
installerOptions.packageType,
installerOptions.version
);
const architecture = validateJavaPlatform(
distributionName,
process.platform,
installerOptions.architecture || os.arch(),
installerOptions.version
);
const normalizedInstallerOptions = {
...installerOptions,
architecture
};
switch (distributionName) {
case JavaDistribution.JdkFile:
return new LocalDistribution(installerOptions, jdkFile);
return new LocalDistribution(normalizedInstallerOptions, jdkFile);
case JavaDistribution.Adopt:
case JavaDistribution.AdoptHotspot:
return new AdoptDistribution(
installerOptions,
normalizedInstallerOptions,
AdoptImplementation.Hotspot
);
case JavaDistribution.AdoptOpenJ9:
return new AdoptDistribution(
installerOptions,
normalizedInstallerOptions,
AdoptImplementation.OpenJ9
);
case JavaDistribution.Temurin:
return new TemurinDistribution(
installerOptions,
normalizedInstallerOptions,
TemurinImplementation.Hotspot
);
case JavaDistribution.Zulu:
return new ZuluDistribution(installerOptions);
return new ZuluDistribution(normalizedInstallerOptions);
case JavaDistribution.Liberica:
return new LibericaDistributions(installerOptions);
return new LibericaDistributions(normalizedInstallerOptions);
case JavaDistribution.LibericaNik:
return new LibericaNikDistributions(installerOptions);
return new LibericaNikDistributions(normalizedInstallerOptions);
case JavaDistribution.Microsoft:
return new MicrosoftDistributions(installerOptions);
return new MicrosoftDistributions(normalizedInstallerOptions);
case JavaDistribution.Semeru:
return new SemeruDistribution(installerOptions);
return new SemeruDistribution(normalizedInstallerOptions);
case JavaDistribution.Corretto:
return new CorrettoDistribution(installerOptions);
return new CorrettoDistribution(normalizedInstallerOptions);
case JavaDistribution.Oracle:
return new OracleDistribution(installerOptions);
return new OracleDistribution(normalizedInstallerOptions);
case JavaDistribution.Dragonwell:
return new DragonwellDistribution(installerOptions);
return new DragonwellDistribution(normalizedInstallerOptions);
case JavaDistribution.SapMachine:
return new SapMachineDistribution(installerOptions);
return new SapMachineDistribution(normalizedInstallerOptions);
case JavaDistribution.GraalVM:
return new GraalVMDistribution(installerOptions);
return new GraalVMDistribution(normalizedInstallerOptions);
case JavaDistribution.GraalVMCommunity:
return new GraalVMCommunityDistribution(installerOptions);
return new GraalVMCommunityDistribution(normalizedInstallerOptions);
case JavaDistribution.JetBrains:
return new JetBrainsDistribution(installerOptions);
return new JetBrainsDistribution(normalizedInstallerOptions);
case JavaDistribution.Kona:
return new KonaDistribution(installerOptions);
return new KonaDistribution(normalizedInstallerOptions);
case JavaDistribution.OracleOpenJdk:
return new OpenJdkDistribution(installerOptions);
return new OpenJdkDistribution(normalizedInstallerOptions);
default:
return null;
}

View File

@@ -0,0 +1,306 @@
import semver from 'semver';
import {JavaDistribution} from './package-types.js';
export type JavaPlatform = 'linux' | 'macos' | 'windows' | 'solaris';
export type JavaArchitecture =
'x86' | 'x64' | 'armv7' | 'aarch64' | 'ppc64le' | 'ppc64' | 's390x';
interface VersionedArchitecture {
architecture: JavaArchitecture;
versionRange: string;
}
type ArchitectureCapability = JavaArchitecture | VersionedArchitecture;
interface RestrictedPlatformCapability {
unrestricted?: false;
platforms: Partial<Record<JavaPlatform, readonly ArchitectureCapability[]>>;
}
interface UnrestrictedPlatformCapability {
unrestricted: true;
}
export type JavaPlatformCapability =
RestrictedPlatformCapability | UnrestrictedPlatformCapability;
const X64_ARM64 = ['x64', 'aarch64'] as const;
const X64_X86 = ['x64', 'x86'] as const;
const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x'] as const;
export const JAVA_PLATFORM_CAPABILITIES: Record<
JavaDistribution,
JavaPlatformCapability
> = {
[JavaDistribution.Adopt]: {
platforms: {
linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<18'}],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.AdoptHotspot]: {
platforms: {
linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<18'}],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.AdoptOpenJ9]: {
platforms: {
linux: STANDARD_LINUX,
macos: ['x64'],
windows: X64_X86
}
},
[JavaDistribution.Temurin]: {
platforms: {
linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<18'}],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.Zulu]: {
platforms: {
linux: ['x64', 'x86', 'armv7', 'aarch64'],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64']
}
},
[JavaDistribution.Liberica]: {
platforms: {
linux: ['x64', 'x86', 'armv7', 'aarch64', 'ppc64le'],
macos: X64_ARM64,
windows: ['x64', 'x86', 'aarch64'],
solaris: ['x64']
}
},
[JavaDistribution.LibericaNik]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.JdkFile]: {
unrestricted: true
},
[JavaDistribution.Microsoft]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.Semeru]: {
platforms: {
linux: ['x64', 'x86', 'ppc64le', 'ppc64', 's390x', 'aarch64'],
macos: X64_ARM64,
windows: ['x64', 'aarch64']
}
},
[JavaDistribution.Corretto]: {
platforms: {
linux: [
'x64',
{architecture: 'x86', versionRange: '<12'},
{architecture: 'armv7', versionRange: '11'},
'aarch64'
],
macos: X64_ARM64,
windows: ['x64', {architecture: 'x86', versionRange: '<12'}]
}
},
[JavaDistribution.Oracle]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.Dragonwell]: {
platforms: {
linux: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.SapMachine]: {
platforms: {
linux: ['x64', 'aarch64', 'ppc64le'],
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.GraalVM]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.GraalVMCommunity]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.JetBrains]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: X64_ARM64
}
},
[JavaDistribution.Kona]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
},
[JavaDistribution.OracleOpenJdk]: {
platforms: {
linux: X64_ARM64,
macos: X64_ARM64,
windows: ['x64']
}
}
};
const ARCHITECTURE_ALIASES: Readonly<Record<string, JavaArchitecture>> = {
amd64: 'x64',
arm: 'armv7',
ia32: 'x86',
arm64: 'aarch64'
};
const CANONICAL_ARCHITECTURES: readonly JavaArchitecture[] = [
'x86',
'x64',
'armv7',
'aarch64',
'ppc64le',
'ppc64',
's390x'
];
const PLATFORM_ALIASES: Readonly<
Partial<Record<NodeJS.Platform, JavaPlatform>>
> = {
darwin: 'macos',
linux: 'linux',
sunos: 'solaris',
win32: 'windows'
};
export function normalizeArchitecture(architecture: string): string {
const trimmedArchitecture = architecture.trim();
const normalizedArchitecture = trimmedArchitecture.toLowerCase();
return (
ARCHITECTURE_ALIASES[normalizedArchitecture] ??
(CANONICAL_ARCHITECTURES.includes(
normalizedArchitecture as JavaArchitecture
)
? normalizedArchitecture
: trimmedArchitecture)
);
}
export function normalizePlatform(
platform: NodeJS.Platform
): JavaPlatform | undefined {
return PLATFORM_ALIASES[platform];
}
export function validateJavaPlatform(
distributionName: string,
platform: NodeJS.Platform,
architecture: string,
version: string
): string {
const normalizedArchitecture = normalizeArchitecture(architecture);
if (!isJavaDistribution(distributionName)) {
return normalizedArchitecture;
}
const capability = JAVA_PLATFORM_CAPABILITIES[distributionName];
if ('unrestricted' in capability && capability.unrestricted === true) {
return normalizedArchitecture;
}
const normalizedPlatform = normalizePlatform(platform);
const architectures = normalizedPlatform
? capability.platforms[normalizedPlatform]
: undefined;
const supported = architectures?.some(item => {
const architectureCapability =
typeof item === 'string' ? {architecture: item} : item;
return (
architectureCapability.architecture === normalizedArchitecture &&
(!('versionRange' in architectureCapability) ||
isVersionCompatible(version, architectureCapability.versionRange))
);
});
if (!supported) {
throw new Error(
`Distribution '${distributionName}' does not support operating system '${normalizedPlatform ?? platform}' with architecture '${normalizedArchitecture}' for Java version '${version}'. Supported combinations: ${formatSupportedCombinations(capability)}.`
);
}
return normalizedArchitecture;
}
function isJavaDistribution(value: string): value is JavaDistribution {
return Object.prototype.hasOwnProperty.call(
JAVA_PLATFORM_CAPABILITIES,
value
);
}
function isVersionCompatible(version: string, supportedRange: string): boolean {
let normalizedVersion = version.trim().toLowerCase();
if (normalizedVersion === 'latest') {
return true;
}
if (/^\d+(\.\d+){3,}$/.test(normalizedVersion)) {
normalizedVersion = normalizeExtendedVersionToSemver(normalizedVersion);
}
const requestedRange = semver.validRange(
normalizedVersion.replace(/-ea$/, '')
);
const capabilityRange = semver.validRange(supportedRange);
if (!requestedRange || !capabilityRange) {
return true;
}
function normalizeExtendedVersionToSemver(version: string): string {
const versionParts = version.split('.');
const mainVersion = versionParts.slice(0, 3).join('.');
if (versionParts.length > 3) {
return `${mainVersion}+${versionParts.slice(3).join('.')}`;
}
return version;
}
return semver.intersects(requestedRange, capabilityRange, {
includePrerelease: true
});
}
function formatSupportedCombinations(
capability: RestrictedPlatformCapability
): string {
return Object.entries(capability.platforms)
.map(([platform, architectures]) => {
const values = architectures.map(item =>
typeof item === 'string'
? item
: `${item.architecture} (${item.versionRange})`
);
return `${platform} (${values.join(', ')})`;
})
.join('; ');
}

View File

@@ -282,4 +282,9 @@ export class TemurinDistribution extends JavaBase {
return process.platform;
}
}
protected distributionArchitecture(): string {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}

View File

@@ -221,6 +221,8 @@ export class ZuluDistribution extends JavaBase {
// would let a 32-bit request resolve to a 64-bit JDK. Use "i686" to
// target only genuine 32-bit builds, matching the legacy API behavior.
return 'i686';
case 'armv7':
return 'arm';
case 'aarch64':
case 'arm64':
return 'aarch64';

View File

@@ -28,6 +28,7 @@ export async function run() {
const cacheDependencyPath = core.getInput(
constants.INPUT_CACHE_DEPENDENCY_PATH
);
const cachePath = core.getMultilineInput(constants.INPUT_CACHE_PATH);
const checkLatest = getBooleanInput(constants.INPUT_CHECK_LATEST, false);
const forceDownload = getBooleanInput(
constants.INPUT_FORCE_DOWNLOAD,
@@ -132,7 +133,7 @@ export async function run() {
await auth.configureAuthentication();
configureMavenArgs();
if (cache && isCacheFeatureAvailable()) {
await restore(cache, cacheDependencyPath);
await restore(cache, cacheDependencyPath, cachePath);
}
} catch (error) {
core.setFailed((error as Error).message);