Compare commits

..

13 Commits

Author SHA1 Message Date
Bruno Borges 3cc3643700 Optimize Temurin tool-cache fast path with lazy loading (#1179)
* Optimize Temurin tool-cache fast path

- Lazy-load distribution installers so only the selected distro module is initialized
- Defer cache feature/cache module loading until cache input is provided
- Start cache restore early and await it safely alongside Java setup flow
- Update orchestration and lazy-loading tests; regenerate dist artifacts

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cc2c0256-c55e-4a35-a584-5bed7e8b11ad

* Address PR review comments

- Lazy-load cache save in cleanup path so no-cache runs avoid cache module init in post action
- Stage dist/setup/package.json in release script for chunked setup bundle completeness

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cc2c0256-c55e-4a35-a584-5bed7e8b11ad

* Fix CodeQL comment tag filter finding

Patch is-unsafe's XML comment-close detector during builds so generated bundles recognize both HTML comment end forms and satisfy CodeQL until the dependency publishes a fix.

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

Copilot-Session: 277302b1-aa95-4012-817b-9752cdaee14e

* Rebuild generated dist bundles

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cc2c0256-c55e-4a35-a584-5bed7e8b11ad

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cc2c0256-c55e-4a35-a584-5bed7e8b11ad
Copilot-Session: 277302b1-aa95-4012-817b-9752cdaee14e
2026-07-29 17:53:33 -04:00
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
Bruno Borges 27f2c62824 Verify JDK downloads with vendor checksums (#1167)
* Verify JDK downloads with vendor checksums

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

Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

* Handle missing vendor checksum values

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

Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

* Preserve checksum error during cleanup failure

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

Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

* Validate checksum metadata value types

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

Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

* Clarify checksum documentation

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

Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

* Expand vendor checksum verification

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

Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

* Accept SHA-256 or SHA-512 for JetBrains checksum sibling

JetBrains publishes a single, generically-named ".checksum" sibling
whose digest algorithm isn't disclosed by the filename. Older JBR 11
builds (e.g. jbrsdk_nomod-11_0_16-*-b2043.64.tar.gz) publish a SHA-256
digest there, while newer builds publish SHA-512. The JetBrains
installer previously assumed SHA-512 unconditionally, so verification
failed with "Malformed sha512 checksum metadata ... expected a
128-character hexadecimal digest" for those older builds, breaking the
jetbrains 11 e2e job on macOS and Windows.

fetchChecksum now accepts a list of candidate algorithms and infers
the actual algorithm from the returned digest's length, preferring the
strongest match. The JetBrains installer passes ['sha512', 'sha256'];
all other callers are unaffected since they already pass a single,
vendor-disclosed algorithm.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

* Use SapMachine archive checksum files

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

Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d
2026-07-29 04:43:56 -04:00
Bruno Borges 19c23b379e Harden java-package validation (#1165)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 10b8fe1e-18f4-42cb-8672-11215715a713
2026-07-29 00:50:01 -04:00
Bruno Borges 6e26972896 Add setup orchestration tests (#1163)
Make the setup entrypoint import-safe and cover its validation, installation sequencing, post-install collaborators, caching, and failure handling directly.

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

Copilot-Session: a5ba8975-a9ac-4d2c-b41d-97c99689d1bd
2026-07-29 00:36:12 -04:00
Bruno Borges 5894ef6b27 Consolidate JDK metadata retry handling (#1162)
* Consolidate JDK metadata retries

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

Copilot-Session: a5e2549a-0d89-4c8f-b7f3-411ad21c8a06

* Expand distribution retry coverage

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

Copilot-Session: a5e2549a-0d89-4c8f-b7f3-411ad21c8a06

---------

Copilot-Session: a5e2549a-0d89-4c8f-b7f3-411ad21c8a06
2026-07-29 00:09:37 -04:00
Bruno Borges e1ce3a3428 Fail on mismatched Maven toolchain ID counts (#1161)
* Fail on mismatched Maven toolchain IDs

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

Copilot-Session: 8c821981-7b21-45fe-9463-5bb375d7dce4

* Clarify Maven toolchain ID version counts

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c821981-7b21-45fe-9463-5bb375d7dce4

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c821981-7b21-45fe-9463-5bb375d7dce4
2026-07-28 23:33:41 -04:00
Bruno Borges ce75feb3d3 Reject invalid boolean input values (#1160)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c6daa6b5-31f5-46b2-a994-b8320b50a50d
2026-07-28 22:43:39 -04:00
95 changed files with 139794 additions and 132800 deletions
@@ -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
+73 -1
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
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
+47 -4
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:
@@ -83,6 +79,15 @@ jobs:
- distribution: oracle
os: ubuntu-latest
version: 21
- distribution: oracle-openjdk
os: macos-15-intel
version: 21
- distribution: oracle-openjdk
os: windows-latest
version: 21
- distribution: oracle-openjdk
os: ubuntu-latest
version: 21
- distribution: graalvm
os: macos-latest
version: 17.0.12
@@ -116,6 +121,25 @@ jobs:
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash
setup-java-checksum-verification:
name: Corretto checksum verification - ubuntu-latest
runs-on: ubuntu-latest
steps:
- *checkout_step
- name: setup-java with forced download
uses: ./
id: setup-java
with:
java-version: '21'
distribution: corretto
force-download: true
- name: Verify Java
env:
JAVA_VERSION: '21'
JAVA_PATH: ${{ steps.setup-java.outputs.path }}
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash
setup-java-alpine-linux:
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - alpine-linux - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
@@ -471,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 }}
+93 -3
View File
@@ -48,9 +48,9 @@ For more details, see the full release notes on the [releases page](https://git
- `distribution`: Java [distribution](#supported-distributions). Required unless `java-version-file` points to `.sdkmanrc` with a recognized distribution suffix (for example `java=21.0.5-tem`).
- `java-package`: The packaging variant of the chosen distribution. Possible values: `jdk`, `jre`, `jdk+fx`, `jre+fx`. For Azul Zulu, `jdk+crac` and `jre+crac` are also supported. For Eclipse Temurin 24 and later, `jdk+jmods` includes the separately packaged JMOD files. Default value: `jdk`.
- `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.
@@ -89,12 +93,20 @@ For more details, see the full release notes on the [releases page](https://git
- `gpg-passphrase-env-var`: Environment variable name for the GPG private key passphrase. Default is GPG\_PASSPHRASE.
- `mvn-toolchain-id`: Name of Maven Toolchain ID if the default name of `${distribution}_${java-version}` is not wanted.
- `mvn-toolchain-id`: Name of Maven Toolchain ID if the default name of `${distribution}_${java-version}` is not wanted. When supplied, the number of IDs must match the number of Java versions.
- `mvn-toolchain-vendor`: Name of Maven Toolchain Vendor if the default name of `${distribution}` is not wanted.
- `show-download-progress`: Set to `true` to keep Maven artifact download and transfer progress in build logs. Default value: `false`. By default, the action adds `-ntp` (`--no-transfer-progress`) to `MAVEN_ARGS`. This input has no effect on non-Maven builds. See [Maven transfer progress](docs/advanced-usage.md#maven-transfer-progress-download-logs) for more details.
### Download integrity verification
When a selected distribution publishes an authoritative checksum for an archive, `setup-java` automatically verifies each downloaded JDK, JRE, or JMOD archive before extraction and caching. No input is required. Automatic checksum verification is currently available for `temurin`, `semeru`, `adopt`, `corretto`, `dragonwell`, `kona`, `sapmachine`, `graalvm`, `graalvm-community`, `zulu`, `oracle`, `oracle-openjdk`, `microsoft`, and `jetbrains`.
Distributions or individual releases without an authoritative checksum continue to install normally, with the omission reported only in debug logs. Archives resolved directly from the runner tool cache are not downloaded again and therefore are not reverified.
Checksums detect corrupted or unexpectedly modified downloads before they are persisted in the runner tool cache.
### Basic Configuration
#### Eclipse Temurin
@@ -178,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
@@ -267,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.
+135
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
+80
View File
@@ -0,0 +1,80 @@
import {jest, describe, it, expect, afterEach} from '@jest/globals';
jest.unstable_mockModule('@actions/cache', () => ({
isFeatureAvailable: jest.fn()
}));
jest.unstable_mockModule('@actions/core', () => ({
warning: jest.fn(),
debug: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
info: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
const cache = await import('@actions/cache');
const core = await import('@actions/core');
const {isCacheFeatureAvailable} = await import('../src/cache-feature.js');
describe('isCacheFeatureAvailable', () => {
it('is disabled on GHES when cache feature is unavailable', () => {
(cache.isFeatureAvailable as jest.Mock<any>).mockImplementation(
() => false
);
const warningMock = core.warning as jest.Mock;
const message =
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.';
try {
process.env['GITHUB_SERVER_URL'] = 'http://example.com';
expect(isCacheFeatureAvailable()).toBe(false);
expect(warningMock).toHaveBeenCalledWith(message);
} finally {
delete process.env['GITHUB_SERVER_URL'];
}
});
it('is disabled on dotcom when cache feature is unavailable', () => {
(cache.isFeatureAvailable as jest.Mock<any>).mockImplementation(
() => false
);
const warningMock = core.warning as jest.Mock;
const message =
'The runner was not able to contact the cache service. Caching will be skipped';
try {
process.env['GITHUB_SERVER_URL'] = 'http://github.com';
expect(isCacheFeatureAvailable()).toBe(false);
expect(warningMock).toHaveBeenCalledWith(message);
} finally {
delete process.env['GITHUB_SERVER_URL'];
}
});
it('is enabled when cache feature is available', () => {
(cache.isFeatureAvailable as jest.Mock<any>).mockImplementation(() => true);
expect(isCacheFeatureAvailable()).toBe(true);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
});
+229 -4
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);
@@ -0,0 +1 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
@@ -0,0 +1 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
+130
View File
@@ -0,0 +1,130 @@
import {afterEach, describe, expect, it, jest} from '@jest/globals';
import {createHash} from 'crypto';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {calculateChecksum, verifyChecksum} from '../src/checksum.js';
import type {ChecksumMetadata} from '../src/distributions/base-models.js';
const temporaryPaths: string[] = [];
async function temporaryFile(contents: string): Promise<string> {
const directory = await fs.promises.mkdtemp(
path.join(os.tmpdir(), 'setup-java-checksum-')
);
const file = path.join(directory, 'archive');
await fs.promises.writeFile(file, contents);
temporaryPaths.push(directory);
return file;
}
afterEach(async () => {
await Promise.all(
temporaryPaths
.splice(0)
.map(item => fs.promises.rm(item, {recursive: true, force: true}))
);
jest.restoreAllMocks();
});
describe('verifyChecksum', () => {
it.each(['sha256', 'sha512'] as const)(
'verifies a matching %s digest',
async algorithm => {
const contents = `jdk archive for ${algorithm}`;
const file = await temporaryFile(contents);
const value = createHash(algorithm).update(contents).digest('hex');
await expect(
verifyChecksum(
file,
{algorithm, value: value.toUpperCase()},
{distribution: 'Test', version: '21.0.1'}
)
).resolves.toBeUndefined();
}
);
it('reports mismatch context and both digests', async () => {
const file = await temporaryFile('corrupt archive');
const expected = 'a'.repeat(64);
const actual = await calculateChecksum(file, 'sha256');
await expect(
verifyChecksum(
file,
{algorithm: 'sha256', value: expected},
{distribution: 'Corretto', version: '21.0.8'}
)
).rejects.toThrow(
`Checksum verification failed for Corretto version 21.0.8: sha256 expected ${expected}, actual ${actual}.`
);
});
it('rejects malformed digest metadata before reading the file', async () => {
await expect(
verifyChecksum(
'/missing/archive',
{algorithm: 'sha512', value: 'not-a-digest'},
{distribution: 'Test', version: '17'}
)
).rejects.toThrow(
'Malformed sha512 checksum metadata: expected a 128-character hexadecimal digest.'
);
});
it.each([undefined, null, 123])(
'reports a malformed digest when the value is %p',
async value => {
const checksum = {
algorithm: 'sha256',
value
} as unknown as ChecksumMetadata;
await expect(
verifyChecksum('/missing/archive', checksum, {
distribution: 'Test',
version: '17'
})
).rejects.toThrow(
'Malformed sha256 checksum metadata: expected a 64-character hexadecimal digest.'
);
}
);
it('rejects unsupported algorithms without leaking source query parameters', async () => {
const checksum = {
algorithm: 'md5',
value: 'a'.repeat(32),
source: 'https://vendor.example/checksum.txt?token=secret-value#private'
} as unknown as ChecksumMetadata;
let message = '';
try {
await verifyChecksum('/missing/archive', checksum, {
distribution: 'Test',
version: '17'
});
} catch (error) {
message = (error as Error).message;
}
expect(message).toContain(
"Unsupported checksum algorithm 'md5' from https://vendor.example/checksum.txt"
);
expect(message).not.toContain('secret-value');
expect(message).not.toContain('token=');
expect(message).not.toContain('#private');
});
it('surfaces file read errors', async () => {
await expect(
verifyChecksum(
'/missing/archive',
{algorithm: 'sha256', value: 'a'.repeat(64)},
{distribution: 'Test', version: '17'}
)
).rejects.toMatchObject({code: 'ENOENT'});
});
});
+58
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 '';
}
});
}
@@ -260,6 +260,7 @@ describe('getAvailableVersions', () => {
it.each([
['amd64', 'x64'],
['arm', 'arm'],
['arm64', 'aarch64']
])(
'defaults to os.arch(): %s mapped to distro arch: %s',
@@ -357,6 +358,14 @@ describe('findPackageForDownload', () => {
distribution['getAvailableVersions'] = async () => manifestData as any;
const resolvedVersion = await distribution['findPackageForDownload'](input);
expect(resolvedVersion.version).toBe(expected);
const vendorPackage = (manifestData as any[]).find(
item => item.version_data.semver === expected
).binaries[0].package;
expect(resolvedVersion.checksum).toEqual({
algorithm: 'sha256',
value: vendorPackage.checksum,
source: vendorPackage.checksum_link
});
});
it('version is found but binaries list is empty', async () => {
@@ -16,6 +16,9 @@ import type {
import path from 'path';
import * as semver from 'semver';
import fs from 'fs';
import {createHash} from 'crypto';
import {HttpClient} from '@actions/http-client';
import os from 'os';
@@ -117,6 +120,17 @@ class EmptyJavaBase extends JavaBase {
url: `some/random_url/java/${availableVersion}`
};
}
public downloadRelease(javaRelease: JavaDownloadRelease): Promise<string> {
return this.downloadAndVerify(javaRelease);
}
public fetchChecksumForTest(
checksumUrl: string,
algorithm: 'sha256' | 'sha512' | ('sha256' | 'sha512')[]
) {
return this.fetchChecksum(checksumUrl, algorithm);
}
}
describe('findInToolcache', () => {
@@ -605,6 +619,31 @@ describe('setupJava', () => {
expect(spyCoreSetOutput).not.toHaveBeenCalled();
});
it('should not repeat version resolution when downloadTool fails', async () => {
mockJavaBase = new EmptyJavaBase({
version: '11',
architecture: 'x86',
packageType: 'jdk',
checkLatest: false,
forceDownload: true
});
const findPackageForDownload = jest.fn(async () => ({
version: '11.0.9',
url: 'https://example.com/jdk.tar.gz'
}));
const downloadError = new Error('download failed');
const downloadTool = jest.fn(async () => {
throw downloadError;
});
mockJavaBase['findPackageForDownload'] = findPackageForDownload;
mockJavaBase['downloadTool'] = downloadTool;
await expect(mockJavaBase.setupJava()).rejects.toBe(downloadError);
expect(findPackageForDownload).toHaveBeenCalledTimes(1);
expect(downloadTool).toHaveBeenCalledTimes(1);
});
it.each([
[
{
@@ -792,6 +831,306 @@ describe('setupJava', () => {
});
});
describe('downloadAndVerify', () => {
const options: JavaInstallerOptions = {
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
};
let temporaryDirectory: string;
let archivePath: string;
beforeEach(async () => {
temporaryDirectory = await fs.promises.mkdtemp(
path.join(os.tmpdir(), 'setup-java-base-')
);
archivePath = path.join(temporaryDirectory, 'archive');
await fs.promises.writeFile(archivePath, 'downloaded archive');
(tc.downloadTool as jest.Mock<any>).mockResolvedValue(archivePath);
});
afterEach(async () => {
await fs.promises.rm(temporaryDirectory, {recursive: true, force: true});
jest.resetAllMocks();
});
it('returns a download after successful verification', async () => {
const distribution = new EmptyJavaBase(options);
const result = await distribution.downloadRelease({
version: '21.0.8',
url: 'https://vendor.example/jdk.tar.gz',
checksum: {
algorithm: 'sha256',
value: createHash('sha256').update('downloaded archive').digest('hex')
}
});
expect(result).toBe(archivePath);
expect(fs.existsSync(archivePath)).toBe(true);
expect(core.debug).toHaveBeenCalledWith(
'Verified sha256 checksum for Empty version 21.0.8.'
);
});
it('removes the download after verification failure', async () => {
const distribution = new EmptyJavaBase(options);
await expect(
distribution.downloadRelease({
version: '21.0.8',
url: 'https://vendor.example/jdk.tar.gz?token=secret',
checksum: {algorithm: 'sha256', value: 'a'.repeat(64)}
})
).rejects.toThrow('Checksum verification failed for Empty version 21.0.8');
expect(fs.existsSync(archivePath)).toBe(false);
});
it('preserves the verification error when removing the download fails', async () => {
const distribution = new EmptyJavaBase(options);
const cleanupError = new Error('cleanup failed');
jest.spyOn(fs.promises, 'rm').mockRejectedValueOnce(cleanupError);
const result = distribution.downloadRelease({
version: '21.0.8',
url: 'https://vendor.example/jdk.tar.gz',
checksum: {algorithm: 'sha256', value: 'a'.repeat(64)}
});
await expect(result).rejects.toMatchObject({
message: expect.stringContaining(
'Failed to remove the downloaded archive after verification failure: cleanup failed'
),
cause: expect.objectContaining({
message: expect.stringContaining(
'Checksum verification failed for Empty version 21.0.8'
)
})
});
});
it('logs when authoritative checksum metadata is unavailable', async () => {
const distribution = new EmptyJavaBase(options);
await expect(
distribution.downloadRelease({
version: '21.0.8',
url: 'https://vendor.example/jdk.tar.gz'
})
).resolves.toBe(archivePath);
expect(core.debug).toHaveBeenCalledWith(
'No authoritative checksum is available for Empty version 21.0.8; skipping checksum verification.'
);
});
it.each([undefined, '', ' '])(
'skips verification when the vendor digest is %p',
async value => {
const distribution = new EmptyJavaBase(options);
await expect(
distribution.downloadRelease({
version: '21.0.8',
url: 'https://vendor.example/jdk.tar.gz',
checksum: {
algorithm: 'sha256',
value
} as JavaDownloadRelease['checksum']
})
).resolves.toBe(archivePath);
expect(core.debug).toHaveBeenCalledWith(
'No authoritative checksum is available for Empty version 21.0.8; skipping checksum verification.'
);
}
);
});
describe('fetchChecksum', () => {
const options: JavaInstallerOptions = {
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
};
afterEach(() => {
jest.restoreAllMocks();
});
function mockGet(statusCode: number, body: string) {
return jest.spyOn(HttpClient.prototype, 'get').mockResolvedValue({
message: {statusCode},
readBody: async () => body
} as any);
}
it('parses a bare hex digest', async () => {
const digest = 'a'.repeat(64);
const spy = mockGet(200, digest);
const distribution = new EmptyJavaBase(options);
const checksum = await distribution.fetchChecksumForTest(
'https://vendor.example/jdk.tar.gz.sha256',
'sha256'
);
expect(spy).toHaveBeenCalledWith(
'https://vendor.example/jdk.tar.gz.sha256'
);
expect(checksum).toEqual({
algorithm: 'sha256',
value: digest,
source: 'https://vendor.example/jdk.tar.gz.sha256'
});
});
it('parses only the first token of a GNU-style checksum file', async () => {
const digest = 'b'.repeat(128);
mockGet(200, `${digest} jbrsdk-21.0.3-linux-x64-b465.3.tar.gz\n`);
const distribution = new EmptyJavaBase(options);
const checksum = await distribution.fetchChecksumForTest(
'https://vendor.example/jdk.tar.gz.checksum',
'sha512'
);
expect(checksum).toEqual({
algorithm: 'sha512',
value: digest,
source: 'https://vendor.example/jdk.tar.gz.checksum'
});
});
it('trims surrounding whitespace and newlines', async () => {
const digest = 'c'.repeat(64);
mockGet(200, `\n ${digest} \n`);
const distribution = new EmptyJavaBase(options);
const checksum = await distribution.fetchChecksumForTest(
'https://vendor.example/jdk.tar.gz.sha256',
'sha256'
);
expect(checksum.value).toBe(digest);
});
it('skips verification when the sibling checksum is not published', async () => {
mockGet(404, 'Not Found');
const distribution = new EmptyJavaBase(options);
await expect(
distribution.fetchChecksumForTest(
'https://vendor.example/jdk.tar.gz.sha256',
'sha256'
)
).resolves.toBeUndefined();
expect(core.debug).toHaveBeenCalledWith(
'No authoritative sha256 checksum is available for Empty from https://vendor.example/jdk.tar.gz.sha256; skipping checksum verification.'
);
});
it('surfaces unexpected HTTP failures without query parameters', async () => {
mockGet(500, 'Server Error');
const distribution = new EmptyJavaBase(options);
await expect(
distribution.fetchChecksumForTest(
'https://vendor.example/jdk.tar.gz.sha256?token=secret',
'sha256'
)
).rejects.toThrow(
'Failed to fetch the authoritative sha256 checksum for Empty from https://vendor.example/jdk.tar.gz.sha256 (HTTP 500).'
);
});
it('rejects an empty successful checksum response', async () => {
mockGet(200, ' \n');
const distribution = new EmptyJavaBase(options);
await expect(
distribution.fetchChecksumForTest(
'https://vendor.example/jdk.tar.gz.sha256',
'sha256'
)
).rejects.toThrow(
'Received an empty authoritative sha256 checksum for Empty from https://vendor.example/jdk.tar.gz.sha256.'
);
});
describe('with a list of candidate algorithms', () => {
it('infers sha512 when the digest is 128 hex characters', async () => {
const digest = 'd'.repeat(128);
mockGet(200, `${digest} jbrsdk.tar.gz\n`);
const distribution = new EmptyJavaBase(options);
const checksum = await distribution.fetchChecksumForTest(
'https://vendor.example/jbrsdk.tar.gz.checksum',
['sha512', 'sha256']
);
expect(checksum).toEqual({
algorithm: 'sha512',
value: digest,
source: 'https://vendor.example/jbrsdk.tar.gz.checksum'
});
});
it('infers sha256 when the digest is 64 hex characters, even though sha512 was preferred', async () => {
// Reproduces older JetBrains JBR builds (e.g. JBR 11), which publish a
// SHA-256 digest at the generic `.checksum` sibling instead of SHA-512.
const digest = 'e'.repeat(64);
mockGet(200, `${digest} jbrsdk_nomod-11_0_16-osx-x64-b2043.64.tar.gz\n`);
const distribution = new EmptyJavaBase(options);
const checksum = await distribution.fetchChecksumForTest(
'https://vendor.example/jbrsdk_nomod-11_0_16-osx-x64-b2043.64.tar.gz.checksum',
['sha512', 'sha256']
);
expect(checksum).toEqual({
algorithm: 'sha256',
value: digest,
source:
'https://vendor.example/jbrsdk_nomod-11_0_16-osx-x64-b2043.64.tar.gz.checksum'
});
});
it('falls back to the first candidate algorithm when the digest length matches none of them', async () => {
const digest = 'f'.repeat(40); // e.g. sha1, not supported
mockGet(200, `${digest} jbrsdk.tar.gz\n`);
const distribution = new EmptyJavaBase(options);
const checksum = await distribution.fetchChecksumForTest(
'https://vendor.example/jbrsdk.tar.gz.checksum',
['sha512', 'sha256']
);
// No candidate algorithm matches, so the first-listed one is kept;
// downstream verification will reject it as malformed.
expect(checksum.algorithm).toBe('sha512');
expect(checksum.value).toBe(digest);
});
it('reports the checksum as unavailable using a combined algorithm label on 404', async () => {
mockGet(404, 'Not Found');
const distribution = new EmptyJavaBase(options);
await expect(
distribution.fetchChecksumForTest(
'https://vendor.example/jbrsdk.tar.gz.checksum',
['sha512', 'sha256']
)
).resolves.toBeUndefined();
expect(core.debug).toHaveBeenCalledWith(
'No authoritative sha512 or sha256 checksum is available for Empty from https://vendor.example/jbrsdk.tar.gz.checksum; skipping checksum verification.'
);
});
});
});
describe('normalizeVersion', () => {
const DummyJavaBase = JavaBase as any;
@@ -202,6 +202,12 @@ describe('getAvailableVersions', () => {
await distribution['findPackageForDownload'](version);
expect(availableVersion).not.toBeNull();
expect(availableVersion.url).toBe(expectedLink);
expect(availableVersion.checksum).toEqual({
algorithm: 'sha256',
value: expect.stringMatching(/^[a-f0-9]{64}$/),
source:
'https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json'
});
});
it('with latest resolves to the newest available major version', async () => {
@@ -293,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 = (
@@ -0,0 +1,23 @@
import {jest, describe, it, expect} from '@jest/globals';
jest.unstable_mockModule('../../src/distributions/zulu/installer.js', () => {
throw new Error(
'Zulu installer module must not be imported on the Temurin fast path'
);
});
const {getJavaDistribution} =
await import('../../src/distributions/distribution-factory.js');
describe('distribution factory lazy loading', () => {
it('does not load non-selected distribution installers for Temurin', async () => {
const distribution = await getJavaDistribution('temurin', {
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
expect(distribution).not.toBeNull();
});
});
@@ -1,16 +1,151 @@
import {getJavaDistribution} from '../../src/distributions/distribution-factory.js';
import {RetryingHttpClient} from '../../src/retrying-http-client.js';
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,
architecture: 'x64',
packageType,
checkLatest: false
});
describe('getJavaDistribution', () => {
it("rejects java-package 'jdk+jmods' for non-Temurin distributions", () => {
expect(() =>
getJavaDistribution('zulu', {
it.each(supportedDistributionsOnCurrentPlatform)(
'uses the shared retrying HTTP client for %s',
async distributionName => {
const distribution = await getJavaDistribution(distributionName, {
version: '25',
architecture: 'x64',
packageType: 'jdk+jmods',
packageType: 'jdk',
checkLatest: false
})
).toThrow(
"java-package 'jdk+jmods' is only supported for distribution 'temurin'."
});
expect(distribution).not.toBeNull();
expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient);
}
);
it.each(
Object.entries(JAVA_PACKAGE_CAPABILITIES).flatMap(
([distributionName, packageTypes]) =>
supportedDistributionsOnCurrentPlatform.includes(
distributionName as JavaDistribution
) || distributionName === JavaDistribution.JdkFile
? packageTypes.map(packageType => [distributionName, packageType])
: []
)
)(
'accepts %s with java-package %s',
async (distributionName, packageType) => {
expect(
await getJavaDistribution(
distributionName,
installerOptions(packageType as string)
)
).not.toBeNull();
}
);
it.each(Object.entries(JAVA_PACKAGE_CAPABILITIES))(
'rejects unsupported java-package values for %s',
async (distributionName, packageTypes) => {
await expect(
getJavaDistribution(distributionName, installerOptions('jdk+typo'))
).rejects.toThrow(
`Java package 'jdk+typo' is not supported for distribution '${distributionName}'. Supported package types: ${packageTypes.join(', ')}.`
);
}
);
it("rejects java-package 'jdk+jmods' for non-Temurin distributions", async () => {
await expect(
getJavaDistribution('zulu', installerOptions('jdk+jmods'))
).rejects.toThrow(
"Java package 'jdk+jmods' is not supported for distribution 'zulu'. Supported package types: jdk, jre, jdk+fx, jre+fx, jdk+crac, jre+crac."
);
});
it.each(['8', '23.x', '23.0.1.1', '<24'])(
"rejects Temurin java-package 'jdk+jmods' for version %s",
async version => {
await expect(
getJavaDistribution(
JavaDistribution.Temurin,
installerOptions('jdk+jmods', version)
)
).rejects.toThrow(
`Java package 'jdk+jmods' is not supported for distribution 'temurin'. Supported package types: jdk, jre, jdk+jmods. Package 'jdk+jmods' requires Java 24 or later; requested version '${version}'.`
);
}
);
it.each(['24', '24.0.1.1', '25-ea', '>=21', 'latest'])(
"accepts Temurin java-package 'jdk+jmods' for version %s",
async version => {
expect(
await getJavaDistribution(
JavaDistribution.Temurin,
installerOptions('jdk+jmods', version)
)
).not.toBeNull();
}
);
it('preserves unsupported distribution handling', async () => {
expect(
await getJavaDistribution(
'not-a-distribution',
installerOptions('not-a-package')
)
).toBeNull();
});
it.each([
['amd64', 'x64'],
['ia32', 'x86'],
['arm64', 'aarch64']
])('passes normalized architecture %s as %s', async (input, expected) => {
const normalized = await getJavaDistribution(JavaDistribution.JdkFile, {
...installerOptions('jdk'),
architecture: input
});
expect(normalized!['architecture']).toBe(expected);
});
it('uses the runner architecture when the input is empty', async () => {
const distribution = await 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', async () => {
await expect(
getJavaDistribution(JavaDistribution.Oracle, {
...installerOptions('jdk'),
architecture: 'x86'
})
).rejects.toThrow(/does not support operating system/);
});
});
@@ -259,6 +259,10 @@ describe('getAvailableVersions', () => {
await distribution['findPackageForDownload'](jdkVersion);
expect(availableVersion).not.toBeNull();
expect(availableVersion.url).toBe(expectedLink);
expect(availableVersion.checksum).toEqual({
algorithm: 'sha256',
value: expect.stringMatching(/^[a-f0-9]{64}$/)
});
}
);
@@ -129,6 +129,14 @@ describe('GraalVMDistribution', () => {
(distribution as any).http = mockHttpClient;
(communityDistribution as any).http = mockHttpClient;
// Default checksum sibling response for `${url}.sha256` requests made by
// GraalVM (Oracle) and GraalVM EA. Individual tests override this when
// they need to assert the exact URL/digest contract.
mockHttpClient.get.mockResolvedValue({
message: {statusCode: 200},
readBody: jest.fn().mockResolvedValue('a'.repeat(64))
});
(util.getDownloadArchiveExtension as jest.Mock<any>).mockReturnValue(
'tar.gz'
);
@@ -407,9 +415,16 @@ describe('GraalVMDistribution', () => {
expect(result).toEqual({
url: 'https://download.oracle.com/graalvm/17/archive/graalvm-jdk-17.0.5_linux-x64_bin.tar.gz',
version: '17.0.5'
version: '17.0.5',
checksum: {
algorithm: 'sha256',
value: 'a'.repeat(64),
source:
'https://download.oracle.com/graalvm/17/archive/graalvm-jdk-17.0.5_linux-x64_bin.tar.gz.sha256'
}
});
expect(mockHttpClient.head).toHaveBeenCalledWith(result.url);
expect(mockHttpClient.get).toHaveBeenCalledWith(`${result.url}.sha256`);
});
it('should construct correct URL for major version (latest)', async () => {
@@ -422,7 +437,13 @@ describe('GraalVMDistribution', () => {
expect(result).toEqual({
url: 'https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz',
version: '21'
version: '21',
checksum: {
algorithm: 'sha256',
value: 'a'.repeat(64),
source:
'https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz.sha256'
}
});
});
@@ -465,7 +486,13 @@ describe('GraalVMDistribution', () => {
expect(result).toEqual({
url: 'https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz',
version: '25'
version: '25',
checksum: {
algorithm: 'sha256',
value: 'a'.repeat(64),
source:
'https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz.sha256'
}
});
});
@@ -637,13 +664,20 @@ describe('GraalVMDistribution', () => {
expect(result).toEqual({
url: 'https://example.com/download/graalvm-jdk-23_linux-x64_bin.tar.gz',
version: '23-ea-20240716'
version: '23-ea-20240716',
checksum: {
algorithm: 'sha256',
value: 'a'.repeat(64),
source:
'https://example.com/download/graalvm-jdk-23_linux-x64_bin.tar.gz.sha256'
}
});
expect(mockHttpClient.getJson).toHaveBeenCalledWith(
'https://api.github.com/repos/graalvm/oracle-graalvm-ea-builds/contents/versions/23-ea.json?ref=main',
{Accept: 'application/json'}
);
expect(mockHttpClient.get).toHaveBeenCalledWith(`${result.url}.sha256`);
});
it('should throw error when no latest EA version found', async () => {
@@ -876,8 +910,15 @@ describe('GraalVMDistribution', () => {
expect(fetchEASpy).toHaveBeenCalledWith('23-ea');
expect(result).toEqual({
url: 'https://example.com/download/graalvm-jdk-23_linux-x64_bin.tar.gz',
version: '23-ea-20240716'
version: '23-ea-20240716',
checksum: {
algorithm: 'sha256',
value: 'a'.repeat(64),
source:
'https://example.com/download/graalvm-jdk-23_linux-x64_bin.tar.gz.sha256'
}
});
expect(mockHttpClient.get).toHaveBeenCalledWith(`${result.url}.sha256`);
// Verify debug logging
expect(core.debug).toHaveBeenCalledWith('Searching for EA build: 23-ea');
@@ -976,7 +1017,13 @@ describe('GraalVMDistribution', () => {
expect(result).toEqual({
url: 'https://example.com/download/graalvm-jdk-23_linux-aarch64_bin.tar.gz',
version: '23-ea-20240716'
version: '23-ea-20240716',
checksum: {
algorithm: 'sha256',
value: 'a'.repeat(64),
source:
'https://example.com/download/graalvm-jdk-23_linux-aarch64_bin.tar.gz.sha256'
}
});
});
@@ -1151,6 +1198,80 @@ describe('GraalVMDistribution', () => {
url: 'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
version: '21.0.2'
});
// The asset had no `digest` field, so no checksum should be attached,
// and the checksum sibling-URL fetch path (used by Oracle GraalVM)
// must not be consulted for GraalVM Community.
expect(result.checksum).toBeUndefined();
expect(mockHttpClient.get).not.toHaveBeenCalled();
});
it('strips the `sha256:` prefix from a GitHub release asset digest', async () => {
const digest = 'd'.repeat(64);
mockHttpClient.getJson.mockResolvedValue({
result: [
{
draft: false,
prerelease: false,
assets: [
{
name: 'graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
browser_download_url:
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
digest: `sha256:${digest}`
}
]
}
],
statusCode: 200,
headers: {}
});
const result = await (
communityDistribution as any
).findPackageForDownload('21.0.2');
expect(result.checksum).toEqual({
algorithm: 'sha256',
value: digest,
source:
'https://api.github.com/repos/graalvm/graalvm-ce-builds/releases?per_page=100'
});
// The digest came from the release listing itself, so no additional
// HTTP request should be made to resolve the checksum.
expect(mockHttpClient.get).not.toHaveBeenCalled();
});
it('safely skips a missing or malformed release asset digest', async () => {
mockHttpClient.getJson.mockResolvedValue({
result: [
{
draft: false,
prerelease: false,
assets: [
{
name: 'graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
browser_download_url:
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
digest: 'md5:not-a-sha256-digest'
}
]
}
],
statusCode: 200,
headers: {}
});
const result = await (
communityDistribution as any
).findPackageForDownload('21.0.2');
expect(result.checksum).toBeUndefined();
expect(mockHttpClient.get).not.toHaveBeenCalled();
expect(core.debug).toHaveBeenCalledWith(
expect.stringContaining(
'No authoritative sha256 digest is available for graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz'
)
);
});
it('should resolve the latest GraalVM Community release for a major version', async () => {
@@ -1282,8 +1403,11 @@ describe('distribution factory', () => {
checkLatest: false
};
it('should map graalvm-community to the community installer', () => {
const community = getJavaDistribution('graalvm-community', defaultOptions);
it('should map graalvm-community to the community installer', async () => {
const community = await getJavaDistribution(
'graalvm-community',
defaultOptions
);
expect(community).toBeInstanceOf(GraalVMCommunityDistribution);
});
@@ -9,7 +9,9 @@ import {
afterAll
} from '@jest/globals';
import https from 'https';
import {HttpClient} from '@actions/http-client';
import {HttpClient, HttpClientResponse} from '@actions/http-client';
import type {IncomingMessage} from 'http';
import {Readable} from 'stream';
import manifestData from '../data/jetbrains.json' with {type: 'json'};
import os from 'os';
@@ -44,6 +46,18 @@ jest.unstable_mockModule('@actions/core', () => ({
const core = await import('@actions/core');
const {JetBrainsDistribution} =
await import('../../src/distributions/jetbrains/installer.js');
const {RetryingHttpClient} = await import('../../src/retrying-http-client.js');
function response(
statusCode: number,
body = '',
headers: IncomingMessage['headers'] = {}
): HttpClientResponse {
const message = Readable.from([Buffer.from(body)]) as IncomingMessage;
message.statusCode = statusCode;
message.headers = headers;
return new HttpClientResponse(message);
}
describe('getAvailableVersions', () => {
let spyHttpClient: any;
@@ -95,9 +109,66 @@ describe('getAvailableVersions', () => {
os.platform() === 'win32' ? manifestData.length : manifestData.length + 2;
expect(availableVersions.length).toBe(length);
}, 10_000);
it('retries a GitHub rate limit using Retry-After', async () => {
spyHttpClient.mockRestore();
const sleep = jest.fn(async () => undefined);
const requestRaw = jest
.spyOn(HttpClient.prototype, 'requestRaw')
.mockResolvedValueOnce(response(429, '', {'retry-after': '2'}))
.mockResolvedValueOnce(response(200, '[]'))
.mockResolvedValueOnce(response(200))
.mockResolvedValueOnce(response(200));
const distribution = new JetBrainsDistribution({
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['http'] = new RetryingHttpClient('test', {
sleep,
random: () => 0
});
const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).toHaveLength(2);
expect(requestRaw).toHaveBeenCalledTimes(4);
expect(requestRaw.mock.calls[0][0].options.path).toBe(
requestRaw.mock.calls[1][0].options.path
);
expect(requestRaw.mock.calls[0][0].options.path).toContain(
'/repos/JetBrains/JetBrainsRuntime/releases'
);
expect(sleep).toHaveBeenCalledWith(2000);
expect(core.info).toHaveBeenCalledWith(
'Request attempt 1 of 4 failed (HTTP 429); retrying in 2000 ms'
);
});
});
describe('findPackageForDownload', () => {
let spyHttpClientGet: any;
const JETBRAINS_CHECKSUM = 'c'.repeat(128);
beforeEach(() => {
// Every resolved release fetches `${url}.checksum` (sha512, GNU
// `<hex> <filename>` format); stub it so tests never reach the real
// network, except the dedicated 'version %s can be downloaded' test
// below which intentionally exercises real HTTPS HEAD requests.
spyHttpClientGet = jest
.spyOn(HttpClient.prototype, 'get')
.mockResolvedValue({
message: {statusCode: 200},
readBody: async () => `${JETBRAINS_CHECKSUM} jbrsdk.tar.gz\n`
} as any);
});
afterEach(() => {
jest.restoreAllMocks();
});
it.each([
['17', '17.0.11+1207.24'],
['11.0', '11.0.16+2043.64'],
@@ -181,4 +252,72 @@ describe('findPackageForDownload', () => {
/No matching version found for SemVer */
);
});
it('fetches the authoritative sha512 checksum only for the resolved version', async () => {
const distribution = new JetBrainsDistribution({
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => manifestData as any;
const result = await distribution['findPackageForDownload']('21');
expect(result.checksum).toEqual({
algorithm: 'sha512',
value: JETBRAINS_CHECKSUM,
source: `${result.url}.checksum`
});
// Only the single resolved/winning version's checksum is requested,
// not one per candidate considered during version resolution.
expect(spyHttpClientGet).toHaveBeenCalledWith(`${result.url}.checksum`);
expect(spyHttpClientGet).toHaveBeenCalledTimes(1);
});
it('parses only the first whitespace-delimited token from the GNU checksum payload', async () => {
spyHttpClientGet.mockResolvedValue({
message: {statusCode: 200},
readBody: async () => `${JETBRAINS_CHECKSUM} jbrsdk-21.tar.gz\n`
} as any);
const distribution = new JetBrainsDistribution({
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => manifestData as any;
const result = await distribution['findPackageForDownload']('21');
expect(result.checksum?.value).toBe(JETBRAINS_CHECKSUM);
});
it('falls back to a sha256 checksum for older JBR builds that only publish one', async () => {
// Older JBR 11 builds (e.g. jbrsdk_nomod-11_0_16-*-b2043.64.tar.gz) publish
// a SHA-256 digest at the generic `.checksum` sibling instead of SHA-512.
const sha256Checksum = 'a'.repeat(64);
spyHttpClientGet.mockResolvedValue({
message: {statusCode: 200},
readBody: async () =>
`${sha256Checksum} jbrsdk_nomod-11_0_16-osx-x64-b2043.64.tar.gz\n`
} as any);
const distribution = new JetBrainsDistribution({
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => manifestData as any;
const result = await distribution['findPackageForDownload']('21');
expect(result.checksum).toEqual({
algorithm: 'sha256',
value: sha256Checksum,
source: `${result.url}.checksum`
});
});
});
@@ -216,6 +216,15 @@ describe('Check findPackageForDownload', () => {
await distribution['findPackageForDownload'](version);
expect(availableRelease).not.toBeNull();
expect(availableRelease.url).toBe(expectedUrl);
if (availableRelease.checksum) {
expect(availableRelease.checksum).toEqual({
algorithm: 'sha256',
value: expect.stringMatching(/^[a-f0-9]{64}$/),
source: 'https://tencent.github.io/konajdk/releases/kona-v1.json'
});
} else {
expect(version).toBe('8.0.20');
}
}
);
});
@@ -103,9 +103,12 @@ const util = await import('../../src/util.js');
describe('findPackageForDownload', () => {
let distribution: InstanceType<typeof MicrosoftDistributions>;
let spyGetManifestFromRepo: any;
let spyHttpClientGet: any;
let spyDebug: any;
let spyCoreError: any;
const MICROSOFT_CHECKSUM = 'b'.repeat(64);
beforeEach(() => {
mockOsArch.mockReturnValue('x64');
mockOsPlatform.mockReturnValue(process.platform);
@@ -124,6 +127,15 @@ describe('findPackageForDownload', () => {
headers: {}
});
// Every resolved release fetches `${download_url}.sha256sum.txt`; stub
// it with a GNU-style `<hex> <filename>` payload so tests never reach
// the real network.
spyHttpClientGet = jest.spyOn(HttpClient.prototype, 'get');
spyHttpClientGet.mockResolvedValue({
message: {statusCode: 200},
readBody: async () => `${MICROSOFT_CHECKSUM} microsoft-jdk.tar.gz\n`
});
spyDebug = core.debug as jest.Mock;
spyDebug.mockImplementation(() => {});
@@ -311,6 +323,34 @@ describe('findPackageForDownload', () => {
'https://example.test/jdk.tar.gz.custom.sig'
);
});
it('fetches the authoritative sha256 checksum from the GNU-style sibling file', async () => {
mockOsPlatform.mockReturnValue(process.platform);
const result = await distribution['findPackageForDownload']('17.0.7');
expect(result.checksum).toEqual({
algorithm: 'sha256',
value: MICROSOFT_CHECKSUM,
source: `${result.url}.sha256sum.txt`
});
expect(spyHttpClientGet).toHaveBeenCalledWith(
`${result.url}.sha256sum.txt`
);
expect(spyHttpClientGet).toHaveBeenCalledTimes(1);
});
it('parses only the first whitespace-delimited token from the GNU checksum payload', async () => {
spyHttpClientGet.mockResolvedValue({
message: {statusCode: 200},
readBody: async () =>
`${MICROSOFT_CHECKSUM} microsoft-jdk-17.0.7-linux-x64.tar.gz\n`
});
const result = await distribution['findPackageForDownload']('17.0.7');
expect(result.checksum?.value).toBe(MICROSOFT_CHECKSUM);
});
});
describe('downloadTool', () => {
@@ -50,6 +50,14 @@ const archivePage = `
<th>9.0.4 (build 9.0.4+11)</th>
<a href="https://download.java.net/java/GA/jdk9/9.0.4/binaries/openjdk-9.0.4_linux-x64_bin.tar.gz">tar.gz</a>
`;
const GA_CHECKSUM = 'c'.repeat(64);
const EA_CHECKSUM = 'd'.repeat(64);
const checksumPages: Record<string, string> = {
'https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz.sha256':
GA_CHECKSUM,
'https://download.java.net/java/early_access/jdk27/32/GPL/openjdk-27-ea+32_linux-x64_bin.tar.gz.sha256':
EA_CHECKSUM
};
function createDistribution(
version = '26',
@@ -82,8 +90,16 @@ describe('OpenJdkDistribution', () => {
'https://jdk.java.net/27/': earlyAccessPage,
'https://jdk.java.net/archive/': archivePage
};
if (url in pages) {
return {
message: {statusCode: 200},
readBody: async () => pages[url]
} as Awaited<ReturnType<HttpClient['get']>>;
}
// Any other GET is a `${archiveUrl}.sha256` checksum sibling request.
return {
readBody: async () => pages[url] ?? ''
message: {statusCode: 200},
readBody: async () => checksumPages[url] ?? 'e'.repeat(64)
} as Awaited<ReturnType<HttpClient['get']>>;
});
});
@@ -97,8 +113,15 @@ describe('OpenJdkDistribution', () => {
expect(result).toEqual({
version: '26.0.2+10',
url: 'https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz'
url: 'https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz',
checksum: {
algorithm: 'sha256',
value: GA_CHECKSUM,
source:
'https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz.sha256'
}
});
expect(getSpy).toHaveBeenCalledWith(`${result.url}.sha256`);
});
it('resolves an archived GA release', async () => {
@@ -144,9 +167,16 @@ describe('OpenJdkDistribution', () => {
expect(result).toEqual({
version: '27.0.0+32',
url: 'https://download.java.net/java/early_access/jdk27/32/GPL/openjdk-27-ea+32_linux-x64_bin.tar.gz'
url: 'https://download.java.net/java/early_access/jdk27/32/GPL/openjdk-27-ea+32_linux-x64_bin.tar.gz',
checksum: {
algorithm: 'sha256',
value: EA_CHECKSUM,
source:
'https://download.java.net/java/early_access/jdk27/32/GPL/openjdk-27-ea+32_linux-x64_bin.tar.gz.sha256'
}
});
expect(getSpy).not.toHaveBeenCalledWith('https://jdk.java.net/archive/');
expect(getSpy).toHaveBeenCalledWith(`${result.url}.sha256`);
});
it('reports available versions when no release matches', async () => {
@@ -203,8 +233,8 @@ describe('OpenJdkDistribution', () => {
expect(windowsRelease[0].url.endsWith('.tar.gz')).toBe(true);
});
it('is registered in the distribution factory', () => {
const distribution = getJavaDistribution('oracle-openjdk', {
it('is registered in the distribution factory', async () => {
const distribution = await getJavaDistribution('oracle-openjdk', {
version: '26',
architecture: 'x64',
packageType: 'jdk',
@@ -47,8 +47,11 @@ describe('findPackageForDownload', () => {
let distribution: InstanceType<typeof OracleDistribution>;
let spyDebug: any;
let spyHttpClient: any;
let spyHttpClientGet: any;
let spyCoreError: any;
const ORACLE_CHECKSUM = 'f'.repeat(64);
beforeEach(() => {
distribution = new OracleDistribution({
version: '',
@@ -63,6 +66,14 @@ describe('findPackageForDownload', () => {
// Mock core.error to suppress error logs
spyCoreError = core.error as jest.Mock;
spyCoreError.mockImplementation(() => {});
// Every resolved release fetches its `${url}.sha256` sibling checksum;
// stub it so tests never reach the real network.
spyHttpClientGet = jest.spyOn(HttpClient.prototype, 'get');
spyHttpClientGet.mockResolvedValue({
message: {statusCode: 200},
readBody: async () => ORACLE_CHECKSUM
});
});
it.each([
@@ -133,6 +144,23 @@ describe('findPackageForDownload', () => {
expect(result.url).toBe(url);
});
it('fetches the authoritative sha256 checksum for the resolved archive', async () => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'head');
spyHttpClient.mockResolvedValue({message: {statusCode: 200}});
const result = await distribution['findPackageForDownload']('21');
jest.restoreAllMocks();
expect(result.checksum).toEqual({
algorithm: 'sha256',
value: ORACLE_CHECKSUM,
source: `${result.url}.sha256`
});
expect(spyHttpClientGet).toHaveBeenCalledWith(`${result.url}.sha256`);
expect(spyHttpClientGet).toHaveBeenCalledTimes(1);
});
it.each([
['amd64', 'x64'],
['arm64', 'aarch64']
@@ -196,6 +224,10 @@ describe('findPackageForDownload with latest', () => {
it('resolves the newest major version from the Adoptium API', async () => {
spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head');
spyHttpClientHead.mockResolvedValue({message: {statusCode: 200}});
jest.spyOn(HttpClient.prototype, 'get').mockResolvedValue({
message: {statusCode: 200},
readBody: async () => 'f'.repeat(64)
} as any);
const distribution = new OracleDistribution({
version: 'latest',
@@ -52,8 +52,10 @@ const utils = await import('../../src/util.js');
describe('getAvailableVersions', () => {
let spyHttpClient: any;
let spyHttpGet: any;
let spyUtilGetDownloadArchiveExtension: any;
let spyCoreError: any;
const archiveChecksum = 'f'.repeat(64);
beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
@@ -62,6 +64,11 @@ describe('getAvailableVersions', () => {
headers: {},
result: manifestData
});
spyHttpGet = jest.spyOn(HttpClient.prototype, 'get');
spyHttpGet.mockResolvedValue({
message: {statusCode: 200},
readBody: async () => `${archiveChecksum} archive`
});
spyUtilGetDownloadArchiveExtension =
utils.getDownloadArchiveExtension as jest.Mock<any>;
@@ -282,9 +289,31 @@ describe('getAvailableVersions', () => {
await distribution['findPackageForDownload'](normalizedVersion);
expect(availableVersion).not.toBeNull();
expect(availableVersion.url).toBe(expectedLink);
expect(availableVersion.checksum).toEqual({
algorithm: 'sha256',
value: archiveChecksum,
source: expectedLink.replace(/\.(?:tar\.gz|zip)$/, '.sha256.txt')
});
}
);
it('uses the checksum published beside the selected EA archive', async () => {
const distribution = new SapMachineDistribution({
version: '21-ea',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
mockPlatform(distribution, 'linux');
const release = await distribution['findPackageForDownload']('21');
expect(spyHttpGet).toHaveBeenCalledWith(
release.url.replace(/\.(?:tar\.gz|zip)$/, '.sha256.txt')
);
expect(release.checksum?.value).toBe(archiveChecksum);
});
it.each([
['8', 'linux', 'x64'],
['8', 'macos', 'aarch64'],
@@ -208,6 +208,14 @@ describe('findPackageForDownload', () => {
distribution['getAvailableVersions'] = async () => manifestData as any;
const resolvedVersion = await distribution['findPackageForDownload'](input);
expect(resolvedVersion.version).toBe(expected);
const vendorPackage = (manifestData as any[]).find(
item => item.version_data.semver === expected
).binaries[0].package;
expect(resolvedVersion.checksum).toEqual({
algorithm: 'sha256',
value: vendorPackage.checksum,
source: vendorPackage.checksum_link
});
});
it('version is found but binaries list is empty', async () => {
@@ -291,6 +291,7 @@ describe('getAvailableVersions', () => {
it.each([
['amd64', 'x64'],
['arm', 'arm'],
['arm64', 'aarch64']
])(
'defaults to os.arch(): %s mapped to distro arch: %s',
@@ -347,6 +348,14 @@ describe('findPackageForDownload', () => {
const resolvedVersion = await distribution['findPackageForDownload'](input);
expect(resolvedVersion.version).toBe(expected);
expect(resolvedVersion.signatureUrl).toBeDefined();
const vendorPackage = (manifestData as any[]).find(
item => item.version_data.semver === expected
).binaries[0].package;
expect(resolvedVersion.checksum).toEqual({
algorithm: 'sha256',
value: vendorPackage.checksum,
source: vendorPackage.checksum_link
});
});
it('version "latest" is normalized to the newest available version', async () => {
@@ -241,6 +241,26 @@ describe('getArchitectureOptions', () => {
});
describe('findPackageForDownload', () => {
let spyPackageDetails: any;
const ZULU_CHECKSUM = 'a'.repeat(64);
beforeEach(() => {
// The resolved winning package fetches sha256_hash from the Azul
// package-details endpoint; stub it so tests never reach the real
// network.
spyPackageDetails = jest.spyOn(HttpClient.prototype, 'getJson');
spyPackageDetails.mockResolvedValue({
statusCode: 200,
headers: {},
result: {sha256_hash: ZULU_CHECKSUM}
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it.each([
['8', '8.0.282+8'],
['11.x', '11.0.10+9'],
@@ -279,6 +299,38 @@ describe('findPackageForDownload', () => {
expect(result.url).toBe(
'https://cdn.azul.com/zulu/bin/zulu11.35.15-ca-jdk11.0.5-macosx_x64.tar.gz'
);
expect(result.checksum).toEqual({
algorithm: 'sha256',
value: ZULU_CHECKSUM,
source: 'https://api.azul.com/metadata/v1/zulu/packages/test-uuid-10933'
});
// Only the winning package's UUID triggers a details request.
expect(spyPackageDetails).toHaveBeenCalledWith(
'https://api.azul.com/metadata/v1/zulu/packages/test-uuid-10933'
);
expect(spyPackageDetails).toHaveBeenCalledTimes(1);
});
it('skips checksum verification when sha256_hash is missing or malformed', async () => {
spyPackageDetails.mockResolvedValue({
statusCode: 200,
headers: {},
result: {sha256_hash: 'not-a-valid-digest'}
});
const distribution = new ZuluDistribution({
version: '',
architecture: 'x86',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => manifestData;
const result = await distribution['findPackageForDownload']('11.0.5');
expect(result.checksum).toBeUndefined();
expect(core.debug).toHaveBeenCalledWith(
expect.stringContaining('No authoritative sha256 checksum')
);
});
it('should throw an error', async () => {
@@ -245,6 +245,26 @@ describe('getArchitectureOptions', () => {
});
describe('findPackageForDownload', () => {
let spyPackageDetails: any;
const ZULU_CHECKSUM = 'a'.repeat(64);
beforeEach(() => {
// The resolved winning package fetches sha256_hash from the Azul
// package-details endpoint; stub it so tests never reach the real
// network.
spyPackageDetails = jest.spyOn(HttpClient.prototype, 'getJson');
spyPackageDetails.mockResolvedValue({
statusCode: 200,
headers: {},
result: {sha256_hash: ZULU_CHECKSUM}
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it.each([
['8', '8.0.282+8'],
['11.x', '11.0.10+9'],
@@ -283,6 +303,38 @@ describe('findPackageForDownload', () => {
expect(result.url).toBe(
'https://cdn.azul.com/zulu/bin/zulu21.32.17-ca-jdk21.0.2-linux_aarch64.tar.gz'
);
expect(result.checksum).toEqual({
algorithm: 'sha256',
value: ZULU_CHECKSUM,
source: 'https://api.azul.com/metadata/v1/zulu/packages/test-uuid-12447'
});
// Only the winning package's UUID triggers a details request.
expect(spyPackageDetails).toHaveBeenCalledWith(
'https://api.azul.com/metadata/v1/zulu/packages/test-uuid-12447'
);
expect(spyPackageDetails).toHaveBeenCalledTimes(1);
});
it('skips checksum verification when sha256_hash is missing or malformed', async () => {
spyPackageDetails.mockResolvedValue({
statusCode: 200,
headers: {},
result: {}
});
const distribution = new ZuluDistribution({
version: '',
architecture: 'arm64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => manifestData;
const result = await distribution['findPackageForDownload']('21.0.2');
expect(result.checksum).toBeUndefined();
expect(core.debug).toHaveBeenCalledWith(
expect.stringContaining('No authoritative sha256 checksum')
);
});
it('should throw an error', async () => {
@@ -242,6 +242,26 @@ describe('getArchitectureOptions', () => {
});
describe('findPackageForDownload', () => {
let spyPackageDetails: any;
const ZULU_CHECKSUM = 'a'.repeat(64);
beforeEach(() => {
// The resolved winning package fetches sha256_hash from the Azul
// package-details endpoint; stub it so tests never reach the real
// network.
spyPackageDetails = jest.spyOn(HttpClient.prototype, 'getJson');
spyPackageDetails.mockResolvedValue({
statusCode: 200,
headers: {},
result: {sha256_hash: ZULU_CHECKSUM}
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it.each([
['8', '8.0.282+8'],
['11.x', '11.0.10+9'],
@@ -280,6 +300,38 @@ describe('findPackageForDownload', () => {
expect(result.url).toBe(
'https://cdn.azul.com/zulu/bin/zulu17.48.15-ca-jdk17.0.10-windows_aarch64.zip'
);
expect(result.checksum).toEqual({
algorithm: 'sha256',
value: ZULU_CHECKSUM,
source: 'https://api.azul.com/metadata/v1/zulu/packages/test-uuid-12446'
});
// Only the winning package's UUID triggers a details request.
expect(spyPackageDetails).toHaveBeenCalledWith(
'https://api.azul.com/metadata/v1/zulu/packages/test-uuid-12446'
);
expect(spyPackageDetails).toHaveBeenCalledTimes(1);
});
it('skips checksum verification when sha256_hash is missing or malformed', async () => {
spyPackageDetails.mockResolvedValue({
statusCode: 200,
headers: {},
result: {sha256_hash: '123'}
});
const distribution = new ZuluDistribution({
version: '',
architecture: 'arm64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => manifestData;
const result = await distribution['findPackageForDownload']('17.0.10');
expect(result.checksum).toBeUndefined();
expect(core.debug).toHaveBeenCalledWith(
expect.stringContaining('No authoritative sha256 checksum')
);
});
it('should throw an error', async () => {
+82
View File
@@ -0,0 +1,82 @@
import fs from 'fs';
import path from 'path';
import {
JAVA_PACKAGE_CAPABILITIES,
JavaDistribution
} from '../src/distributions/package-types.js';
const repositoryRoot = process.cwd();
const readRepositoryFile = (filePath: string) =>
fs.readFileSync(path.join(repositoryRoot, filePath), 'utf8');
const allPackageTypes = [
...new Set(Object.values(JAVA_PACKAGE_CAPABILITIES).flat())
];
describe('java-package published contract', () => {
it.each(['action.yml', 'README.md'])(
'documents every supported package type in %s',
filePath => {
const content = readRepositoryFile(filePath);
const contractLine =
filePath === 'action.yml'
? content.match(/ {2}java-package:\n(?: {4}.+\n)+/)?.[0]
: content
.split('\n')
.find(line => line.includes('- `java-package`:'));
expect(contractLine).toBeDefined();
for (const packageType of allPackageTypes) {
expect(contractLine).toContain(`\`${packageType}\``);
}
}
);
it.each(Object.entries(JAVA_PACKAGE_CAPABILITIES))(
'keeps the advanced compatibility table aligned for %s',
(distributionName, packageTypes) => {
const advancedUsage = readRepositoryFile('docs/advanced-usage.md');
const compatibilityTable = advancedUsage.slice(
advancedUsage.indexOf('### Package compatibility')
);
const compatibilityRow = compatibilityTable
.split('\n')
.find(
line =>
line.startsWith('|') && line.includes(`\`${distributionName}\``)
);
expect(compatibilityRow).toBeDefined();
for (const packageType of packageTypes) {
expect(compatibilityRow).toContain(`\`${packageType}\``);
}
}
);
it('only exercises supported distribution/package combinations in E2E', () => {
const workflow = readRepositoryFile('.github/workflows/e2e-versions.yml');
const defaultMatrix = workflow.match(
/distribution:\s*\n\s*\[([^\]]+)\]\s*\n\s*java-package:\s*\['([^']+)'\]/
);
expect(defaultMatrix).not.toBeNull();
const defaultDistributions = [
...defaultMatrix![1].matchAll(/'([^']+)'/g)
].map(match => match[1]);
const defaultPackage = defaultMatrix![2];
for (const distributionName of defaultDistributions) {
expect(supportedPackagesFor(distributionName)).toContain(defaultPackage);
}
const includedPackages = workflow.matchAll(
/- distribution: '([^']+)'\s*\n\s*java-package: ([^\s]+)/g
);
for (const match of includedPackages) {
const [, distributionName, packageType] = match;
expect(supportedPackagesFor(distributionName)).toContain(packageType);
}
});
});
function supportedPackagesFor(distributionName: string): readonly string[] {
return JAVA_PACKAGE_CAPABILITIES[distributionName as JavaDistribution] ?? [];
}
+136
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}\``);
}
}
);
});
+251
View File
@@ -0,0 +1,251 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import type {IncomingMessage} from 'http';
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn()
}));
const core = await import('@actions/core');
const httpm = await import('@actions/http-client');
const {RetryingHttpClient, isRetryableNetworkError, parseRetryAfter} =
await import('../src/retrying-http-client.js');
function response(
statusCode: number,
retryAfter?: string
): httpm.HttpClientResponse {
return {
message: {
statusCode,
headers: retryAfter ? {'retry-after': retryAfter} : {}
} as IncomingMessage,
readBody: jest.fn(async () => '')
} as unknown as httpm.HttpClientResponse;
}
describe('RetryingHttpClient', () => {
let request: ReturnType<typeof jest.spyOn>;
let sleep: jest.Mock<(delayMs: number) => Promise<void>>;
beforeEach(() => {
request = jest.spyOn(httpm.HttpClient.prototype, 'request');
sleep = jest.fn(async () => undefined);
});
afterEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
});
it('uses exponential backoff with jitter for retryable responses', async () => {
request
.mockResolvedValueOnce(response(503))
.mockResolvedValueOnce(response(502))
.mockResolvedValueOnce(response(200));
const client = new RetryingHttpClient('test', {
sleep,
random: () => 0,
baseDelayMs: 1000,
maxDelayMs: 10000
});
await expect(client.get('https://example.com')).resolves.toBeDefined();
expect(request).toHaveBeenCalledTimes(3);
expect(sleep).toHaveBeenNthCalledWith(1, 500);
expect(sleep).toHaveBeenNthCalledWith(2, 1000);
expect(core.info).toHaveBeenNthCalledWith(
1,
'Request attempt 1 of 4 failed (HTTP 503); retrying in 500 ms'
);
expect(core.info).toHaveBeenNthCalledWith(
2,
'Request attempt 2 of 4 failed (HTTP 502); retrying in 1000 ms'
);
});
it('honors Retry-After delta-seconds over the client delay', async () => {
request
.mockResolvedValueOnce(response(429, '3'))
.mockResolvedValueOnce(response(200));
const client = new RetryingHttpClient('test', {
sleep,
random: () => 0
});
await client.get('https://example.com');
expect(sleep).toHaveBeenCalledWith(3000);
});
it('honors Retry-After HTTP dates over the client delay', async () => {
const now = Date.parse('2026-07-29T00:00:00Z');
request
.mockResolvedValueOnce(response(503, new Date(now + 5000).toUTCString()))
.mockResolvedValueOnce(response(200));
const client = new RetryingHttpClient('test', {
sleep,
random: () => 0,
now: () => now
});
await client.get('https://example.com');
expect(sleep).toHaveBeenCalledWith(5000);
});
it('caps Retry-After at the configured maximum delay', async () => {
request
.mockResolvedValueOnce(response(429, '60'))
.mockResolvedValueOnce(response(200));
const client = new RetryingHttpClient('test', {
sleep,
random: () => 0,
maxDelayMs: 10000
});
await client.get('https://example.com');
expect(sleep).toHaveBeenCalledWith(10000);
});
it.each([429, 502, 503, 504, 522])(
'retries HTTP %s responses',
async statusCode => {
request
.mockResolvedValueOnce(response(statusCode))
.mockResolvedValueOnce(response(200));
const client = new RetryingHttpClient('test', {
sleep,
random: () => 0
});
await client.get('https://example.com');
expect(request).toHaveBeenCalledTimes(2);
}
);
it.each(['ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED'])(
'retries network errors with code %s',
async code => {
request
.mockRejectedValueOnce(Object.assign(new Error(code), {code}))
.mockResolvedValueOnce(response(200));
const client = new RetryingHttpClient('test', {
sleep,
random: () => 0
});
await client.get('https://example.com');
expect(request).toHaveBeenCalledTimes(2);
}
);
it('retries retryable aggregate network errors', async () => {
const aggregateError = Object.assign(new Error('connection failed'), {
errors: [Object.assign(new Error('timed out'), {code: 'ETIMEDOUT'})]
});
request
.mockRejectedValueOnce(aggregateError)
.mockResolvedValueOnce(response(200));
const client = new RetryingHttpClient('test', {
sleep,
random: () => 0
});
await client.get('https://example.com');
expect(request).toHaveBeenCalledTimes(2);
expect(sleep).toHaveBeenCalledWith(500);
});
it('does not retry non-retryable responses or network errors', async () => {
request.mockResolvedValueOnce(response(500));
const client = new RetryingHttpClient('test', {sleep});
await expect(client.get('https://example.com')).resolves.toBeDefined();
expect(request).toHaveBeenCalledTimes(1);
expect(sleep).not.toHaveBeenCalled();
request.mockRejectedValueOnce(
Object.assign(new Error('certificate failed'), {code: 'CERT_HAS_EXPIRED'})
);
await expect(client.get('https://example.com')).rejects.toThrow(
'certificate failed'
);
expect(request).toHaveBeenCalledTimes(2);
expect(sleep).not.toHaveBeenCalled();
});
it('stops after the configured total attempt count', async () => {
request
.mockResolvedValueOnce(response(503))
.mockResolvedValueOnce(response(503));
const client = new RetryingHttpClient('test', {
maxAttempts: 2,
sleep,
random: () => 0
});
const finalResponse = await client.get('https://example.com');
expect(finalResponse.message.statusCode).toBe(503);
expect(request).toHaveBeenCalledTimes(2);
expect(sleep).toHaveBeenCalledTimes(1);
});
it('propagates the final network error after exhausting attempts', async () => {
const finalError = Object.assign(new Error('still unavailable'), {
code: 'ECONNREFUSED'
});
request
.mockRejectedValueOnce(
Object.assign(new Error('unavailable'), {code: 'ECONNREFUSED'})
)
.mockRejectedValueOnce(finalError);
const client = new RetryingHttpClient('test', {
maxAttempts: 2,
sleep,
random: () => 0
});
await expect(client.get('https://example.com')).rejects.toBe(finalError);
expect(request).toHaveBeenCalledTimes(2);
expect(sleep).toHaveBeenCalledTimes(1);
});
it('does not retry write requests', async () => {
request.mockResolvedValueOnce(response(503));
const client = new RetryingHttpClient('test', {sleep});
await client.post('https://example.com', '{}');
expect(request).toHaveBeenCalledTimes(1);
expect(sleep).not.toHaveBeenCalled();
});
});
describe('retry classification', () => {
it('parses valid Retry-After values and ignores invalid or past values', () => {
const now = Date.parse('2026-07-29T00:00:00Z');
expect(parseRetryAfter('7', now)).toBe(7000);
expect(parseRetryAfter(new Date(now + 3000).toUTCString(), now)).toBe(3000);
expect(parseRetryAfter(new Date(now - 3000).toUTCString(), now)).toBe(
undefined
);
expect(parseRetryAfter('not-a-date', now)).toBe(undefined);
});
it('recognizes direct and nested retryable network error codes', () => {
expect(isRetryableNetworkError({code: 'ECONNRESET'})).toBe(true);
expect(
isRetryableNetworkError({errors: [{code: 'ENOTFOUND'}, {code: 'OTHER'}]})
).toBe(true);
expect(isRetryableNetworkError({code: 'CERT_HAS_EXPIRED'})).toBe(false);
expect(isRetryableNetworkError(new Error('unknown'))).toBe(false);
});
});
+118
View File
@@ -0,0 +1,118 @@
import {jest, describe, it, expect, beforeEach} from '@jest/globals';
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((value: string) => value),
toWin32Path: jest.fn((value: string) => value),
toPosixPath: jest.fn((value: string) => value)
}));
jest.unstable_mockModule('fs', () => ({
default: {
readFileSync: jest.fn()
}
}));
jest.unstable_mockModule('../src/util.js', () => ({
getBooleanInput: jest.fn(),
getVersionFromFileContent: jest.fn()
}));
jest.unstable_mockModule('../src/toolchains.js', () => ({
validateToolchainIds: jest.fn(),
configureToolchains: jest.fn()
}));
jest.unstable_mockModule(
'../src/distributions/distribution-factory.js',
() => ({
getJavaDistribution: jest.fn()
})
);
jest.unstable_mockModule('../src/auth.js', () => ({
configureAuthentication: jest.fn()
}));
jest.unstable_mockModule('../src/maven-args.js', () => ({
configureMavenArgs: jest.fn()
}));
jest.unstable_mockModule('../src/problem-matcher.js', () => ({
configureProblemMatcher: jest.fn()
}));
// These modules should never be imported when `cache` input is empty.
jest.unstable_mockModule('../src/cache-feature.js', () => {
throw new Error('cache-feature module should not be loaded');
});
jest.unstable_mockModule('../src/cache.js', () => {
throw new Error('cache module should not be loaded');
});
const core = await import('@actions/core');
const util = await import('../src/util.js');
const toolchains = await import('../src/toolchains.js');
const factory = await import('../src/distributions/distribution-factory.js');
const {run} = await import('../src/setup-java.js');
describe('setup-java conditional module loading', () => {
const inputs = new Map<string, string>();
const multilineInputs = new Map<string, string[]>();
const booleanInputs = new Map<string, boolean>();
beforeEach(() => {
jest.resetAllMocks();
inputs.clear();
multilineInputs.clear();
booleanInputs.clear();
(core.getInput as jest.Mock).mockImplementation((name: unknown) => {
return inputs.get(name as string) ?? '';
});
(core.getMultilineInput as jest.Mock).mockImplementation(
(name: unknown) => {
return multilineInputs.get(name as string) ?? [];
}
);
(util.getBooleanInput as jest.Mock).mockImplementation(
(name: unknown, defaultValue: unknown) => {
return booleanInputs.get(name as string) ?? defaultValue;
}
);
(toolchains.configureToolchains as jest.Mock).mockResolvedValue(undefined);
});
it('does not import cache modules when cache input is not provided', async () => {
inputs.set('distribution', 'temurin');
multilineInputs.set('java-version', ['21']);
(factory.getJavaDistribution as jest.Mock).mockResolvedValue({
setupJava: jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
}))
});
await run();
expect(core.setFailed).not.toHaveBeenCalled();
});
});
+497
View File
@@ -0,0 +1,497 @@
import {jest, describe, it, expect, beforeEach} from '@jest/globals';
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((value: string) => value),
toWin32Path: jest.fn((value: string) => value),
toPosixPath: jest.fn((value: string) => value)
}));
jest.unstable_mockModule('fs', () => ({
default: {
readFileSync: jest.fn()
}
}));
jest.unstable_mockModule('../src/util.js', () => ({
getBooleanInput: jest.fn(),
getVersionFromFileContent: jest.fn()
}));
jest.unstable_mockModule('../src/toolchains.js', () => ({
validateToolchainIds: jest.fn(),
configureToolchains: jest.fn()
}));
jest.unstable_mockModule('../src/cache.js', () => ({
restore: jest.fn()
}));
jest.unstable_mockModule('../src/cache-feature.js', () => ({
isCacheFeatureAvailable: jest.fn()
}));
jest.unstable_mockModule(
'../src/distributions/distribution-factory.js',
() => ({
getJavaDistribution: jest.fn()
})
);
jest.unstable_mockModule('../src/auth.js', () => ({
configureAuthentication: jest.fn()
}));
jest.unstable_mockModule('../src/maven-args.js', () => ({
configureMavenArgs: jest.fn()
}));
jest.unstable_mockModule('../src/problem-matcher.js', () => ({
configureProblemMatcher: jest.fn()
}));
const core = await import('@actions/core');
const fs = (await import('fs')).default;
const util = await import('../src/util.js');
const toolchains = await import('../src/toolchains.js');
const cache = await import('../src/cache.js');
const cacheFeature = await import('../src/cache-feature.js');
const factory = await import('../src/distributions/distribution-factory.js');
const auth = await import('../src/auth.js');
const mavenArgs = await import('../src/maven-args.js');
const problemMatcher = await import('../src/problem-matcher.js');
const {run} = await import('../src/setup-java.js');
const inputCallsOnImport = (core.getInput as jest.Mock).mock.calls.length;
const multilineInputCallsOnImport = (core.getMultilineInput as jest.Mock).mock
.calls.length;
describe('setup action orchestration', () => {
const inputs = new Map<string, string>();
const multilineInputs = new Map<string, string[]>();
const booleanInputs = new Map<string, boolean>();
beforeEach(() => {
jest.resetAllMocks();
inputs.clear();
multilineInputs.clear();
booleanInputs.clear();
(core.getInput as jest.Mock).mockImplementation((name: unknown) => {
return inputs.get(name as string) ?? '';
});
(core.getMultilineInput as jest.Mock).mockImplementation(
(name: unknown) => {
return multilineInputs.get(name as string) ?? [];
}
);
(util.getBooleanInput as jest.Mock).mockImplementation(
(name: unknown, defaultValue: unknown) => {
return booleanInputs.get(name as string) ?? defaultValue;
}
);
(cacheFeature.isCacheFeatureAvailable as jest.Mock).mockReturnValue(true);
(toolchains.configureToolchains as jest.Mock).mockResolvedValue(undefined);
(auth.configureAuthentication as jest.Mock).mockResolvedValue(undefined);
(cache.restore as jest.Mock).mockResolvedValue(undefined);
});
it('does not execute the action when imported', () => {
expect(inputCallsOnImport).toBe(0);
expect(multilineInputCallsOnImport).toBe(0);
});
it('requires java-version or java-version-file', async () => {
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'java-version or java-version-file input expected'
);
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
expect(problemMatcher.configureProblemMatcher).not.toHaveBeenCalled();
});
it('requires distribution when java-version is provided', async () => {
multilineInputs.set('java-version', ['21']);
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'distribution input is required'
);
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
});
it('requires distribution when it cannot be inferred from the version file', async () => {
inputs.set('java-version-file', '.java-version');
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('21'));
(util.getVersionFromFileContent as jest.Mock).mockReturnValue({
version: '21'
});
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'distribution input is required when not specified in the version file'
);
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
});
it('fails when the version file has no supported version', async () => {
inputs.set('java-version-file', '.java-version');
inputs.set('distribution', 'temurin');
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('invalid'));
(util.getVersionFromFileContent as jest.Mock).mockReturnValue(undefined);
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'No supported version was found in file .java-version'
);
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
});
it('uses the distribution inferred from a version file', async () => {
inputs.set('java-version-file', '.sdkmanrc');
inputs.set('architecture', 'x64');
inputs.set('java-package', 'jdk');
inputs.set('distribution', 'zulu');
inputs.set('jdk-file', '/tmp/java.tar.gz');
multilineInputs.set('mvn-toolchain-id', ['file-jdk']);
booleanInputs.set('check-latest', true);
booleanInputs.set('force-download', true);
booleanInputs.set('set-default', false);
booleanInputs.set('verify-signature', true);
inputs.set('verify-signature-public-key', 'public-key');
(fs.readFileSync as jest.Mock).mockReturnValue(
Buffer.from('java=21.0.5-tem')
);
(util.getVersionFromFileContent as jest.Mock).mockReturnValue({
version: '21.0.5',
distribution: 'temurin'
});
const setupJava = jest.fn(async () => ({
version: '21.0.5+11',
path: '/opt/java/21'
}));
(factory.getJavaDistribution as jest.Mock).mockReturnValue({setupJava});
await run();
expect(util.getVersionFromFileContent).toHaveBeenCalledWith(
'java=21.0.5-tem',
'zulu',
'.sdkmanrc'
);
expect(factory.getJavaDistribution).toHaveBeenCalledWith(
'temurin',
{
version: '21.0.5',
architecture: 'x64',
packageType: 'jdk',
checkLatest: true,
forceDownload: true,
setDefault: false,
verifySignature: true,
verifySignaturePublicKey: 'public-key'
},
'/tmp/java.tar.gz'
);
expect(toolchains.validateToolchainIds).toHaveBeenCalledWith(
[],
'.sdkmanrc',
['file-jdk']
);
expect(toolchains.configureToolchains).toHaveBeenCalledWith(
'21.0.5',
'temurin',
'/opt/java/21',
'file-jdk'
);
expect(core.setFailed).not.toHaveBeenCalled();
});
it('installs multiple JDKs in order with matching toolchain IDs', async () => {
inputs.set('distribution', 'temurin');
inputs.set('architecture', 'x64');
inputs.set('java-package', 'jdk');
multilineInputs.set('java-version', ['17', '21']);
multilineInputs.set('mvn-toolchain-id', ['java-17', 'java-21']);
const setupJava17 = jest.fn(async () => ({
version: '17.0.12+7',
path: '/opt/java/17'
}));
const setupJava21 = jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
}));
(factory.getJavaDistribution as jest.Mock)
.mockReturnValueOnce({setupJava: setupJava17})
.mockReturnValueOnce({setupJava: setupJava21});
await run();
expect(factory.getJavaDistribution).toHaveBeenNthCalledWith(
1,
'temurin',
expect.objectContaining({version: '17'}),
''
);
expect(factory.getJavaDistribution).toHaveBeenNthCalledWith(
2,
'temurin',
expect.objectContaining({version: '21'}),
''
);
expect(toolchains.configureToolchains).toHaveBeenNthCalledWith(
1,
'17',
'temurin',
'/opt/java/17',
'java-17'
);
expect(toolchains.configureToolchains).toHaveBeenNthCalledWith(
2,
'21',
'temurin',
'/opt/java/21',
'java-21'
);
expect(setupJava17.mock.invocationCallOrder[0]).toBeLessThan(
setupJava21.mock.invocationCallOrder[0]
);
});
it('uses the resolved version for the latest Maven toolchain', async () => {
inputs.set('distribution', 'temurin');
multilineInputs.set('java-version', ['latest']);
const setupJava = jest.fn(async () => ({
version: '24.0.2+12',
path: '/opt/java/24'
}));
(factory.getJavaDistribution as jest.Mock).mockReturnValue({setupJava});
await run();
expect(toolchains.configureToolchains).toHaveBeenCalledWith(
'24.0.2+12',
'temurin',
'/opt/java/24',
undefined
);
});
it('starts cache restoration before post-install steps and awaits it before finishing', async () => {
inputs.set('distribution', 'temurin');
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 cacheRestore = deferred<void>();
let resolveSetupJava: (() => void) | undefined;
const setupJava = jest.fn(
() =>
new Promise<{version: string; path: string}>(resolve => {
resolveSetupJava = () =>
resolve({
version: '21.0.4+7',
path: '/opt/java/21'
});
})
);
(cache.restore as jest.Mock).mockReturnValue(cacheRestore.promise);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({setupJava});
const runPromise = run();
try {
await tick();
expect(cacheFeature.isCacheFeatureAvailable).toHaveBeenCalled();
expect(cache.restore).toHaveBeenCalledWith('maven', '**/pom.xml', [
'/custom/maven/repository',
'!/custom/maven/repository/excluded'
]);
expect(toolchains.configureToolchains).not.toHaveBeenCalled();
resolveSetupJava?.();
await tick();
expect(problemMatcher.configureProblemMatcher).toHaveBeenCalledWith(
expect.stringMatching(/\.github[/\\]java\.json$/)
);
expect(
(toolchains.configureToolchains as jest.Mock).mock
.invocationCallOrder[0]
).toBeLessThan(
(problemMatcher.configureProblemMatcher as jest.Mock).mock
.invocationCallOrder[0]
);
expect(
(problemMatcher.configureProblemMatcher as jest.Mock).mock
.invocationCallOrder[0]
).toBeLessThan(
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
);
expect(
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
).toBeLessThan(
(mavenArgs.configureMavenArgs as jest.Mock).mock.invocationCallOrder[0]
);
let completed = false;
runPromise.then(() => {
completed = true;
});
await tick();
expect(completed).toBe(false);
} finally {
resolveSetupJava?.();
cacheRestore.resolve();
await runPromise;
}
expect(core.setFailed).not.toHaveBeenCalled();
});
it('skips cache restoration when the cache feature is unavailable', async () => {
inputs.set('distribution', 'temurin');
inputs.set('cache', 'maven');
multilineInputs.set('java-version', ['21']);
(cacheFeature.isCacheFeatureAvailable as jest.Mock).mockReturnValue(false);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
}))
});
await run();
expect(cache.restore).not.toHaveBeenCalled();
});
it('does not initialize cache modules when cache input is absent', async () => {
inputs.set('distribution', 'temurin');
multilineInputs.set('java-version', ['21']);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
}))
});
await run();
expect(cacheFeature.isCacheFeatureAvailable).not.toHaveBeenCalled();
expect(cache.restore).not.toHaveBeenCalled();
});
it('reports unsupported distributions through core.setFailed', async () => {
inputs.set('distribution', 'unsupported');
multilineInputs.set('java-version', ['21']);
(factory.getJavaDistribution as jest.Mock).mockReturnValue(null);
await run();
expect(core.setFailed).toHaveBeenCalledWith(
'No supported distribution was found for input unsupported'
);
expect(toolchains.configureToolchains).not.toHaveBeenCalled();
expect(problemMatcher.configureProblemMatcher).not.toHaveBeenCalled();
});
it('reports collaborator failures and stops post-install configuration', async () => {
inputs.set('distribution', 'temurin');
multilineInputs.set('java-version', ['21']);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => {
throw new Error('download failed');
})
});
await run();
expect(core.setFailed).toHaveBeenCalledWith('download failed');
expect(toolchains.configureToolchains).not.toHaveBeenCalled();
expect(problemMatcher.configureProblemMatcher).not.toHaveBeenCalled();
expect(auth.configureAuthentication).not.toHaveBeenCalled();
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();
expect(cache.restore).not.toHaveBeenCalled();
});
it('reports post-install failures and skips later collaborators', async () => {
inputs.set('distribution', 'temurin');
inputs.set('cache', 'maven');
multilineInputs.set('java-version', ['21']);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
}))
});
(auth.configureAuthentication as jest.Mock).mockRejectedValue(
new Error('authentication failed')
);
await run();
expect(problemMatcher.configureProblemMatcher).toHaveBeenCalled();
expect(core.setFailed).toHaveBeenCalledWith('authentication failed');
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();
expect(cache.restore).toHaveBeenCalled();
});
it('keeps Java setup errors deterministic when cache restore also fails', async () => {
inputs.set('distribution', 'temurin');
inputs.set('cache', 'maven');
multilineInputs.set('java-version', ['21']);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => {
throw new Error('download failed');
})
});
(cache.restore as jest.Mock).mockRejectedValue(
new Error('cache restore failed')
);
await run();
expect(core.setFailed).toHaveBeenCalledWith('download failed');
});
});
function deferred<T>() {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return {promise, resolve, reject};
}
async function tick() {
await new Promise(resolve => setTimeout(resolve, 0));
}
+64
View File
@@ -1007,3 +1007,67 @@ describe('toolchains tests', () => {
expect((contents.match(/<toolchain>/g) || []).length).toBe(runs.length);
}, 100000);
});
describe('validateToolchainIds', () => {
it.each([
{
name: 'uses generated IDs when no custom IDs are supplied',
versions: ['17', '21'],
versionFile: '',
toolchainIds: []
},
{
name: 'accepts one custom ID for a single Java version',
versions: ['21'],
versionFile: '',
toolchainIds: ['custom-21']
},
{
name: 'accepts one custom ID per Java version',
versions: ['17', '21'],
versionFile: '',
toolchainIds: ['custom-17', 'custom-21']
},
{
name: 'accepts one custom ID with java-version-file',
versions: [],
versionFile: '.java-version',
toolchainIds: ['custom-file-version']
}
])('$name', ({versions, versionFile, toolchainIds}) => {
expect(() =>
toolchains.validateToolchainIds(versions, versionFile, toolchainIds)
).not.toThrow();
});
it.each([
{
name: 'rejects fewer IDs than Java versions',
versions: ['17', '21'],
versionFile: '',
toolchainIds: ['custom-17'],
expectedMessage:
'The number of Maven toolchain IDs (1) must match the number of Java versions (2)'
},
{
name: 'rejects extra IDs for a single Java version',
versions: ['21'],
versionFile: '',
toolchainIds: ['custom-21', 'custom-extra'],
expectedMessage:
'The number of Maven toolchain IDs (2) must match the number of Java versions (1)'
},
{
name: 'rejects extra IDs with java-version-file',
versions: [],
versionFile: '.java-version',
toolchainIds: ['custom-file-version', 'custom-extra'],
expectedMessage:
'The number of Maven toolchain IDs (2) must match the number of Java versions (1)'
}
])('$name', ({versions, versionFile, toolchainIds, expectedMessage}) => {
expect(() =>
toolchains.validateToolchainIds(versions, versionFile, toolchainIds)
).toThrow(expectedMessage);
});
});
+65 -54
View File
@@ -13,13 +13,6 @@ import * as path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Mock @actions/cache
jest.unstable_mockModule('@actions/cache', () => ({
isFeatureAvailable: jest.fn(),
saveCache: jest.fn(),
restoreCache: jest.fn()
}));
// Mock @actions/core
jest.unstable_mockModule('@actions/core', () => ({
getInput: jest.fn(),
@@ -46,7 +39,6 @@ jest.unstable_mockModule('@actions/core', () => ({
toPosixPath: jest.fn((p: string) => p)
}));
const cache = await import('@actions/cache');
const core = await import('@actions/core');
const {
@@ -54,12 +46,75 @@ const {
getNextPageUrlFromLinkHeader,
getVersionFromFileContent,
isVersionSatisfies,
isCacheFeatureAvailable,
isGhes,
validatePaginationUrl,
getLatestMajorVersion
getLatestMajorVersion,
getBooleanInput
} = await import('../src/util.js');
describe('getBooleanInput', () => {
let inputs: Record<string, string>;
beforeEach(() => {
inputs = {};
(core.getInput as jest.Mock).mockImplementation(
(name: string) => inputs[name] ?? ''
);
});
afterEach(() => {
jest.resetAllMocks();
});
it.each([
['true', true],
['TRUE', true],
['TrUe', true],
[' true ', true],
['false', false],
['FALSE', false],
['FaLsE', false],
[' false ', false]
])('parses %j as %s', (value: string, expected: boolean) => {
inputs['boolean-input'] = value;
expect(getBooleanInput('boolean-input')).toBe(expected);
});
it.each([
[undefined, false],
[false, false],
[true, true]
])(
'uses the configured default %s when the input is omitted',
(defaultValue: boolean | undefined, expected: boolean) => {
expect(getBooleanInput('boolean-input', defaultValue)).toBe(expected);
}
);
it('uses the configured default for a whitespace-only input', () => {
inputs['boolean-input'] = ' ';
expect(getBooleanInput('boolean-input', true)).toBe(true);
});
it.each([
'check-latest',
'force-download',
'set-default',
'verify-signature',
'overwrite-settings',
'show-download-progress',
'problem-matcher'
])('rejects an invalid value for %s', inputName => {
inputs[inputName] = 'ture';
expect(() => getBooleanInput(inputName)).toThrow(
`Invalid value 'ture' for boolean input '${inputName}'. Expected 'true' or 'false'.`
);
});
});
describe('isVersionSatisfies', () => {
it.each([
['x', '11.0.0', true],
@@ -88,50 +143,6 @@ describe('isVersionSatisfies', () => {
);
});
describe('isCacheFeatureAvailable', () => {
it('isCacheFeatureAvailable disabled on GHES', () => {
(cache.isFeatureAvailable as jest.Mock<any>).mockImplementation(
() => false
);
const infoMock = core.warning as jest.Mock;
const message =
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.';
try {
process.env['GITHUB_SERVER_URL'] = 'http://example.com';
expect(isCacheFeatureAvailable()).toBeFalsy();
expect(infoMock).toHaveBeenCalledWith(message);
} finally {
delete process.env['GITHUB_SERVER_URL'];
}
});
it('isCacheFeatureAvailable disabled on dotcom', () => {
(cache.isFeatureAvailable as jest.Mock<any>).mockImplementation(
() => false
);
const infoMock = core.warning as jest.Mock;
const message =
'The runner was not able to contact the cache service. Caching will be skipped';
try {
process.env['GITHUB_SERVER_URL'] = 'http://github.com';
expect(isCacheFeatureAvailable()).toBe(false);
expect(infoMock).toHaveBeenCalledWith(message);
} finally {
delete process.env['GITHUB_SERVER_URL'];
}
});
it('isCacheFeatureAvailable is enabled', () => {
(cache.isFeatureAvailable as jest.Mock<any>).mockImplementation(() => true);
expect(isCacheFeatureAvailable()).toBe(true);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
});
describe('convertVersionToSemver', () => {
it.each([
['12', '12'],
+10 -3
View File
@@ -13,11 +13,11 @@ inputs:
description: 'Java distribution. See the list of supported distributions in README file. This input is required except when java-version-file points to .sdkmanrc with a recognized distribution suffix (e.g., java=21.0.5-tem).'
required: false
java-package:
description: 'The package type (jdk, jre, jdk+fx, jre+fx, jdk+crac, jre+crac, jdk+jmods)'
description: 'The package type (`jdk`, `jre`, `jdk+fx`, `jre+fx`, `jdk+crac`, `jre+crac`, `jdk+jmods`, `jdk+jcef`, `jre+jcef`, `jdk+ft`, or `jre+ft`). Supported values vary by distribution.'
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
@@ -96,7 +103,7 @@ inputs:
required: false
default: ${{ github.server_url == 'https://github.com' && github.token || '' }}
mvn-toolchain-id:
description: 'Name of Maven Toolchain ID if the default name of "${distribution}_${java-version}" is not wanted. See examples of supported syntax in Advanced Usage file'
description: 'Name of Maven Toolchain ID if the default name of "${distribution}_${java-version}" is not wanted. When supplied, the number of IDs must match the number of Java versions. See examples of supported syntax in Advanced Usage file'
required: false
mvn-toolchain-vendor:
description: 'Name of Maven Toolchain Vendor if the default name of "${distribution}" is not wanted. See examples of supported syntax in Advanced Usage file'
+356
View File
@@ -0,0 +1,356 @@
export const id = 377;
export const ids = [377];
export const modules = {
/***/ 7377:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ save: () => (/* binding */ save)
/* harmony export */ });
/* unused harmony export restore */
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(857);
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _actions_cache__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5767);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3838);
/* harmony import */ var _actions_glob__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2377);
/**
* @fileoverview this file provides methods handling dependency cache
*/
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 = [
{
id: 'maven',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.m2', 'repository')],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [
'**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
]
},
{
id: 'gradle',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.gradle', 'caches')],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [
'**/*.gradle*',
'**/gradle-wrapper.properties',
'buildSrc/**/Versions.kt',
'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml',
'**/versions.properties'
],
// The Gradle wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the Gradle
// caches. This keeps it available across the frequent *.gradle* changes
// that rotate the main cache key. See issue #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
]
},
{
id: 'sbt',
path: [
(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.ivy2', 'cache'),
(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.sbt'),
getCoursierCachePath(),
// Some files should not be cached to avoid resolution problems.
// In particular the resolution of snapshots (ideological gap between maven/ivy).
'!' + (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.sbt', '*.lock'),
'!' + (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '**', 'ivydata-*.properties')
],
pattern: [
'**/*.sbt',
'**/project/build.properties',
'**/project/**.scala',
'**/project/**.sbt'
]
}
];
function getCoursierCachePath() {
if (os__WEBPACK_IMPORTED_MODULE_1___default().type() === 'Linux')
return (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.cache', 'coursier');
if (os__WEBPACK_IMPORTED_MODULE_1___default().type() === 'Darwin')
return (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), 'Library', 'Caches', 'Coursier');
return (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), 'AppData', 'Local', 'Coursier', 'Cache');
}
function findPackageManager(id) {
const packageManager = supportedPackageManager.find(packageManager => packageManager.id === id);
if (packageManager === undefined) {
throw new Error(`unknown package manager specified: ${id}`);
}
return packageManager;
}
function resolveCachePaths(packageManager, cachePaths) {
return cachePaths.length > 0 ? cachePaths : packageManager.path;
}
function getCachePathsFromState(packageManager) {
const cachePathsState = _actions_core__WEBPACK_IMPORTED_MODULE_3__/* .getState */ .Gu(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.
*/
function additionalCachePrimaryKeyState(name) {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name) {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id, fileHash) {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/**
* A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
* @see {@link https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key|spec of cache key}
*/
async function computeCacheKey(packageManager, cacheDependencyPath) {
const pattern = cacheDependencyPath
? cacheDependencyPath.trim().split('\n')
: packageManager.pattern;
const fileHash = await glob.hashFiles(pattern.join('\n'));
if (!fileHash) {
throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`);
}
return buildCacheKey(packageManager.id, fileHash);
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(additionalCache) {
const fileHash = await glob.hashFiles(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
}
/**
* Restore the dependency cache
* @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, cachePaths = []) {
const packageManager = findPackageManager(id);
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(cachePaths, primaryKey);
if (matchedKey) {
core.saveState(CACHE_MATCHED_KEY, matchedKey);
core.setOutput('cache-hit', matchedKey === primaryKey);
core.info(`Cache restored from key: ${matchedKey}`);
}
else {
core.setOutput('cache-hit', false);
core.info(`${packageManager.id} cache is not found`);
}
}
/**
* Compute keys for additional caches (e.g. build-tool wrapper distributions).
* Additional caches without a matching configuration file are omitted.
*/
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
* @param id ID of the package manager, should be "maven" or "gradle"
*/
async function save(id) {
const packageManager = findPackageManager(id);
const cachePaths = getCachePathsFromState(packageManager);
const matchedKey = _actions_core__WEBPACK_IMPORTED_MODULE_3__/* .getState */ .Gu(CACHE_MATCHED_KEY);
// Inputs are re-evaluated before the post action, so we want the original key used for restore
const primaryKey = _actions_core__WEBPACK_IMPORTED_MODULE_3__/* .getState */ .Gu(STATE_CACHE_PRIMARY_KEY);
for (const additionalCache of packageManager.additionalCaches ?? []) {
try {
await saveAdditionalCache(packageManager, additionalCache);
}
catch (error) {
const err = error;
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .warning */ .$e(`Failed to save ${additionalCache.name} cache: ${err.message}. Continuing with primary cache save.`);
}
}
if (!primaryKey) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .warning */ .$e('Error retrieving key from state.');
return;
}
else if (matchedKey === primaryKey) {
// no change in target directories
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
return;
}
try {
const cacheId = await _actions_cache__WEBPACK_IMPORTED_MODULE_2__/* .saveCache */ .Io(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
// has already logged the reason at the appropriate severity, so just
// trace it instead of misreporting that the cache was saved.
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`Cache was not saved for the key: ${primaryKey}`);
return;
}
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === _actions_cache__WEBPACK_IMPORTED_MODULE_2__/* .ReserveCacheError */ .Zh.name) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(err.message);
}
else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .warning */ .$e('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.');
}
throw error;
}
}
}
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(packageManager, additionalCache) {
const primaryKey = _actions_core__WEBPACK_IMPORTED_MODULE_3__/* .getState */ .Gu(additionalCachePrimaryKeyState(additionalCache.name));
const matchedKey = _actions_core__WEBPACK_IMPORTED_MODULE_3__/* .getState */ .Gu(additionalCacheMatchedKeyState(additionalCache.name));
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`No primary key for the ${additionalCache.name} cache, not saving cache.`);
return;
}
else if (matchedKey === primaryKey) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`);
return;
}
const globber = await _actions_glob__WEBPACK_IMPORTED_MODULE_4__/* .create */ .v(additionalCache.path.join('\n'), {
implicitDescendants: false
});
const cachePaths = await globber.glob();
if (cachePaths.length === 0) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`${additionalCache.name} cache paths do not exist, not saving cache.`);
return;
}
try {
const cacheId = await _actions_cache__WEBPACK_IMPORTED_MODULE_2__/* .saveCache */ .Io(cachePaths, primaryKey);
if (cacheId === -1) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`${additionalCache.name} cache was not saved for the key: ${primaryKey}`);
return;
}
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`${additionalCache.name} cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === _actions_cache__WEBPACK_IMPORTED_MODULE_2__/* .ValidationError */ .yI.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`);
return;
}
if (err.name === _actions_cache__WEBPACK_IMPORTED_MODULE_2__/* .ReserveCacheError */ .Zh.name) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(err.message);
}
else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .warning */ .$e(`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;
}
}
}
/**
* @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache
* @returns true if the given error seems related to the {@link https://github.com/actions/cache/issues/454|running Gradle Daemon issue}.
* @see {@link https://github.com/actions/cache/issues/454#issuecomment-840493935|why --no-daemon is necessary}
*/
function isProbablyGradleDaemonProblem(packageManager, error) {
if (packageManager.id !== 'gradle' ||
process.env['RUNNER_OS'] !== 'Windows') {
return false;
}
const message = error.message || '';
return message.startsWith('Tar failed with error: ');
}
/***/ })
};
+62534
View File
File diff suppressed because it is too large Load Diff
+2021 -64465
View File
File diff suppressed because it is too large Load Diff
+162
View File
@@ -0,0 +1,162 @@
export const id = 126;
export const ids = [126];
export const modules = {
/***/ 4126:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ CorrettoDistribution: () => (/* binding */ CorrettoDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4527);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
const CORRETTO_VERSIONS_URL = 'https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json';
class CorrettoDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Corretto', installerOptions);
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_4__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_4__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_4__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_3___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async findPackageForDownload(version) {
if (!this.stable) {
throw new Error('Early access versions are not supported');
}
const availableVersions = await this.getAvailableVersions();
// The `latest` alias is normalized to the SemVer wildcard, but Corretto
// matches on an exact major version, so resolve it to the newest available
// major from Corretto's own list.
if (this.latest) {
const majors = availableVersions
.map(item => parseInt(item.version, 10))
.filter(major => Number.isFinite(major) && major > 0);
if (majors.length === 0) {
throw new Error('Could not determine the latest available Corretto major version from remote metadata');
}
version = Math.max(...majors).toString();
}
if (version.includes('.')) {
throw new Error('Only major versions are supported');
}
const matchingVersions = availableVersions
.filter(item => item.version == version)
.map(item => {
return {
version: (0,_util_js__WEBPACK_IMPORTED_MODULE_4__/* .convertVersionToSemver */ .ZY)(item.correttoVersion),
url: item.downloadLink,
checksum: {
algorithm: 'sha256',
value: item.checksum_sha256,
source: CORRETTO_VERSIONS_URL
}
};
});
const resolvedVersion = matchingVersions.length > 0 ? matchingVersions[0] : null;
if (!resolvedVersion) {
const availableVersionStrings = availableVersions.map(item => item.version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
return resolvedVersion;
}
async getAvailableVersions() {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
const imageType = this.packageType;
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for Corretto took'); // eslint-disable-line no-console
}
const fetchCurrentVersions = await this.http.getJson(CORRETTO_VERSIONS_URL);
const fetchedCurrentVersions = fetchCurrentVersions.result;
if (!fetchedCurrentVersions) {
throw Error(`Could not fetch latest corretto versions from ${CORRETTO_VERSIONS_URL}`);
}
const eligibleVersions = fetchedCurrentVersions?.[platform]?.[arch]?.[imageType];
const availableVersions = this.getAvailableVersionsForPlatform(eligibleVersions);
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .startGroup */ .Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Corretto took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Available versions: [${availableVersions.length}]`);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(availableVersions
.map(item => `${item.version}: ${item.correttoVersion}`)
.join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .endGroup */ .N4();
}
return availableVersions;
}
getAvailableVersionsForPlatform(eligibleVersions) {
const availableVersions = [];
for (const version in eligibleVersions) {
const availableVersion = eligibleVersions[version];
for (const fileType in availableVersion) {
const skipNonExtractableBinaries = fileType != (0,_util_js__WEBPACK_IMPORTED_MODULE_4__/* .getDownloadArchiveExtension */ .ag)();
if (skipNonExtractableBinaries) {
continue;
}
const availableVersionDetails = availableVersion[fileType];
const correttoVersion = this.getCorrettoVersion(availableVersionDetails.resource);
availableVersions.push({
checksum: availableVersionDetails.checksum,
checksum_sha256: availableVersionDetails.checksum_sha256,
fileType,
resource: availableVersionDetails.resource,
downloadLink: `https://corretto.aws${availableVersionDetails.resource}`,
version: version,
correttoVersion
});
}
}
return availableVersions;
}
getPlatformOption() {
// Corretto has its own platform names so we need to map them
switch (process.platform) {
case 'darwin':
return 'macos';
case 'win32':
return 'windows';
default:
return process.platform;
}
}
distributionArchitecture() {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
getCorrettoVersion(resource) {
const regex = /(\d+.+)\//;
const match = regex.exec(resource);
if (match === null) {
throw Error(`Could not parse corretto version from ${resource}`);
}
return match[1];
}
}
/***/ })
};
+157
View File
@@ -0,0 +1,157 @@
export const id = 151;
export const ids = [151];
export const modules = {
/***/ 8151:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ KonaDistribution: () => (/* binding */ KonaDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
const KONA_RELEASES_URL = 'https://tencent.github.io/konajdk/releases/kona-v1.json';
class KonaDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Kona', installerOptions);
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Kona JDK ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
const javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)();
const archivePath = process.platform === 'win32'
? (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath)
: javaArchivePath;
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(archivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_3___default().readdirSync(extractedJavaPath)[0];
const jdkDirectory = path__WEBPACK_IMPORTED_MODULE_4___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(jdkDirectory, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async findPackageForDownload(version) {
if (!this.stable) {
throw new Error('Kona provides stable releases only');
}
if (this.packageType !== 'jdk') {
throw new Error('Kona provides jdk only');
}
const availableReleases = await this.getAvailableReleases();
const releases = availableReleases
.filter(item => {
return (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.version);
})
.map(item => {
return {
version: item.version,
url: item.downloadUrl,
checksum: item.checksum
? {
algorithm: 'sha256',
value: item.checksum,
source: KONA_RELEASES_URL
}
: undefined
};
})
.sort((a, b) => -semver__WEBPACK_IMPORTED_MODULE_2___default().compareBuild(a.version, b.version));
if (!releases.length) {
throw new Error(`No Kona release for the specified version "${version}" on OS "${this.getOs()}" and arch "${this.getArch()}".`);
}
return releases[0];
}
async getAvailableReleases() {
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
console.time('Retrieving available releases for Kona took'); // eslint-disable-line no-console
}
const releaseInfo = await this.fetchReleaseInfo();
if (!releaseInfo) {
throw new Error(`Couldn't fetch Kona release information`);
}
const availableReleases = this.chooseReleases(this.getOs(), this.getArch(), releaseInfo);
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .startGroup */ .Oh('Print information about available releases');
console.timeEnd('Retrieving available releases for Kona took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(availableReleases.map(item => item.version).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .endGroup */ .N4();
}
return availableReleases;
}
async fetchReleaseInfo() {
try {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Fetching Kona release info from URL: ${KONA_RELEASES_URL}`);
return (await this.http.getJson(KONA_RELEASES_URL))
.result;
}
catch (err) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Fetching Kona release info from the URL: ${KONA_RELEASES_URL} failed with the error: ${err.message}`);
return null;
}
}
chooseReleases(os, arch, releaseInfo) {
const releases = [];
for (const majorVersion in releaseInfo) {
const versions = releaseInfo[majorVersion];
for (const version of versions) {
if (!version.latest) {
continue;
}
for (const file of version.files) {
if (file.os === os && file.arch === arch) {
releases.push({
version: version.version,
jdkVersion: version.jdkVersion,
os: os,
arch: arch,
downloadUrl: version.baseUrl + file.filename,
checksum: file.checksum
});
break;
}
}
}
}
return releases;
}
getOs() {
switch (process.platform) {
case 'darwin':
return 'macos';
case 'win32':
return 'windows';
default:
return process.platform;
}
}
getArch() {
switch (this.architecture) {
case 'arm64':
return 'aarch64';
case 'x64':
return 'x86_64';
default:
return this.architecture;
}
}
}
/***/ })
};
+121
View File
@@ -0,0 +1,121 @@
export const id = 182;
export const ids = [182];
export const modules = {
/***/ 1182:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ OracleDistribution: () => (/* binding */ OracleDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527);
/* harmony import */ var _actions_http_client__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4942);
const ORACLE_DL_BASE = 'https://download.oracle.com/java';
class OracleDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Oracle', installerOptions);
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_3___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async findPackageForDownload(range) {
const arch = this.distributionArchitecture();
if (arch !== 'x64' && arch !== 'aarch64') {
throw new Error(`Unsupported architecture: ${this.architecture}`);
}
if (!this.stable) {
throw new Error('Early access versions are not supported');
}
if (this.packageType !== 'jdk') {
throw new Error('Oracle JDK provides only the `jdk` package type');
}
const platform = this.getPlatform();
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)();
// The `latest` alias is normalized to the SemVer wildcard. Oracle builds its
// download URLs from a concrete major and has no endpoint to list releases,
// so resolve the newest available GA major from the Adoptium API and use it.
if (this.latest) {
const latestMajor = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getLatestMajorVersion */ .ri)(this.http);
range = latestMajor.toString();
}
const isOnlyMajorProvided = !range.includes('.');
const major = isOnlyMajorProvided ? range : range.split('.')[0];
const possibleUrls = [];
/**
* NOTE
* If only major version was provided we will check it under /latest first
* in order to retrieve the latest possible version if possible,
* otherwise we will fall back to /archive where we are guaranteed to
* find any version if it exists
*/
if (isOnlyMajorProvided) {
possibleUrls.push(`${ORACLE_DL_BASE}/${major}/latest/jdk-${major}_${platform}-${arch}_bin.${extension}`);
}
possibleUrls.push(`${ORACLE_DL_BASE}/${major}/archive/jdk-${range}_${platform}-${arch}_bin.${extension}`);
if (parseInt(major) < 17) {
throw new Error('Oracle JDK is only supported for JDK 17 and later');
}
for (const url of possibleUrls) {
const response = await this.http.head(url);
if (response.message.statusCode === _actions_http_client__WEBPACK_IMPORTED_MODULE_6__/* .HttpCodes */ .Hv.OK) {
return {
url,
version: range,
checksum: await this.fetchChecksum(`${url}.sha256`, 'sha256')
};
}
if (response.message.statusCode !== _actions_http_client__WEBPACK_IMPORTED_MODULE_6__/* .HttpCodes */ .Hv.NotFound) {
throw new Error(`Http request for Oracle JDK failed with status code: ${response.message.statusCode}`);
}
}
if (this.latest) {
const error = this.createVersionNotFoundError(range);
error.message += `\nThe latest Java major version (${range}) is not yet available for the Oracle JDK distribution. Please specify a concrete version instead of 'latest'.`;
throw error;
}
throw this.createVersionNotFoundError(range);
}
getPlatform(platform = process.platform) {
switch (platform) {
case 'darwin':
return 'macos';
case 'win32':
return 'windows';
case 'linux':
return 'linux';
default:
throw new Error(`Platform '${platform}' is not supported. Supported platforms: 'linux', 'macos', 'windows'`);
}
}
}
/***/ })
};
+90
View File
@@ -0,0 +1,90 @@
export const id = 19;
export const ids = [19];
export const modules = {
/***/ 1019:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ LocalDistribution: () => (/* binding */ LocalDistribution)
/* harmony export */ });
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9805);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3838);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527);
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(7242);
class LocalDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O {
jdkFile;
constructor(installerOptions, jdkFile) {
super('jdkfile', installerOptions);
this.jdkFile = jdkFile;
}
async setupJava() {
if (this.latest) {
throw new Error("The 'latest' version alias is not supported for the 'jdkfile' distribution. Please specify a concrete version.");
}
let foundJava = this.forceDownload ? null : this.findInToolcache();
if (foundJava) {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Resolved Java ${foundJava.version} from tool-cache`);
}
else {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Java ${this.version} was not found in tool-cache. Trying to unpack JDK file...`);
if (!this.jdkFile) {
throw new Error("'jdkFile' is not specified");
}
const jdkFilePath = path__WEBPACK_IMPORTED_MODULE_3___default().resolve(this.jdkFile);
const stats = fs__WEBPACK_IMPORTED_MODULE_2___default().statSync(jdkFilePath);
if (!stats.isFile()) {
throw new Error(`JDK file was not found in path '${jdkFilePath}'`);
}
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Extracting Java from '${jdkFilePath}'`);
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(jdkFilePath);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_3___default().join(extractedJavaPath, archiveName);
const javaVersion = this.version;
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_0__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaVersion), this.architecture);
foundJava = {
version: javaVersion,
path: javaPath
};
}
// JDK folder may contain postfix "Contents/Home" on macOS
const macOSPostfixPath = path__WEBPACK_IMPORTED_MODULE_3___default().join(foundJava.path, _constants_js__WEBPACK_IMPORTED_MODULE_6__/* .MACOS_JAVA_CONTENT_POSTFIX */ .PG);
if (process.platform === 'darwin' && fs__WEBPACK_IMPORTED_MODULE_2___default().existsSync(macOSPostfixPath)) {
foundJava.path = macOSPostfixPath;
}
if (this.setDefault) {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Setting Java ${foundJava.version} as the default`);
this.setJavaDefault(foundJava.version, foundJava.path);
}
else {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Installing Java ${foundJava.version} (not setting as default)`);
this.setJavaEnvironment(foundJava.version, foundJava.path);
}
return foundJava;
}
async findPackageForDownload(version // eslint-disable-line @typescript-eslint/no-unused-vars
) {
throw new Error('This method should not be implemented in local file provider');
}
async downloadTool(javaRelease // eslint-disable-line @typescript-eslint/no-unused-vars
) {
throw new Error('This method should not be implemented in local file provider');
}
}
/***/ })
};
+168
View File
@@ -0,0 +1,168 @@
export const id = 220;
export const ids = [220];
export const modules = {
/***/ 3220:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
MicrosoftDistributions: () => (/* binding */ MicrosoftDistributions)
});
// UNUSED EXPORTS: MICROSOFT_PUBLIC_KEY
// EXTERNAL MODULE: ./src/distributions/base-installer.ts + 2 modules
var base_installer = __webpack_require__(6242);
// EXTERNAL MODULE: ./src/util.ts
var util = __webpack_require__(4527);
// EXTERNAL MODULE: ./src/gpg.ts
var gpg = __webpack_require__(8343);
;// CONCATENATED MODULE: ./src/distributions/microsoft/microsoft-key.ts
// Microsoft Build of OpenJDK GPG signing key
// Retrieved from: https://download.visualstudio.microsoft.com/download/pr/b90071e2-e0cf-4411-98be-dbeb09d67bf0/8622862bcd54206e158c5abca0582c9b/464279_464280_aoc_20210208.asc
const MICROSOFT_PUBLIC_KEY = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BSN Pgp v1.1.0.0
mQENBGAhlWcBCADCQjj6huLTenvZSLej35e9YKEHm4lix2uvPOONexMaU8V2v7KL
RGdoXF7jwHci7efnPZ+9zpS2+g3rhvv8M7yWy9E/1psEtGzvmp1IL/qIabMEQqi+
UlhPGh7MQ/BkXAlic8Dyl3XYqr0EXS11iCiTr6Zkxs9Ee4V54gxL4gogRn4wk9sl
/nrjgDzMsUwla0pynoQQvYpqCdiAr3gKKllT1skCDqgVOMMyZxsx9HjZxg/3AJz6
r5i512L2R+3Hkv+XmxT+mnGBCFcny0DM7PjNXEmIK3ZSkro1tQML90zx3Fyh5esx
fpVvuIXGFV75o35VVCBZoiD3hcfOnIJsPQ9nABEBAAG0OE1pY3Jvc29mdCBKYXZh
IEVuZ2luZWVyaW5nIDxqYXZhcGxhdGluZnJhQG1pY3Jvc29mdC5jb20+iQE4BBMB
CAAiBQJgIZVnAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRA1Ux0xWyHB
icwTCACJO2FGNocNvdUtAb+eDKuGwt0chAJdCES2ZtgBScwrwDyWpxpRznoXWBHL
MJeLyxJoKsCG3vVlY4uh48psCzVm3OKvi7MCPT955t8W6TzfSBxTpjR8zRgJkjPJ
EGhHTlusUfz7TtM5etJF0qscSJH1grcNsgtee97mk4QyEzT8Di83NQmYxKcBrliq
yK/SWWt8VkTyYAEO6L5PoB4L9r8ka27uQs+jgCw+/Z0JMtNmmhyNGY3+a1YtPeoy
JdQaI9LphfKGbVaz6SK2aol7vj+c2TG3TLUYdOYGMH1OZlri2GTkCVjwna2GC7p4
Fa133tP85xzJEq1XeXm8WeLFo2wV
=rHCS
-----END PGP PUBLIC KEY BLOCK-----`;
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules
var core = __webpack_require__(3838);
// EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js + 2 modules
var tool_cache = __webpack_require__(9805);
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__(9896);
var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_);
// EXTERNAL MODULE: external "path"
var external_path_ = __webpack_require__(6928);
var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
;// CONCATENATED MODULE: ./src/distributions/microsoft/installer.ts
class MicrosoftDistributions extends base_installer/* JavaBase */.O {
constructor(installerOptions) {
super('Microsoft', installerOptions);
}
async downloadTool(javaRelease) {
core/* info */.pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
if (this.verifySignature) {
if (!javaRelease.signatureUrl) {
throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.`);
}
core/* info */.pq(`Verifying Java package signature...`);
try {
await gpg/* verifyPackageSignature */.Yi(javaArchivePath, javaRelease.signatureUrl, this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY);
}
catch (error) {
throw new Error(`Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${error.message}`, { cause: error });
}
}
core/* info */.pq(`Extracting Java archive...`);
const extension = (0,util/* getDownloadArchiveExtension */.ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,util/* renameWinArchive */.n2)(javaArchivePath);
}
const extractedJavaPath = await (0,util/* extractJdkFile */.PE)(javaArchivePath, extension);
const archiveName = external_fs_default().readdirSync(extractedJavaPath)[0];
const archivePath = external_path_default().join(extractedJavaPath, archiveName);
const javaPath = await tool_cache/* cacheDir */.e8(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async findPackageForDownload(range) {
const arch = this.distributionArchitecture();
if (arch !== 'x64' && arch !== 'aarch64') {
throw new Error(`Unsupported architecture: ${this.architecture}`);
}
if (!this.stable) {
throw new Error('Early access versions are not supported');
}
if (this.packageType !== 'jdk') {
throw new Error('Microsoft Build of OpenJDK provides only the `jdk` package type');
}
const manifest = await this.getAvailableVersions();
if (!manifest) {
throw new Error('Could not load manifest for Microsoft Build of OpenJDK');
}
const foundRelease = await tool_cache/* findFromManifest */.DC(range, true, manifest, arch);
if (!foundRelease) {
const availableVersionStrings = manifest.map(item => item.version);
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
const file = foundRelease.files[0];
const signatureUrl = file.signature_url ?? `${file.download_url}.sig`;
return {
url: file.download_url,
signatureUrl,
version: foundRelease.version,
checksum: await this.fetchChecksum(`${file.download_url}.sha256sum.txt`, 'sha256')
};
}
supportsSignatureVerification() {
return true;
}
async getAvailableVersions() {
// TODO get these dynamically!
// We will need Microsoft to add an endpoint where we can query for versions.
const owner = 'actions';
const repository = 'setup-java';
const branch = 'main';
const filePath = 'src/distributions/microsoft/microsoft-openjdk-versions.json';
let releases = null;
const fileUrl = `https://api.github.com/repos/${owner}/${repository}/contents/${filePath}?ref=${branch}`;
const headers = (0,util/* getGitHubHttpHeaders */.U_)();
let response = null;
if (core/* isDebug */._o()) {
console.time('Retrieving available versions for Microsoft took'); // eslint-disable-line no-console
}
try {
response = await this.http.getJson(fileUrl, headers);
if (!response.result) {
return null;
}
}
catch (err) {
core/* debug */.Yz(`Http request for microsoft-openjdk-versions.json failed with status code: ${response?.statusCode}. Error: ${err}`);
return null;
}
if (response.result) {
releases = response.result;
}
if (core/* isDebug */._o() && releases) {
core/* startGroup */.Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Microsoft took'); // eslint-disable-line no-console
core/* debug */.Yz(`Available versions: [${releases.length}]`);
core/* debug */.Yz(releases.map(item => item.version).join(', '));
core/* endGroup */.N4();
}
return releases;
}
}
/***/ })
};
+562
View File
@@ -0,0 +1,562 @@
export const id = 242;
export const ids = [242];
export const modules = {
/***/ 6242:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
O: () => (/* binding */ JavaBase)
});
// EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js + 2 modules
var tool_cache = __webpack_require__(9805);
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules
var core = __webpack_require__(3838);
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__(9896);
// EXTERNAL MODULE: ./node_modules/semver/index.js
var semver = __webpack_require__(2088);
var semver_default = /*#__PURE__*/__webpack_require__.n(semver);
// EXTERNAL MODULE: external "path"
var external_path_ = __webpack_require__(6928);
var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
// EXTERNAL MODULE: ./node_modules/@actions/http-client/lib/index.js + 1 modules
var lib = __webpack_require__(4942);
// EXTERNAL MODULE: ./src/util.ts
var util = __webpack_require__(4527);
// EXTERNAL MODULE: ./src/constants.ts
var constants = __webpack_require__(7242);
;// CONCATENATED MODULE: ./src/retrying-http-client.ts
const RETRYABLE_HTTP_STATUS_CODES = new Set([429, 502, 503, 504, 522]);
const RETRYABLE_NETWORK_ERROR_CODES = new Set([
'ETIMEDOUT',
'ECONNRESET',
'ENOTFOUND',
'ECONNREFUSED'
]);
const RETRYABLE_HTTP_VERBS = new Set(['OPTIONS', 'GET', 'DELETE', 'HEAD']);
class RetryingHttpClient extends lib/* HttpClient */.Qq {
maxAttempts;
baseDelayMs;
maxDelayMs;
sleep;
random;
now;
constructor(userAgent, retryOptions = {}) {
super(userAgent, undefined, { allowRetries: false });
this.maxAttempts = retryOptions.maxAttempts ?? 4;
this.baseDelayMs = retryOptions.baseDelayMs ?? 1000;
this.maxDelayMs = retryOptions.maxDelayMs ?? 10000;
this.sleep =
retryOptions.sleep ??
(delayMs => new Promise(resolve => setTimeout(resolve, delayMs)));
this.random = retryOptions.random ?? Math.random;
this.now = retryOptions.now ?? Date.now;
if (this.maxAttempts < 1) {
throw new Error('maxAttempts must be at least 1');
}
if (this.baseDelayMs < 0 || this.maxDelayMs < this.baseDelayMs) {
throw new Error('baseDelayMs must be non-negative and no greater than maxDelayMs');
}
}
async request(verb, requestUrl, data, headers) {
if (!RETRYABLE_HTTP_VERBS.has(verb)) {
return super.request(verb, requestUrl, data, headers);
}
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
try {
const response = await super.request(verb, requestUrl, data, headers);
const statusCode = response.message.statusCode;
if (!statusCode ||
!RETRYABLE_HTTP_STATUS_CODES.has(statusCode) ||
attempt === this.maxAttempts) {
return response;
}
const delayMs = this.getDelayMs(attempt, response.message.headers['retry-after']);
await response.readBody();
this.logRetry(attempt, delayMs, `HTTP ${statusCode}`);
await this.sleep(delayMs);
}
catch (error) {
if (!isRetryableNetworkError(error) || attempt === this.maxAttempts) {
throw error;
}
const delayMs = this.getDelayMs(attempt);
this.logRetry(attempt, delayMs, getErrorMessage(error));
await this.sleep(delayMs);
}
}
throw new Error('HTTP retry attempts exhausted unexpectedly');
}
getDelayMs(failedAttempt, retryAfter) {
const exponentialDelay = Math.min(this.maxDelayMs, this.baseDelayMs * 2 ** (failedAttempt - 1));
const jitteredDelay = Math.floor(exponentialDelay / 2 + this.random() * (exponentialDelay / 2));
const retryAfterDelay = parseRetryAfter(retryAfter, this.now());
return Math.min(this.maxDelayMs, Math.max(jitteredDelay, retryAfterDelay ?? 0));
}
logRetry(failedAttempt, delayMs, reason) {
core/* info */.pq(`Request attempt ${failedAttempt} of ${this.maxAttempts} failed (${reason}); retrying in ${delayMs} ms`);
}
}
function parseRetryAfter(value, nowMs) {
const retryAfter = Array.isArray(value) ? value[0] : value;
if (!retryAfter) {
return undefined;
}
if (/^\d+$/.test(retryAfter.trim())) {
return Number(retryAfter) * 1000;
}
const retryAt = Date.parse(retryAfter);
if (Number.isNaN(retryAt) || retryAt <= nowMs) {
return undefined;
}
return retryAt - nowMs;
}
function isRetryableNetworkError(error) {
if (!isErrorRecord(error)) {
return false;
}
if (typeof error.code === 'string' &&
RETRYABLE_NETWORK_ERROR_CODES.has(error.code)) {
return true;
}
return (Array.isArray(error.errors) &&
error.errors.some(nestedError => isRetryableNetworkError(nestedError)));
}
function isErrorRecord(error) {
return typeof error === 'object' && error !== null;
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : 'network error';
}
// EXTERNAL MODULE: external "os"
var external_os_ = __webpack_require__(857);
var external_os_default = /*#__PURE__*/__webpack_require__.n(external_os_);
// EXTERNAL MODULE: external "crypto"
var external_crypto_ = __webpack_require__(6982);
// EXTERNAL MODULE: external "stream/promises"
var promises_ = __webpack_require__(4548);
;// CONCATENATED MODULE: ./src/checksum.ts
function sanitizedSource(source) {
if (!source) {
return '';
}
try {
const url = new URL(source);
return ` from ${url.origin}${url.pathname}`;
}
catch {
return ' from an invalid checksum source';
}
}
// Length, in hex characters, of a digest produced by each supported algorithm.
// Exported so callers (e.g. fetchChecksum) can infer which algorithm a vendor
// actually used when it doesn't disclose it via the checksum URL/filename.
function expectedDigestLength(algorithm) {
return algorithm === 'sha256' ? 64 : algorithm === 'sha512' ? 128 : 0;
}
function normalizeExpectedDigest(checksum) {
const algorithm = checksum.algorithm;
const digest = typeof checksum.value === 'string'
? checksum.value.trim().toLowerCase()
: '';
const expectedLength = expectedDigestLength(algorithm);
if (expectedLength === 0) {
throw new Error(`Unsupported checksum algorithm '${String(algorithm)}'${sanitizedSource(checksum.source)}. Supported algorithms are sha256 and sha512.`);
}
if (!new RegExp(`^[a-f0-9]{${expectedLength}}$`).test(digest)) {
throw new Error(`Malformed ${algorithm} checksum metadata${sanitizedSource(checksum.source)}: expected a ${expectedLength}-character hexadecimal digest.`);
}
return digest;
}
async function calculateChecksum(filePath, algorithm) {
const hash = (0,external_crypto_.createHash)(algorithm);
await (0,promises_.pipeline)((0,external_fs_.createReadStream)(filePath), hash);
return hash.digest('hex');
}
async function verifyChecksum(filePath, checksum, context) {
const expected = normalizeExpectedDigest(checksum);
const actual = await calculateChecksum(filePath, checksum.algorithm);
const matches = (0,external_crypto_.timingSafeEqual)(Buffer.from(expected, 'hex'), Buffer.from(actual, 'hex'));
if (!matches) {
throw new Error(`Checksum verification failed for ${context.distribution} version ${context.version}: ${checksum.algorithm} expected ${expected}, actual ${actual}.`);
}
}
// EXTERNAL MODULE: ./src/distributions/platform-types.ts
var platform_types = __webpack_require__(7444);
;// CONCATENATED MODULE: ./src/distributions/base-installer.ts
class JavaBase {
distribution;
http;
version;
architecture;
packageType;
stable;
latest;
checkLatest;
forceDownload;
setDefault;
verifySignature;
verifySignaturePublicKey;
constructor(distribution, installerOptions) {
this.distribution = distribution;
this.http = new RetryingHttpClient('actions/setup-java');
({
version: this.version,
stable: this.stable,
latest: this.latest
} = this.normalizeVersion(installerOptions.version));
this.architecture = (0,platform_types/* normalizeArchitecture */.dV)(installerOptions.architecture || external_os_default().arch());
this.packageType = installerOptions.packageType;
this.checkLatest = installerOptions.checkLatest;
this.forceDownload = installerOptions.forceDownload ?? false;
this.setDefault =
installerOptions.setDefault !== undefined
? installerOptions.setDefault
: true;
this.verifySignature = installerOptions.verifySignature ?? false;
this.verifySignaturePublicKey = installerOptions.verifySignaturePublicKey;
}
async downloadAndVerify(javaRelease) {
const archivePath = await tool_cache/* downloadTool */.bq(javaRelease.url);
const checksum = javaRelease.checksum;
if (!checksum || !checksum.value?.trim()) {
core/* debug */.Yz(`No authoritative checksum is available for ${this.distribution} version ${javaRelease.version}; skipping checksum verification.`);
return archivePath;
}
try {
await verifyChecksum(archivePath, checksum, {
distribution: this.distribution,
version: javaRelease.version
});
core/* debug */.Yz(`Verified ${checksum.algorithm} checksum for ${this.distribution} version ${javaRelease.version}.`);
return archivePath;
}
catch (error) {
let cleanupError;
let cleanupFailed = false;
try {
await external_fs_.promises.rm(archivePath, { force: true });
}
catch (caughtCleanupError) {
cleanupError = caughtCleanupError;
cleanupFailed = true;
}
if (cleanupFailed) {
throw new Error(`${error.message} Failed to remove the downloaded archive after verification failure: ${cleanupError.message}`, { cause: error });
}
throw error;
}
}
async fetchChecksum(checksumUrl, algorithm) {
// Some vendors (e.g. JetBrains) publish a single, generically-named
// checksum sibling (`.checksum`) whose digest algorithm isn't disclosed
// by the URL and has changed across releases. Accepting a list of
// candidate algorithms lets callers pass every algorithm the vendor is
// known to use; the actual algorithm is then inferred from the length of
// the returned digest.
const algorithms = Array.isArray(algorithm) ? algorithm : [algorithm];
const algorithmLabel = algorithms.join(' or ');
const response = await this.http.get(checksumUrl);
const statusCode = response.message.statusCode;
const source = (() => {
try {
const url = new URL(checksumUrl);
return `${url.origin}${url.pathname}`;
}
catch {
return 'an invalid checksum URL';
}
})();
if (statusCode === lib/* HttpCodes */.Hv.NotFound) {
core/* debug */.Yz(`No authoritative ${algorithmLabel} checksum is available for ${this.distribution} from ${source}; skipping checksum verification.`);
return undefined;
}
if (statusCode !== lib/* HttpCodes */.Hv.OK) {
throw new Error(`Failed to fetch the authoritative ${algorithmLabel} checksum for ${this.distribution} from ${source} (HTTP ${statusCode}).`);
}
const body = await response.readBody();
const value = body.trim().split(/\s+/, 1)[0] ?? '';
if (!value) {
throw new Error(`Received an empty authoritative ${algorithmLabel} checksum for ${this.distribution} from ${source}.`);
}
// Prefer the strongest algorithm whose digest length matches what was
// actually returned; fall back to the first candidate (preserving prior
// behavior/error messages) when the digest doesn't match any of them.
const resolvedAlgorithm = algorithms.find(algo => value.length === expectedDigestLength(algo)) ??
algorithms[0];
return { algorithm: resolvedAlgorithm, value, source: checksumUrl };
}
async setupJava() {
if (this.verifySignature && !this.supportsSignatureVerification()) {
throw new Error(`Input 'verify-signature' is not supported for distribution '${this.distribution}'.`);
}
let foundJava = this.forceDownload ? null : this.findInToolcache();
if (foundJava && !this.checkLatest && !this.latest) {
core/* info */.pq(`Resolved Java ${foundJava.version} from tool-cache`);
}
else {
core/* info */.pq('Trying to resolve the latest version from remote');
try {
const javaRelease = await this.findPackageForDownload(this.version);
core/* info */.pq(`Resolved latest version as ${javaRelease.version}`);
if (!this.forceDownload && foundJava?.version === javaRelease.version) {
core/* info */.pq(`Resolved Java ${foundJava.version} from tool-cache`);
}
else {
core/* info */.pq('Trying to download...');
foundJava = await this.downloadTool(javaRelease);
core/* info */.pq(`Java ${foundJava.version} was downloaded`);
}
}
catch (error) {
this.logSetupError(error);
throw error;
}
}
if (!foundJava) {
throw new Error('Failed to resolve Java version');
}
// JDK folder may contain postfix "Contents/Home" on macOS
const macOSPostfixPath = external_path_default().join(foundJava.path, constants/* MACOS_JAVA_CONTENT_POSTFIX */.PG);
if (process.platform === 'darwin' && external_fs_.existsSync(macOSPostfixPath)) {
foundJava.path = macOSPostfixPath;
}
if (this.setDefault) {
core/* info */.pq(`Setting Java ${foundJava.version} as the default`);
this.setJavaDefault(foundJava.version, foundJava.path);
}
else {
core/* info */.pq(`Installing Java ${foundJava.version} (not setting as default)`);
this.setJavaEnvironment(foundJava.version, foundJava.path);
}
return foundJava;
}
logSetupError(error) {
const httpStatusCode = error instanceof tool_cache/* HTTPError */.Hl
? error.httpStatusCode
: error instanceof lib/* HttpClientError */.Kg
? error.statusCode
: undefined;
if (httpStatusCode) {
if (httpStatusCode === 403) {
core/* error */.z3('HTTP 403: Permission denied or access restricted.');
}
else if (httpStatusCode === 429) {
core/* warning */.$e('HTTP 429: Rate limit exceeded. Please retry later.');
}
else {
core/* error */.z3(`HTTP ${httpStatusCode}: ${error.message}`);
}
}
else if (error && error.errors && Array.isArray(error.errors)) {
core/* error */.z3(`Java setup failed due to network or configuration error(s)`);
if (error instanceof Error && error.stack) {
core/* debug */.Yz(error.stack);
}
for (const err of error.errors) {
const endpoint = err?.address || err?.hostname || '';
const port = err?.port ? `:${err.port}` : '';
const message = err?.message || 'Aggregate error';
const endpointInfo = !message.includes(endpoint)
? ` ${endpoint}${port}`
: '';
const localInfo = err.localAddress && err.localPort
? ` - Local (${err.localAddress}:${err.localPort})`
: '';
const logMessage = `${message}${endpointInfo}${localInfo}`;
core/* error */.z3(logMessage);
core/* debug */.Yz(`${err.stack || err.message}`);
Object.entries(err).forEach(([key, value]) => {
core/* debug */.Yz(`"${key}": ${JSON.stringify(value)}`);
});
}
}
else {
const message = error instanceof Error ? error.message : JSON.stringify(error);
core/* error */.z3(`Java setup process failed due to: ${message}`);
if (typeof error?.code === 'string') {
core/* debug */.Yz(error.stack);
}
const errorDetails = {
name: error.name,
message: error.message,
...Object.getOwnPropertyNames(error)
.filter(prop => !['name', 'message', 'stack'].includes(prop))
.reduce((acc, prop) => {
acc[prop] = error[prop];
return acc;
}, {})
};
Object.entries(errorDetails).forEach(([key, value]) => {
core/* debug */.Yz(`"${key}": ${JSON.stringify(value)}`);
});
}
}
get toolcacheFolderName() {
return `Java_${this.distribution}_${this.packageType}`;
}
supportsSignatureVerification() {
return false;
}
getToolcacheVersionName(version) {
if (!this.stable) {
if (version.includes('+')) {
return version.replace('+', '-ea.');
}
else {
return `${version}-ea`;
}
}
// Kotlin and some Java dependencies don't work properly when Java path contains "+" sign
// so replace "/hostedtoolcache/Java/11.0.3+4/x64" to "/hostedtoolcache/Java/11.0.3-4/x64" when saves to cache
// related issue: https://github.com/actions/virtual-environments/issues/3014
return version.replace('+', '-');
}
findInToolcache() {
// we can't use tc.find directly because firstly, we need to filter versions by stability flag
// if *-ea is provided, take only ea versions from toolcache, otherwise - only stable versions
const availableVersions = tool_cache/* findAllVersions */.iq(this.toolcacheFolderName, this.architecture)
.map(item => {
return {
version: item
.replace('-ea.', '+')
.replace(/-ea$/, '')
// Kotlin and some Java dependencies don't work properly when Java path contains "+" sign
// so replace "/hostedtoolcache/Java/11.0.3-4/x64" to "/hostedtoolcache/Java/11.0.3+4/x64" when retrieves to cache
// related issue: https://github.com/actions/virtual-environments/issues/3014
.replace('-', '+'),
path: (0,util/* getToolcachePath */.yH)(this.toolcacheFolderName, item, this.architecture) || '',
stable: !item.includes('-ea')
};
})
.filter(item => item.stable === this.stable);
const satisfiedVersions = availableVersions
.filter(item => (0,util/* isVersionSatisfies */.y)(this.version, item.version))
.filter(item => item.path)
.sort((a, b) => {
return -semver_default().compareBuild(a.version, b.version);
});
if (!satisfiedVersions || satisfiedVersions.length === 0) {
return null;
}
return {
version: satisfiedVersions[0].version,
path: satisfiedVersions[0].path
};
}
normalizeVersion(version) {
let stable = true;
const latest = false;
// Support the `latest` alias (case-insensitive), which floats to the newest
// available stable/GA release. It is translated to the SemVer wildcard `x`
// so the existing "newest satisfying version wins" resolution applies.
const normalized = version.trim().toLowerCase();
if (normalized === 'latest') {
return {
version: 'x',
stable: true,
latest: true
};
}
// Reject `latest` combined with any qualifier (e.g. `latest-ea`). Such inputs
// would otherwise have their `-ea` suffix stripped and fall through to the
// generic SemVer check, which fails with a confusing "'latest' is not valid
// SemVer" message even though `latest` is a supported value. Fail early with a
// targeted explanation instead.
if (normalized.startsWith('latest')) {
throw new Error(`The 'latest' alias resolves stable (GA) releases only and cannot be combined with '-ea' or other qualifiers (received '${version}'). Use 'latest' on its own, or specify a concrete version.`);
}
if (version.endsWith('-ea')) {
version = version.replace(/-ea$/, '');
stable = false;
}
else if (version.includes('-ea.')) {
// transform '11.0.3-ea.2' -> '11.0.3+2'
version = version.replace('-ea.', '+');
stable = false;
}
// Java uses a versioning scheme (JEP 322) that can contain more numeric
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
// exact versions to SemVer build notation ('18.0.1+1') so they are
// accepted. Ranges and versions that already carry build metadata are
// left untouched.
if (/^\d+(\.\d+){3,}$/.test(version)) {
version = (0,util/* convertVersionToSemver */.ZY)(version);
}
if (!semver_default().validRange(version)) {
throw new Error(`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`);
}
return {
version,
stable,
latest
};
}
createVersionNotFoundError(versionOrRange, availableVersions, additionalContext) {
const parts = [
`No matching version found for SemVer '${versionOrRange}'.`,
`Distribution: ${this.distribution}`,
`Package type: ${this.packageType}`,
`Architecture: ${this.architecture}`
];
// Add additional context if provided (e.g., platform/OS info)
if (additionalContext) {
parts.push(additionalContext);
}
if (availableVersions && availableVersions.length > 0) {
const maxVersionsToShow = core/* isDebug */._o() ? availableVersions.length : 50;
const versionsToShow = availableVersions.slice(0, maxVersionsToShow);
const truncated = availableVersions.length > maxVersionsToShow;
parts.push(`Available versions: ${versionsToShow.join(', ')}${truncated ? ', ...' : ''}`);
if (truncated) {
parts.push(`(showing first ${maxVersionsToShow} of ${availableVersions.length} versions, enable debug mode to see all)`);
}
}
const error = new Error(parts.join('\n'));
error.name = 'VersionNotFoundError';
return error;
}
setJavaDefault(version, toolPath) {
core/* exportVariable */.dN('JAVA_HOME', toolPath);
core/* addPath */.fM(external_path_default().join(toolPath, 'bin'));
this.setJavaEnvironment(version, toolPath);
}
setJavaEnvironment(version, toolPath) {
const majorVersion = version.split('.')[0];
core/* setOutput */.uH('distribution', this.distribution);
core/* setOutput */.uH('path', toolPath);
core/* setOutput */.uH('version', version);
core/* exportVariable */.dN(`JAVA_HOME_${majorVersion}_${this.architecture.toUpperCase()}`, toolPath);
}
distributionArchitecture() {
return this.architecture;
}
}
/***/ })
};
+206
View File
@@ -0,0 +1,206 @@
export const id = 282;
export const ids = [282];
export const modules = {
/***/ 2282:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ JetBrainsDistribution: () => (/* binding */ JetBrainsDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
/* harmony import */ var _actions_http_client__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(4942);
class JetBrainsDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
constructor(installerOptions) {
super('JetBrains', installerOptions);
}
async findPackageForDownload(range) {
const versionsRaw = await this.getAvailableVersions();
const versions = versionsRaw.map(v => {
const formattedVersion = `${v.semver}+${v.build}`;
return {
version: formattedVersion,
url: v.url
};
});
const satisfiedVersions = versions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(range, item.version))
.sort((a, b) => {
return -semver__WEBPACK_IMPORTED_MODULE_4___default().compareBuild(a.version, b.version);
});
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = versionsRaw.map(item => `${item.tag_name} (${item.semver}+${item.build})`);
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return {
...resolvedFullVersion,
// JetBrains' `.checksum` sibling doesn't disclose its algorithm via the
// filename, and older JBR builds (e.g. JBR 11) publish a SHA-256 digest
// there while newer builds publish SHA-512. Accept either, preferring
// the stronger SHA-512 when the digest length is ambiguous.
checksum: await this.fetchChecksum(`${resolvedFullVersion.url}.checksum`, ['sha512', 'sha256'])
};
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
const javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, 'tar.gz');
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_3___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async getAvailableVersions() {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for JBR took'); // eslint-disable-line no-console
}
// need to iterate through all pages to retrieve the list of all versions
// GitHub API doesn't provide way to retrieve the count of pages to iterate so infinity loop
let page_index = 1;
const rawVersions = [];
const bearerToken = process.env.GITHUB_TOKEN;
while (true) {
const requestArguments = `per_page=100&page=${page_index}`;
const requestHeaders = {};
if (bearerToken) {
requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
}
const rawUrl = `https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?${requestArguments}`;
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o() && page_index === 1) {
// url is identical except page_index so print it once for debug
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Gathering available versions from '${rawUrl}'`);
}
const paginationPageResult = (await this.http.getJson(rawUrl, requestHeaders)).result;
if (!paginationPageResult || paginationPageResult.length === 0) {
// break infinity loop because we have reached end of pagination
break;
}
const paginationPage = paginationPageResult.filter(version => this.stable ? !version.prerelease : version.prerelease);
if (!paginationPage || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break;
}
rawVersions.push(...paginationPage);
page_index++;
}
if (this.stable) {
// Add versions not available from the API but are downloadable
const hidden = ['11_0_10b1145.115', '11_0_11b1341.60'];
rawVersions.push(...hidden.map(tag => ({ tag_name: tag, name: tag, prerelease: false })));
}
const versions0 = rawVersions.map(async (v) => {
// Release tags look like one of these:
// jbr-release-21.0.3b465.3
// jbr17-b87.7
// jb11_0_11-b87.7
// jbr11_0_15b2043.56
// 11_0_11b1536.2
// 11_0_11-b1522
const tag = v.tag_name;
// Extract version string
const vstring = tag
.replace('jbr-release-', '')
.replace('jbr', '')
.replace('jb', '')
.replace('-', '');
const vsplit = vstring.split('b');
let semver = vsplit[0];
const build = vsplit[1];
// Normalize semver
if (!semver.includes('.') && !semver.includes('_'))
semver = `${semver}.0.0`;
// Construct URL
let type;
switch (this.packageType ?? '') {
case 'jre':
type = 'jbr';
break;
case 'jdk+jcef':
type = 'jbrsdk_jcef';
break;
case 'jre+jcef':
type = 'jbr_jcef';
break;
case 'jdk+ft':
type = 'jbrsdk_ft';
break;
case 'jre+ft':
type = 'jbr_ft';
break;
default:
type = 'jbrsdk';
break;
}
let url = `https://cache-redirector.jetbrains.com/intellij-jbr/${type}-${semver}-${platform}-${arch}-b${build}.tar.gz`;
let include = false;
const res = await this.http.head(url);
if (res.message.statusCode === _actions_http_client__WEBPACK_IMPORTED_MODULE_7__/* .HttpCodes */ .Hv.OK) {
include = true;
}
else {
url = `https://cache-redirector.jetbrains.com/intellij-jbr/${type}_nomod-${semver}-${platform}-${arch}-b${build}.tar.gz`;
const res2 = await this.http.head(url);
if (res2.message.statusCode === _actions_http_client__WEBPACK_IMPORTED_MODULE_7__/* .HttpCodes */ .Hv.OK) {
include = true;
}
}
const version = {
tag_name: tag,
semver: semver.replace(/_/g, '.'),
build: build,
url: url
};
return {
item: version,
include: include
};
});
const versions = await Promise.all(versions0).then(res => res.filter(item => item.include).map(item => item.item));
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .startGroup */ .Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for JBR took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Available versions: [${versions.length}]`);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(versions.map(item => item.semver).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .endGroup */ .N4();
}
return versions;
}
getPlatformOption() {
// Jetbrains has own platform names so need to map them
switch (process.platform) {
case 'darwin':
return 'osx';
case 'win32':
return 'windows';
default:
return process.platform;
}
}
}
/***/ })
};
+356
View File
@@ -0,0 +1,356 @@
export const id = 377;
export const ids = [377];
export const modules = {
/***/ 7377:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ restore: () => (/* binding */ restore)
/* harmony export */ });
/* unused harmony export save */
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(857);
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _actions_cache__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5767);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3838);
/* harmony import */ var _actions_glob__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2377);
/**
* @fileoverview this file provides methods handling dependency cache
*/
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 = [
{
id: 'maven',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.m2', 'repository')],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [
'**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
]
},
{
id: 'gradle',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.gradle', 'caches')],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [
'**/*.gradle*',
'**/gradle-wrapper.properties',
'buildSrc/**/Versions.kt',
'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml',
'**/versions.properties'
],
// The Gradle wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the Gradle
// caches. This keeps it available across the frequent *.gradle* changes
// that rotate the main cache key. See issue #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
]
},
{
id: 'sbt',
path: [
(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.ivy2', 'cache'),
(0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.sbt'),
getCoursierCachePath(),
// Some files should not be cached to avoid resolution problems.
// In particular the resolution of snapshots (ideological gap between maven/ivy).
'!' + (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.sbt', '*.lock'),
'!' + (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '**', 'ivydata-*.properties')
],
pattern: [
'**/*.sbt',
'**/project/build.properties',
'**/project/**.scala',
'**/project/**.sbt'
]
}
];
function getCoursierCachePath() {
if (os__WEBPACK_IMPORTED_MODULE_1___default().type() === 'Linux')
return (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), '.cache', 'coursier');
if (os__WEBPACK_IMPORTED_MODULE_1___default().type() === 'Darwin')
return (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), 'Library', 'Caches', 'Coursier');
return (0,path__WEBPACK_IMPORTED_MODULE_0__.join)(os__WEBPACK_IMPORTED_MODULE_1___default().homedir(), 'AppData', 'Local', 'Coursier', 'Cache');
}
function findPackageManager(id) {
const packageManager = supportedPackageManager.find(packageManager => packageManager.id === id);
if (packageManager === undefined) {
throw new Error(`unknown package manager specified: ${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.
*/
function additionalCachePrimaryKeyState(name) {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name) {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id, fileHash) {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/**
* A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
* @see {@link https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key|spec of cache key}
*/
async function computeCacheKey(packageManager, cacheDependencyPath) {
const pattern = cacheDependencyPath
? cacheDependencyPath.trim().split('\n')
: packageManager.pattern;
const fileHash = await _actions_glob__WEBPACK_IMPORTED_MODULE_4__/* .hashFiles */ .y(pattern.join('\n'));
if (!fileHash) {
throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`);
}
return buildCacheKey(packageManager.id, fileHash);
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(additionalCache) {
const fileHash = await _actions_glob__WEBPACK_IMPORTED_MODULE_4__/* .hashFiles */ .y(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
}
/**
* Restore the dependency cache
* @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, cachePaths = []) {
const packageManager = findPackageManager(id);
const resolvedCachePaths = resolveCachePaths(packageManager, cachePaths);
const [primaryKey, preparedAdditionalCaches] = await Promise.all([
computeCacheKey(packageManager, cacheDependencyPath),
prepareAdditionalCaches(packageManager.additionalCaches ?? [])
]);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`primary key is ${primaryKey}`);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .saveState */ .LZ(STATE_CACHE_PRIMARY_KEY, primaryKey);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .saveState */ .LZ(STATE_CACHE_PATHS, JSON.stringify(resolvedCachePaths));
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .setOutput */ .uH(STATE_CACHE_PRIMARY_KEY, primaryKey);
for (const preparedCache of preparedAdditionalCaches) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`${preparedCache.cache.name} primary key is ${preparedCache.primaryKey}`);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .saveState */ .LZ(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 _actions_cache__WEBPACK_IMPORTED_MODULE_2__/* .restoreCache */ .P3(cachePaths, primaryKey);
if (matchedKey) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .saveState */ .LZ(CACHE_MATCHED_KEY, matchedKey);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .setOutput */ .uH('cache-hit', matchedKey === primaryKey);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Cache restored from key: ${matchedKey}`);
}
else {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .setOutput */ .uH('cache-hit', false);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`${packageManager.id} cache is not found`);
}
}
/**
* Compute keys for additional caches (e.g. build-tool wrapper distributions).
* Additional caches without a matching configuration file are omitted.
*/
async function prepareAdditionalCaches(additionalCaches) {
const preparedCaches = await Promise.all(additionalCaches.map(async (additionalCache) => {
const primaryKey = await computeAdditionalCacheKey(additionalCache);
if (!primaryKey) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`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 _actions_cache__WEBPACK_IMPORTED_MODULE_2__/* .restoreCache */ .P3(additionalCache.path, primaryKey);
if (matchedKey) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .saveState */ .LZ(additionalCacheMatchedKeyState(additionalCache.name), matchedKey);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`${additionalCache.name} cache restored from key: ${matchedKey}`);
}
else {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`${additionalCache.name} cache is not found`);
}
}
/**
* Save the dependency cache
* @param id ID of the package manager, should be "maven" or "gradle"
*/
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 ?? []) {
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.');
return;
}
else if (matchedKey === primaryKey) {
// no change in target directories
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
return;
}
try {
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
// has already logged the reason at the appropriate severity, so just
// trace it instead of misreporting that the cache was saved.
core.debug(`Cache was not saved for the key: ${primaryKey}`);
return;
}
core.info(`Cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === cache.ReserveCacheError.name) {
core.info(err.message);
}
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.');
}
throw error;
}
}
}
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(packageManager, additionalCache) {
const primaryKey = core.getState(additionalCachePrimaryKeyState(additionalCache.name));
const matchedKey = core.getState(additionalCacheMatchedKeyState(additionalCache.name));
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
core.debug(`No primary key for the ${additionalCache.name} cache, not saving cache.`);
return;
}
else if (matchedKey === primaryKey) {
core.info(`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`);
return;
}
const globber = await glob.create(additionalCache.path.join('\n'), {
implicitDescendants: false
});
const cachePaths = await globber.glob();
if (cachePaths.length === 0) {
core.debug(`${additionalCache.name} cache paths do not exist, not saving cache.`);
return;
}
try {
const cacheId = await cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) {
core.debug(`${additionalCache.name} cache was not saved for the key: ${primaryKey}`);
return;
}
core.info(`${additionalCache.name} cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === cache.ValidationError.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
core.debug(`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`);
return;
}
if (err.name === cache.ReserveCacheError.name) {
core.info(err.message);
}
else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
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;
}
}
}
/**
* @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache
* @returns true if the given error seems related to the {@link https://github.com/actions/cache/issues/454|running Gradle Daemon issue}.
* @see {@link https://github.com/actions/cache/issues/454#issuecomment-840493935|why --no-daemon is necessary}
*/
function isProbablyGradleDaemonProblem(packageManager, error) {
if (packageManager.id !== 'gradle' ||
process.env['RUNNER_OS'] !== 'Windows') {
return false;
}
const message = error.message || '';
return message.startsWith('Tar failed with error: ');
}
/***/ })
};
+32
View File
@@ -0,0 +1,32 @@
export const id = 394;
export const ids = [394];
export const modules = {
/***/ 1394:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ isCacheFeatureAvailable: () => (/* binding */ isCacheFeatureAvailable)
/* harmony export */ });
/* harmony import */ var _actions_cache__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5767);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3838);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4527);
function isCacheFeatureAvailable() {
if (_actions_cache__WEBPACK_IMPORTED_MODULE_0__/* .isFeatureAvailable */ .w3()) {
return true;
}
if ((0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .isGhes */ .aT)()) {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .warning */ .$e('Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.');
return false;
}
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .warning */ .$e('The runner was not able to contact the cache service. Caching will be skipped');
return false;
}
/***/ })
};
+269
View File
@@ -0,0 +1,269 @@
export const id = 463;
export const ids = [463];
export const modules = {
/***/ 463:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
TemurinDistribution: () => (/* binding */ TemurinDistribution),
TemurinImplementation: () => (/* binding */ TemurinImplementation)
});
// UNUSED EXPORTS: ADOPTIUM_PUBLIC_KEY
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules
var core = __webpack_require__(3838);
// EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js + 2 modules
var tool_cache = __webpack_require__(9805);
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__(9896);
var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_);
// EXTERNAL MODULE: external "path"
var external_path_ = __webpack_require__(6928);
var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
// EXTERNAL MODULE: ./node_modules/semver/index.js
var semver = __webpack_require__(2088);
var semver_default = /*#__PURE__*/__webpack_require__.n(semver);
// EXTERNAL MODULE: ./src/gpg.ts
var gpg = __webpack_require__(8343);
;// CONCATENATED MODULE: ./src/distributions/temurin/adoptium-key.ts
// Adoptium GPG signing key (fingerprint: 3B04D753C9050D9A5D343F39843C48A565F8F04B)
// Retrieved from: https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3B04D753C9050D9A5D343F39843C48A565F8F04B
const ADOPTIUM_PUBLIC_KEY = `-----BEGIN PGP PUBLIC KEY BLOCK-----
xsBNBGGTvTQBCAC6ey144n7CG8foafF6mwgIBN1fIm1ILZDuGS4tMr0/XI8pgJnT
QvsPxZWEvtSm7bEMObzEoZJcXwjBcJl1B0ui8k5kHMTI75gCmZPsoKLFWIEpuRBQ
PBocusw80apDmLnNDQLVQvDFtEua5gaNa/fRw9YsmBoXBqvgrjFUIdGyWoQvH5+a
9OYlWD9n5VV0gnVMb+aclwVzB/zJw3kHGSgzuMtlAHeQiah7Y8yomQn/UIX8yqDf
+11sP3+c87YcjkRqImRTtmKEDcEtGPAIXC6SYA+uEEkbYE0Fy0chkvtnVWJ597fa
Epai4rnICU8zoJ6X5z3v1aM2WerhX9oq9X8PABEBAAHNQEFkb3B0aXVtIEdQRyBL
ZXkgKERFQi9SUE0gU2lnbmluZyBLZXkpIDx0ZW11cmluLWRldkBlY2xpcHNlLm9y
Zz7CwJIEEwEIADwWIQQ7BNdTyQUNml00PzmEPEilZfjwSwUCYZO9NAIbAwULCQgH
AgMiAgEGFQoJCAsCBBYCAwECHgcCF4AACgkQhDxIpWX48Et4AggAjjJzYWuKV3nG
7ngInngl8G/m9JoHr7BmwgcQXYhdy5hVkMcUx5JLeXz2LMBUH/F2nD595hgjMabk
kVib20X8lq9RsNbdfc2hBcWU6qyHKxsIqT4boI2/XDyEzzMyyZWWNGo/27Ci7Xmj
pWu31nh0pDdPqdyWDIKojbVVnxlCRY8as8Sm+1ufi709KCi4MuwHNsUlCSwb/fju
NKeHkrHbLcHKUUIEcmTSKRWrpMYBzm1HYOGBz4xPuELwUfUp71ehfoyBZlp6RDRf
l5TYI1FmCyHuvjNhrJgWv7bOTcf8yObGY+TEUhzc4xQqCrF4ur9d3opvsuPBQsv+
Klqi5KSZgs7ATQRhk700AQgAq14okly8cFrpYVenEQPiB75AUZfKRpMduiR6IxAj
SKcH7aSoFZ9AubUEBVpZsyT5svxoEPe1i4TdbF+m9FGy42EcOlLa3ArLTj5H8FRl
UdGZB9I5mk4GptOzPM+aHMMu92vW/ZwjuS8DvOiQSp+cUmG1EqOMJSM7e/4BM71z
E+OKaVJCj79pEzhG3SK/IC/OlxxyETT66NSfYJd7Sw5R6Vr19am/uNU690W0CJ+q
VQeFpmDMr7LnfdFRIh+lJe05+PvWXeidkGjox5cbG52wf8aRIR/FgkfcFvqRMN1f
B+dVOWueloUeVAnzcUznOKmUEs7LP9ObJhYHHgup4IAU2wARAQABwsB2BBgBCAAg
FiEEOwTXU8kFDZpdND85hDxIpWX48EsFAmGTvTQCGwwACgkQhDxIpWX48EvXHQf/
Q0nZsGDXnZHiBoojeSdpkO7WBjMIP3w1GdLvRpPQrS8TfOPbZuoevzCNh38Y3gwF
yelJspvzDQrBXhgkzAGlucYg8Y7KHa5Ebm7iDgMzc37L1hYSZTYCqwd7aowfgy34
hOk3B67LffkJpIh738Oa9CtlwxQ9xcytmBmQ1fBBOwm/9IhAwHPQuydYIs4DxWbj
0MGSP4fDntU7e4UjsHNmhudDcYol0FaqdHHIIB9C/G4CzetRwHFOn3b4JwXMU7YU
6aJA3mXhi3hggMC3wkT2HHZ/TquuOdNc02fypWOCDOHz0alBBJNqoVUNFNqU3tfJ
wI4qF/KKq9BfyfucAs0ykA==
=XLag
-----END PGP PUBLIC KEY BLOCK-----`;
// EXTERNAL MODULE: ./src/distributions/base-installer.ts + 2 modules
var base_installer = __webpack_require__(6242);
// EXTERNAL MODULE: ./src/constants.ts
var constants = __webpack_require__(7242);
// EXTERNAL MODULE: ./src/util.ts
var util = __webpack_require__(4527);
;// CONCATENATED MODULE: ./src/distributions/temurin/installer.ts
var TemurinImplementation;
(function (TemurinImplementation) {
TemurinImplementation["Hotspot"] = "Hotspot";
})(TemurinImplementation || (TemurinImplementation = {}));
class TemurinDistribution extends base_installer/* JavaBase */.O {
jvmImpl;
includeJmods;
constructor(installerOptions, jvmImpl) {
super(`Temurin-${jvmImpl}`, installerOptions);
this.jvmImpl = jvmImpl;
this.includeJmods = this.packageType === 'jdk+jmods';
}
/**
* @internal For cross-distribution reuse only. Not intended as a public API.
*/
async findPackageForDownload(version) {
return this.resolvePackage(version, this.includeJmods ? 'jdk' : this.packageType);
}
async resolvePackage(version, imageType) {
const availableVersionsRaw = await this.getAvailableVersions(imageType);
const availableVersionsWithBinaries = availableVersionsRaw
.filter(item => item.binaries.length > 0)
.map(item => {
// normalize 17.0.0-beta+33.0.202107301459 to 17.0.0+33.0.202107301459 for earlier access versions
const formattedVersion = this.stable
? item.version_data.semver
: item.version_data.semver.replace('-beta+', '+');
return {
version: formattedVersion,
url: item.binaries[0].package.link,
signatureUrl: item.binaries[0].package.signature_link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
};
});
const satisfiedVersions = availableVersionsWithBinaries
.filter(item => (0,util/* isVersionSatisfies */.y)(version, item.version))
.sort((a, b) => {
return -semver_default().compareBuild(a.version, b.version);
});
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
return resolvedFullVersion;
}
async downloadTool(javaRelease) {
core/* info */.pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadPackage(javaRelease);
core/* info */.pq(`Extracting Java archive...`);
const extension = (0,util/* getDownloadArchiveExtension */.ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,util/* renameWinArchive */.n2)(javaArchivePath);
}
const extractedJavaPath = await (0,util/* extractJdkFile */.PE)(javaArchivePath, extension);
const archiveName = external_fs_default().readdirSync(extractedJavaPath)[0];
const archivePath = external_path_default().join(extractedJavaPath, archiveName);
const javaHome = process.platform === 'darwin'
? external_path_default().join(archivePath, constants/* MACOS_JAVA_CONTENT_POSTFIX */.PG)
: archivePath;
if (this.includeJmods && !external_fs_default().existsSync(external_path_default().join(javaHome, 'jmods'))) {
await this.installJmods(javaRelease.version, javaHome);
}
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await tool_cache/* cacheDir */.e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
supportsSignatureVerification() {
return true;
}
async downloadPackage(release) {
const archivePath = await this.downloadAndVerify(release);
if (this.verifySignature) {
if (!release.signatureUrl) {
throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`);
}
core/* info */.pq(`Verifying Java package signature...`);
try {
await gpg/* verifyPackageSignature */.Yi(archivePath, release.signatureUrl, this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY);
}
catch (error) {
throw new Error(`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${error.message}`, { cause: error });
}
}
return archivePath;
}
async installJmods(version, javaHome) {
const jmodsRelease = await this.resolvePackage(version, 'jmods');
core/* info */.pq(`Downloading JMODs ${jmodsRelease.version} (${this.distribution}) from ${jmodsRelease.url} ...`);
let jmodsArchivePath = await this.downloadPackage(jmodsRelease);
if (process.platform === 'win32') {
jmodsArchivePath = (0,util/* renameWinArchive */.n2)(jmodsArchivePath);
}
const extractedJmodsPath = await (0,util/* extractJdkFile */.PE)(jmodsArchivePath, (0,util/* getDownloadArchiveExtension */.ag)());
const jmodsDirectory = external_path_default().join(extractedJmodsPath, external_fs_default().readdirSync(extractedJmodsPath)[0]);
external_fs_default().cpSync(jmodsDirectory, external_path_default().join(javaHome, 'jmods'), { recursive: true });
}
async getAvailableVersions(imageType = this.includeJmods ? 'jdk' : this.packageType) {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
const versionRange = encodeURI('[1.0,100.0]'); // retrieve all available versions
const releaseType = this.stable ? 'ga' : 'ea';
if (core/* isDebug */._o()) {
console.time('Retrieving available versions for Temurin took'); // eslint-disable-line no-console
}
const baseRequestArguments = [
`project=jdk`,
'vendor=adoptium',
`heap_size=normal`,
'sort_method=DEFAULT',
'sort_order=DESC',
`os=${platform}`,
`architecture=${arch}`,
`image_type=${imageType}`,
`release_type=${releaseType}`,
`jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&');
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
let availableVersionsUrl = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions = [];
let pageCount = 0;
if (core/* isDebug */._o()) {
core/* debug */.Yz(`Gathering available versions from '${availableVersionsUrl}'`);
}
while (availableVersionsUrl) {
pageCount++;
const response = await this.http.getJson(availableVersionsUrl);
const paginationPage = response.result;
const nextUrl = (0,util/* getNextPageUrlFromLinkHeader */.rC)(response.headers);
if (nextUrl &&
!(0,util/* validatePaginationUrl */.SA)(nextUrl, 'https://api.adoptium.net')) {
core/* warning */.$e(`Ignoring pagination link with unexpected origin: ${nextUrl}`);
availableVersionsUrl = null;
}
else {
availableVersionsUrl = nextUrl;
}
if (paginationPage === null || paginationPage.length === 0) {
break;
}
availableVersions.push(...paginationPage);
if (pageCount >= util/* MAX_PAGINATION_PAGES */.Tp) {
core/* warning */.$e(`Reached pagination safeguard limit (${util/* MAX_PAGINATION_PAGES */.Tp} pages) while listing Temurin releases.`);
break;
}
}
if (core/* isDebug */._o()) {
core/* startGroup */.Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Temurin took'); // eslint-disable-line no-console
core/* debug */.Yz(`Available versions: [${availableVersions.length}]`);
core/* debug */.Yz(availableVersions.map(item => item.version_data.semver).join(', '));
core/* endGroup */.N4();
}
return availableVersions;
}
getPlatformOption() {
// Adoptium has own platform names so need to map them
switch (process.platform) {
case 'darwin':
return 'mac';
case 'win32':
return 'windows';
case 'linux':
if (external_fs_default().existsSync('/etc/alpine-release')) {
return 'alpine-linux';
}
return 'linux';
default:
return process.platform;
}
}
distributionArchitecture() {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}
/***/ })
};
+146
View File
@@ -0,0 +1,146 @@
export const id = 524;
export const ids = [524];
export const modules = {
/***/ 8524:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ LibericaNikDistributions: () => (/* binding */ LibericaNikDistributions)
/* harmony export */ });
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6242);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4527);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_6__);
const supportedPlatform = `'linux', 'macos', 'windows'`;
const supportedArchitectures = `'x64', 'aarch64'`;
class LibericaNikDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_0__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Liberica_NIK', installerOptions);
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_5___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_6___default().join(extractedJavaPath, archiveName);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async findPackageForDownload(range) {
const availableVersionsRaw = await this.getAvailableVersions();
const availableVersions = availableVersionsRaw
.map(item => {
const jdkVersion = this.getJdkVersion(item);
return jdkVersion ? { url: item.downloadUrl, version: jdkVersion } : null;
})
.filter((item) => item !== null);
const satisfiedVersion = availableVersions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .isVersionSatisfies */ .y)(range, item.version))
.sort((a, b) => -semver__WEBPACK_IMPORTED_MODULE_1___default().compareBuild(a.version, b.version))[0];
if (!satisfiedVersion) {
const availableVersionStrings = availableVersions.map(item => item.version);
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return satisfiedVersion;
}
async getAvailableVersions() {
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for Liberica NIK took'); // eslint-disable-line no-console
}
const url = this.prepareAvailableVersionsUrl();
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`Gathering available versions from '${url}'`);
const availableVersions = (await this.http.getJson(url)).result ?? [];
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .startGroup */ .Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Liberica NIK took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`Available versions: [${availableVersions.length}]`);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(availableVersions.map(item => item.version).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .endGroup */ .N4();
}
return availableVersions;
}
prepareAvailableVersionsUrl() {
const urlOptions = {
os: this.getPlatformOption(),
'bundle-type': this.getBundleType(),
...this.getArchitectureOptions(),
'build-type': this.stable ? 'all' : 'ea',
'installation-type': 'archive',
fields: 'downloadUrl,version,components,component,embedded'
};
const searchParams = new URLSearchParams(urlOptions).toString();
return `https://api.bell-sw.com/v1/nik/releases?${searchParams}`;
}
// NIK's top-level `version` is the GraalVM/NIK version; the JDK version that
// users select on lives in the embedded `liberica` component.
getJdkVersion(release) {
const liberica = release.components?.find(component => component.component === 'liberica');
return liberica ? this.convertVersionToSemver(liberica.version) : null;
}
// The `full` bundle adds JavaFX/Swing GUI support; otherwise use `standard`.
getBundleType() {
const [, feature] = this.packageType.split('+');
return feature?.includes('fx') ? 'full' : 'standard';
}
getArchitectureOptions() {
const arch = this.distributionArchitecture();
switch (arch) {
case 'x64':
return { bitness: '64', arch: 'x86' };
case 'aarch64':
return { bitness: '64', arch: 'arm' };
default:
throw new Error(`Architecture '${this.architecture}' is not supported. Supported architectures: ${supportedArchitectures}`);
}
}
getPlatformOption(platform = process.platform) {
switch (platform) {
case 'darwin':
return 'macos';
case 'win32':
case 'cygwin':
return 'windows';
case 'linux':
return 'linux';
default:
throw new Error(`Platform '${platform}' is not supported. Supported platforms: ${supportedPlatform}`);
}
}
// JDK versions come as strings like '25.0.1+16', '23+38' or '11.0.15.1+2'.
// Normalize them to valid SemVer while preserving build metadata so newer
// NIK builds of the same JDK sort ahead of older ones.
convertVersionToSemver(jdkVersion) {
const [main, build] = jdkVersion.split('+');
const parts = main.split('.');
while (parts.length < 3) {
parts.push('0');
}
const base = parts.slice(0, 3).join('.');
const buildMeta = [...parts.slice(3), ...(build ? [build] : [])];
return buildMeta.length ? `${base}+${buildMeta.join('.')}` : base;
}
}
/***/ })
};
+192
View File
@@ -0,0 +1,192 @@
export const id = 557;
export const ids = [557];
export const modules = {
/***/ 7557:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ SapMachineDistribution: () => (/* binding */ SapMachineDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6242);
class SapMachineDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_6__/* .JavaBase */ .O {
constructor(installerOptions) {
super('SapMachine', installerOptions);
}
async findPackageForDownload(version) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Only stable versions: ${this.stable}`);
if (!['jdk', 'jre'].includes(this.packageType)) {
throw new Error('SapMachine provides only the `jdk` and `jre` package type');
}
const availableVersions = await this.getAvailableVersions();
const matchedVersions = availableVersions
.filter(item => {
return (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .isVersionSatisfies */ .y)(version, item.version);
})
.map(item => {
return {
version: item.version,
url: item.downloadLink
};
});
if (!matchedVersions.length) {
const availableVersionStrings = availableVersions.map(item => item.version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
const resolvedVersion = matchedVersions[0];
const checksumUrl = resolvedVersion.url.replace(/\.(?:tar\.gz|zip)$/, '.sha256.txt');
return {
...resolvedVersion,
checksum: await this.fetchChecksum(checksumUrl, 'sha256')
};
}
async getAvailableVersions() {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
let fetchedReleasesJson = await this.fetchReleasesFromUrl('https://sapmachine.io/assets/data/sapmachine-releases-all.json');
if (!fetchedReleasesJson) {
fetchedReleasesJson = await this.fetchReleasesFromUrl('https://sap.github.io/SapMachine/assets/data/sapmachine-releases-all.json');
}
if (!fetchedReleasesJson) {
throw new Error(`Couldn't fetch SapMachine versions information from both primary and backup urls`);
}
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz('Successfully fetched information about available SapMachine versions');
const availableVersions = this.parseVersions(platform, arch, fetchedReleasesJson);
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .startGroup */ .Oh('Print information about available versions');
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(availableVersions.map(item => item.version).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .endGroup */ .N4();
}
return availableVersions;
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_3___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_4___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
parseVersions(platform, arch, versions) {
const eligibleVersions = [];
for (const [, majorVersionMap] of Object.entries(versions)) {
for (const [, jdkVersionMap] of Object.entries(majorVersionMap.updates)) {
for (const [buildVersion, buildVersionMap] of Object.entries(jdkVersionMap)) {
let buildVersionWithoutPrefix = buildVersion.replace('sapmachine-', '');
if (!buildVersionWithoutPrefix.includes('.')) {
// replace major version with major.minor.patch and keep the remaining build identifier after the + as is with regex
buildVersionWithoutPrefix = buildVersionWithoutPrefix.replace(/(\d+)(\+.*)?/, '$1.0.0$2');
}
// replace + with . to convert to semver format if we have more than 3 version digits
if (buildVersionWithoutPrefix.split('.').length > 3) {
buildVersionWithoutPrefix = buildVersionWithoutPrefix.replace('+', '.');
}
buildVersionWithoutPrefix = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .convertVersionToSemver */ .ZY)(buildVersionWithoutPrefix);
// ignore invalid version
if (!semver__WEBPACK_IMPORTED_MODULE_2___default().valid(buildVersionWithoutPrefix)) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Invalid version: ${buildVersionWithoutPrefix}`);
continue;
}
// skip earlyAccessVersions if stable version requested
if (this.stable && buildVersionMap.ea === 'true') {
continue;
}
for (const [edition, editionAssets] of Object.entries(buildVersionMap.assets)) {
if (this.packageType !== edition) {
continue;
}
for (const [archAndPlatForm, archAssets] of Object.entries(editionAssets)) {
let expectedArchAndPlatform = `${platform}-${arch}`;
if (platform === 'linux-musl') {
expectedArchAndPlatform = `linux-${arch}-musl`;
}
if (archAndPlatForm !== expectedArchAndPlatform) {
continue;
}
for (const [contentType, contentTypeAssets] of Object.entries(archAssets)) {
// skip if not tar.gz and zip files
if (contentType !== 'tar.gz' && contentType !== 'zip') {
continue;
}
eligibleVersions.push({
os: platform,
architecture: arch,
version: buildVersionWithoutPrefix,
checksum: contentTypeAssets.checksum,
downloadLink: contentTypeAssets.url,
packageType: edition
});
}
}
}
}
}
}
const sortedVersions = this.sortParsedVersions(eligibleVersions);
return sortedVersions;
}
// Sorts versions in descending order as by default data in JSON isn't sorted
sortParsedVersions(eligibleVersions) {
const sortedVersions = eligibleVersions.sort((versionObj1, versionObj2) => {
const version1 = versionObj1.version;
const version2 = versionObj2.version;
return semver__WEBPACK_IMPORTED_MODULE_2___default().compareBuild(version1, version2);
});
return sortedVersions.reverse();
}
getPlatformOption() {
switch (process.platform) {
case 'win32':
return 'windows';
case 'darwin':
return 'macos';
case 'linux':
// figure out if alpine/musl
if (fs__WEBPACK_IMPORTED_MODULE_3___default().existsSync('/etc/alpine-release')) {
return 'linux-musl';
}
return 'linux';
default:
return process.platform;
}
}
async fetchReleasesFromUrl(url, headers = {}) {
try {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Trying to fetch available SapMachine versions info from the primary url: ${url}`);
const releases = (await this.http.getJson(url, headers)).result;
return releases;
}
catch (err) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Fetching SapMachine versions info from the link: ${url} ended up with the error: ${err.message}`);
return null;
}
}
}
/***/ })
};
+152
View File
@@ -0,0 +1,152 @@
export const id = 63;
export const ids = [63];
export const modules = {
/***/ 2063:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ LibericaDistributions: () => (/* binding */ LibericaDistributions)
/* harmony export */ });
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6242);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4527);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_6__);
const supportedPlatform = `'linux', 'linux-musl', 'macos', 'solaris', 'windows'`;
const supportedArchitectures = `'x86', 'x64', 'armv7', 'aarch64', 'ppc64le'`;
class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_0__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Liberica', installerOptions);
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_5___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_6___default().join(extractedJavaPath, archiveName);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async findPackageForDownload(range) {
const availableVersionsRaw = await this.getAvailableVersions();
const availableVersions = availableVersionsRaw.map(item => ({
url: item.downloadUrl,
version: this.convertVersionToSemver(item)
}));
const satisfiedVersion = availableVersions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .isVersionSatisfies */ .y)(range, item.version))
.sort((a, b) => -semver__WEBPACK_IMPORTED_MODULE_1___default().compareBuild(a.version, b.version))[0];
if (!satisfiedVersion) {
const availableVersionStrings = availableVersions.map(item => item.version);
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return satisfiedVersion;
}
async getAvailableVersions() {
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for Liberica took'); // eslint-disable-line no-console
}
const url = this.prepareAvailableVersionsUrl();
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`Gathering available versions from '${url}'`);
const availableVersions = (await this.http.getJson(url)).result ?? [];
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .startGroup */ .Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Liberica took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`Available versions: [${availableVersions.length}]`);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(availableVersions.map(item => item.version).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .endGroup */ .N4();
}
return availableVersions;
}
prepareAvailableVersionsUrl() {
const urlOptions = {
os: this.getPlatformOption(),
'bundle-type': this.getBundleType(),
...this.getArchitectureOptions(),
'build-type': this.stable ? 'all' : 'ea',
'installation-type': 'archive',
fields: 'downloadUrl,version,featureVersion,interimVersion,updateVersion,buildVersion'
};
const searchParams = new URLSearchParams(urlOptions).toString();
return `https://api.bell-sw.com/v1/liberica/releases?${searchParams}`;
}
getBundleType() {
const [bundleType, feature] = this.packageType.split('+');
if (feature?.includes('fx')) {
return bundleType + '-full';
}
return bundleType;
}
getArchitectureOptions() {
const arch = this.distributionArchitecture();
switch (arch) {
case 'x86':
return { bitness: '32', arch: 'x86' };
case 'x64':
return { bitness: '64', arch: 'x86' };
case 'armv7':
return { bitness: '32', arch: 'arm' };
case 'aarch64':
return { bitness: '64', arch: 'arm' };
case 'ppc64le':
return { bitness: '64', arch: 'ppc' };
default:
throw new Error(`Architecture '${this.architecture}' is not supported. Supported architectures: ${supportedArchitectures}`);
}
}
getPlatformOption(platform = process.platform) {
switch (platform) {
case 'darwin':
return 'macos';
case 'win32':
case 'cygwin':
return 'windows';
case 'linux':
return 'linux';
case 'sunos':
return 'solaris';
default:
throw new Error(`Platform '${platform}' is not supported. Supported platforms: ${supportedPlatform}`);
}
}
convertVersionToSemver(version) {
const { buildVersion, featureVersion, interimVersion, updateVersion } = version;
const mainVersion = [featureVersion, interimVersion, updateVersion].join('.');
if (buildVersion != 0) {
return `${mainVersion}+${buildVersion}`;
}
return mainVersion;
}
distributionArchitecture() {
const arch = super.distributionArchitecture();
switch (arch) {
case 'arm':
return 'armv7';
default:
return arch;
}
}
}
/***/ })
};
+188
View File
@@ -0,0 +1,188 @@
export const id = 675;
export const ids = [675];
export const modules = {
/***/ 7675:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ DragonwellDistribution: () => (/* binding */ DragonwellDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
class DragonwellDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Dragonwell', installerOptions);
}
async findPackageForDownload(version) {
if (!this.stable) {
throw new Error('Early access versions are not supported by Dragonwell');
}
if (this.packageType !== 'jdk') {
throw new Error('Dragonwell provides only the `jdk` package type');
}
const availableVersions = await this.getAvailableVersions();
const matchedVersions = availableVersions
.filter(item => {
return (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.jdk_version);
})
.map(item => {
return {
version: item.jdk_version,
url: item.download_link,
checksum: item.checksum
? {
algorithm: 'sha256',
value: item.checksum
}
: undefined
};
});
if (!matchedVersions.length) {
const availableVersionStrings = availableVersions.map(item => item.jdk_version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
const resolvedVersion = matchedVersions[0];
return resolvedVersion;
}
async getAvailableVersions() {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
let fetchedDragonwellJson = await this.fetchJsonFromPrimaryUrl();
if (!fetchedDragonwellJson) {
fetchedDragonwellJson = await this.fetchJsonFromBackupUrl();
}
if (!fetchedDragonwellJson) {
throw new Error(`Couldn't fetch Dragonwell versions information from both primary and backup urls`);
}
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz('Successfully fetched information about available Dragonwell versions');
const availableVersions = this.parseVersions(platform, arch, fetchedDragonwellJson);
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .startGroup */ .Oh('Print information about available versions');
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(availableVersions.map(item => item.jdk_version).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .endGroup */ .N4();
}
return availableVersions;
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_3___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_4___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
parseVersions(platform, arch, dragonwellVersions) {
const eligibleVersions = [];
for (const majorVersion in dragonwellVersions) {
const majorVersionMap = dragonwellVersions[majorVersion];
for (let jdkVersion in majorVersionMap) {
const jdkVersionMap = majorVersionMap[jdkVersion];
if (!(platform in jdkVersionMap)) {
continue;
}
const platformMap = jdkVersionMap[platform];
if (!(arch in platformMap)) {
continue;
}
const archMap = platformMap[arch];
if (jdkVersion === 'latest') {
continue;
}
// Some version of Dragonwell JDK are numerated with help of non-semver notation (more then 3 digits).
// Common practice is to transform excess digits to the so-called semver build part, which is prefixed with the plus sign, to be able to operate with them using semver tools.
const jdkVersionNums = jdkVersion
.replace('+', '.')
.split('.');
jdkVersion = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(`${jdkVersionNums.slice(0, 3).join('.')}.${jdkVersionNums[jdkVersionNums.length - 1]}`);
for (const edition in archMap) {
eligibleVersions.push({
os: platform,
architecture: arch,
jdk_version: jdkVersion,
checksum: archMap[edition].sha256 ?? '',
download_link: archMap[edition].download_url,
edition: edition,
image_type: 'jdk'
});
break; // Get the first available link to the JDK. In most cases it should point to the Extended version of JDK, in rare cases like with v17 it points to the Standard version (the only available).
}
}
}
const sortedVersions = this.sortParsedVersions(eligibleVersions);
return sortedVersions;
}
// Sorts versions in descending order as by default data in JSON isn't sorted
sortParsedVersions(eligibleVersions) {
const sortedVersions = eligibleVersions.sort((versionObj1, versionObj2) => {
const version1 = versionObj1.jdk_version;
const version2 = versionObj2.jdk_version;
return semver__WEBPACK_IMPORTED_MODULE_2___default().compareBuild(version1, version2);
});
return sortedVersions.reverse();
}
getPlatformOption() {
switch (process.platform) {
case 'win32':
return 'windows';
default:
return process.platform;
}
}
async fetchJsonFromPrimaryUrl() {
const primaryUrl = 'https://dragonwell-jdk.io/map_with_checksum.json';
try {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Trying to fetch available Dragonwell versions info from the primary url: ${primaryUrl}`);
const fetchedDragonwellJson = (await this.http.getJson(primaryUrl)).result;
return fetchedDragonwellJson;
}
catch (err) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Fetching Dragonwell versions info from the primary link: ${primaryUrl} ended up with the error: ${err.message}`);
return null;
}
}
async fetchJsonFromBackupUrl() {
const owner = 'dragonwell-releng';
const repository = 'dragonwell-setup-java';
const branch = 'main';
const filePath = 'releases.json';
const backupUrl = `https://api.github.com/repos/${owner}/${repository}/contents/${filePath}?ref=${branch}`;
const headers = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getGitHubHttpHeaders */ .U_)();
try {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Trying to fetch available Dragonwell versions info from the backup url: ${backupUrl}`);
const fetchedDragonwellJson = (await this.http.getJson(backupUrl, headers)).result;
return fetchedDragonwellJson;
}
catch (err) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Fetching Dragonwell versions info from the backup url: ${backupUrl} ended up with the error: ${err.message}`);
return null;
}
}
}
/***/ })
};
+130
View File
@@ -0,0 +1,130 @@
export const id = 735;
export const ids = [735];
export const modules = {
/***/ 3735:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ OpenJdkDistribution: () => (/* binding */ OpenJdkDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
const OPENJDK_BASE_URL = 'https://jdk.java.net';
class OpenJdkDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Oracle OpenJDK', installerOptions);
}
async findPackageForDownload(range) {
if (this.packageType !== 'jdk') {
throw new Error('Oracle OpenJDK provides only the `jdk` package type');
}
const arch = this.distributionArchitecture();
if (!['x64', 'aarch64'].includes(arch)) {
throw new Error(`Unsupported architecture: ${this.architecture}`);
}
const platform = this.getPlatform();
const releases = await this.getAvailableVersions(platform, arch);
const matchingReleases = releases
.filter(release => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(range, release.version))
.sort((left, right) => -semver__WEBPACK_IMPORTED_MODULE_4___default().compareBuild(left.version, right.version));
if (!matchingReleases.length) {
throw this.createVersionNotFoundError(range, releases.map(release => release.version), `Platform: ${platform}`);
}
const release = matchingReleases[0];
return {
...release,
checksum: await this.fetchChecksum(`${release.url}.sha256`, 'sha256')
};
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = javaRelease.url.endsWith('.zip') ? 'zip' : 'tar.gz';
if (extension === 'zip') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_3___default().join(extractedJavaPath, archiveName);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async getAvailableVersions(platform, arch) {
const homePage = await this.fetchPage(`${OPENJDK_BASE_URL}/`);
const releasePageUrls = Array.from(homePage.matchAll(/href="\/(\d+)\/">JDK\s+\d+/g), match => `${OPENJDK_BASE_URL}/${match[1]}/`);
const pages = await Promise.all(releasePageUrls.map(url => this.fetchPage(url)));
if (this.stable) {
pages.push(await this.fetchPage(`${OPENJDK_BASE_URL}/archive/`));
}
const releases = pages.flatMap(page => this.parseReleases(page, platform, arch));
return releases.filter(release => release.url.includes('/early_access/') !== this.stable);
}
async fetchPage(url) {
const response = await this.http.get(url);
return response.readBody();
}
parseReleases(html, platform, arch) {
const platformPattern = platform === 'macos' ? '(?:macos|osx)' : platform;
const extensionPattern = platform === 'windows' ? '(?:zip|tar\\.gz)' : 'tar\\.gz';
const pattern = new RegExp(`href="(https://download\\.java\\.net/[^"]+/openjdk-([^"_]+)_${platformPattern}-${arch}_bin\\.${extensionPattern})"`, 'g');
return Array.from(html.matchAll(pattern), match => {
const url = match[1];
const build = url.match(/\/(\d+)\/(?:GPL\/)?openjdk-/)?.[1] ??
this.findBuildInArchiveHeading(html, match.index, match[2]);
return {
version: this.toSemver(match[2], build),
url
};
});
}
findBuildInArchiveHeading(html, assetIndex, version) {
const headings = Array.from(html.slice(0, assetIndex).matchAll(/\(build\s+([^)]+)\)/g));
const headingVersion = headings.at(-1)?.[1];
if (!headingVersion) {
return undefined;
}
const [javaVersion, build] = headingVersion.split('+');
return javaVersion === version ? build : undefined;
}
toSemver(version, urlBuild) {
const [javaVersion, filenameBuild] = version.replace('-ea', '').split('+');
const versionParts = javaVersion.split('.');
const normalizedVersion = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(versionParts.length === 1 ? `${javaVersion}.0.0` : javaVersion);
const build = filenameBuild ?? (versionParts.length <= 3 ? urlBuild : undefined);
return build ? `${normalizedVersion}+${build}` : normalizedVersion;
}
getPlatform(platform = process.platform) {
switch (platform) {
case 'darwin':
return 'macos';
case 'linux':
return 'linux';
case 'win32':
return 'windows';
default:
throw new Error(`Platform '${platform}' is not supported. Supported platforms: 'linux', 'macos', 'windows'`);
}
}
}
/***/ })
};
+62535
View File
File diff suppressed because it is too large Load Diff
+471
View File
@@ -0,0 +1,471 @@
export const id = 874;
export const ids = [874,463];
export const modules = {
/***/ 7874:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ AdoptDistribution: () => (/* binding */ AdoptDistribution),
/* harmony export */ AdoptImplementation: () => (/* binding */ AdoptImplementation)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
/* harmony import */ var _temurin_installer_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(463);
var AdoptImplementation;
(function (AdoptImplementation) {
AdoptImplementation["Hotspot"] = "Hotspot";
AdoptImplementation["OpenJ9"] = "OpenJ9";
})(AdoptImplementation || (AdoptImplementation = {}));
class AdoptDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
jvmImpl;
temurinDistribution;
constructor(installerOptions, jvmImpl, temurinDistribution = null) {
super(`Adopt-${jvmImpl}`, installerOptions);
this.jvmImpl = jvmImpl;
if (temurinDistribution !== null &&
jvmImpl !== AdoptImplementation.Hotspot) {
throw new Error('Only Hotspot JVM is supported by Temurin.');
}
// Only use the temurin repo for Hotspot JVMs
this.temurinDistribution =
temurinDistribution ??
(jvmImpl === AdoptImplementation.Hotspot
? new _temurin_installer_js__WEBPACK_IMPORTED_MODULE_7__.TemurinDistribution(installerOptions, _temurin_installer_js__WEBPACK_IMPORTED_MODULE_7__.TemurinImplementation.Hotspot)
: null);
}
async findPackageForDownload(version) {
if (this.jvmImpl === AdoptImplementation.Hotspot) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .notice */ .lm("AdoptOpenJDK has moved to Eclipse Temurin https://github.com/actions/setup-java#supported-distributions please consider changing to the 'temurin' distribution type in your setup-java configuration.");
}
if (this.jvmImpl === AdoptImplementation.Hotspot &&
this.temurinDistribution !== null) {
try {
return await this.temurinDistribution.findPackageForDownload(version);
}
catch (error) {
// Log the failure but always fall back to legacy AdoptOpenJDK for resilience
const errorMessage = error instanceof Error ? error.message : String(error);
if (error instanceof Error && error.name === 'VersionNotFoundError') {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .notice */ .lm('The JVM you are looking for could not be found in the Temurin repository, this likely indicates ' +
'that you are using an out of date version of Java, consider updating and moving to using the Temurin distribution type in setup-java.');
}
else {
// Log other errors for debugging but gracefully fall back
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Temurin lookup failed: ${errorMessage}. Falling back to AdoptOpenJDK API.`);
}
}
}
// failed to find a Temurin version, so fall back to AdoptOpenJDK
return this.findPackageForDownloadOldAdoptOpenJdk(version);
}
async findPackageForDownloadOldAdoptOpenJdk(version) {
const availableVersionsRaw = await this.getAvailableVersions();
const availableVersionsWithBinaries = availableVersionsRaw
.filter(item => item.binaries.length > 0)
.map(item => {
return {
version: item.version_data.semver,
url: item.binaries[0].package.link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
};
});
const satisfiedVersions = availableVersionsWithBinaries
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.version))
.sort((a, b) => {
return -semver__WEBPACK_IMPORTED_MODULE_4___default().compareBuild(a.version, b.version);
});
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
return resolvedFullVersion;
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_3___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
get toolcacheFolderName() {
if (this.jvmImpl === AdoptImplementation.Hotspot) {
// exclude Hotspot postfix from distribution name because Hosted runners have pre-cached Adopt OpenJDK under "Java_Adopt_jdk"
// for more information see: https://github.com/actions/setup-java/pull/155#discussion_r610451063
return `Java_Adopt_${this.packageType}`;
}
return super.toolcacheFolderName;
}
async getAvailableVersions() {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
const imageType = this.packageType;
const versionRange = encodeURI('[1.0,100.0]'); // retrieve all available versions
const releaseType = this.stable ? 'ga' : 'ea';
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for Adopt took'); // eslint-disable-line no-console
}
const baseRequestArguments = [
`project=jdk`,
'vendor=adoptopenjdk',
`heap_size=normal`,
'sort_method=DEFAULT',
'sort_order=DESC',
`os=${platform}`,
`architecture=${arch}`,
`image_type=${imageType}`,
`release_type=${releaseType}`,
`jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&');
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
let availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions = [];
let pageCount = 0;
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Gathering available versions from '${availableVersionsUrl}'`);
}
while (availableVersionsUrl) {
pageCount++;
const response = await this.http.getJson(availableVersionsUrl);
const paginationPage = response.result;
const nextUrl = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getNextPageUrlFromLinkHeader */ .rC)(response.headers);
if (nextUrl &&
!(0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .validatePaginationUrl */ .SA)(nextUrl, 'https://api.adoptopenjdk.net')) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .warning */ .$e(`Ignoring pagination link with unexpected origin: ${nextUrl}`);
availableVersionsUrl = null;
}
else {
availableVersionsUrl = nextUrl;
}
if (paginationPage === null || paginationPage.length === 0) {
break;
}
availableVersions.push(...paginationPage);
if (pageCount >= _util_js__WEBPACK_IMPORTED_MODULE_6__/* .MAX_PAGINATION_PAGES */ .Tp) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .warning */ .$e(`Reached pagination safeguard limit (${_util_js__WEBPACK_IMPORTED_MODULE_6__/* .MAX_PAGINATION_PAGES */ .Tp} pages) while listing Adopt releases.`);
break;
}
}
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .startGroup */ .Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Adopt took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Available versions: [${availableVersions.length}]`);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(availableVersions.map(item => item.version_data.semver).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .endGroup */ .N4();
}
return availableVersions;
}
getPlatformOption() {
// Adopt has own platform names so need to map them
switch (process.platform) {
case 'darwin':
return 'mac';
case 'win32':
return 'windows';
default:
return process.platform;
}
}
distributionArchitecture() {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}
/***/ }),
/***/ 463:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
TemurinDistribution: () => (/* binding */ TemurinDistribution),
TemurinImplementation: () => (/* binding */ TemurinImplementation)
});
// UNUSED EXPORTS: ADOPTIUM_PUBLIC_KEY
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules
var core = __webpack_require__(3838);
// EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js + 2 modules
var tool_cache = __webpack_require__(9805);
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__(9896);
var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_);
// EXTERNAL MODULE: external "path"
var external_path_ = __webpack_require__(6928);
var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
// EXTERNAL MODULE: ./node_modules/semver/index.js
var semver = __webpack_require__(2088);
var semver_default = /*#__PURE__*/__webpack_require__.n(semver);
// EXTERNAL MODULE: ./src/gpg.ts
var gpg = __webpack_require__(8343);
;// CONCATENATED MODULE: ./src/distributions/temurin/adoptium-key.ts
// Adoptium GPG signing key (fingerprint: 3B04D753C9050D9A5D343F39843C48A565F8F04B)
// Retrieved from: https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3B04D753C9050D9A5D343F39843C48A565F8F04B
const ADOPTIUM_PUBLIC_KEY = `-----BEGIN PGP PUBLIC KEY BLOCK-----
xsBNBGGTvTQBCAC6ey144n7CG8foafF6mwgIBN1fIm1ILZDuGS4tMr0/XI8pgJnT
QvsPxZWEvtSm7bEMObzEoZJcXwjBcJl1B0ui8k5kHMTI75gCmZPsoKLFWIEpuRBQ
PBocusw80apDmLnNDQLVQvDFtEua5gaNa/fRw9YsmBoXBqvgrjFUIdGyWoQvH5+a
9OYlWD9n5VV0gnVMb+aclwVzB/zJw3kHGSgzuMtlAHeQiah7Y8yomQn/UIX8yqDf
+11sP3+c87YcjkRqImRTtmKEDcEtGPAIXC6SYA+uEEkbYE0Fy0chkvtnVWJ597fa
Epai4rnICU8zoJ6X5z3v1aM2WerhX9oq9X8PABEBAAHNQEFkb3B0aXVtIEdQRyBL
ZXkgKERFQi9SUE0gU2lnbmluZyBLZXkpIDx0ZW11cmluLWRldkBlY2xpcHNlLm9y
Zz7CwJIEEwEIADwWIQQ7BNdTyQUNml00PzmEPEilZfjwSwUCYZO9NAIbAwULCQgH
AgMiAgEGFQoJCAsCBBYCAwECHgcCF4AACgkQhDxIpWX48Et4AggAjjJzYWuKV3nG
7ngInngl8G/m9JoHr7BmwgcQXYhdy5hVkMcUx5JLeXz2LMBUH/F2nD595hgjMabk
kVib20X8lq9RsNbdfc2hBcWU6qyHKxsIqT4boI2/XDyEzzMyyZWWNGo/27Ci7Xmj
pWu31nh0pDdPqdyWDIKojbVVnxlCRY8as8Sm+1ufi709KCi4MuwHNsUlCSwb/fju
NKeHkrHbLcHKUUIEcmTSKRWrpMYBzm1HYOGBz4xPuELwUfUp71ehfoyBZlp6RDRf
l5TYI1FmCyHuvjNhrJgWv7bOTcf8yObGY+TEUhzc4xQqCrF4ur9d3opvsuPBQsv+
Klqi5KSZgs7ATQRhk700AQgAq14okly8cFrpYVenEQPiB75AUZfKRpMduiR6IxAj
SKcH7aSoFZ9AubUEBVpZsyT5svxoEPe1i4TdbF+m9FGy42EcOlLa3ArLTj5H8FRl
UdGZB9I5mk4GptOzPM+aHMMu92vW/ZwjuS8DvOiQSp+cUmG1EqOMJSM7e/4BM71z
E+OKaVJCj79pEzhG3SK/IC/OlxxyETT66NSfYJd7Sw5R6Vr19am/uNU690W0CJ+q
VQeFpmDMr7LnfdFRIh+lJe05+PvWXeidkGjox5cbG52wf8aRIR/FgkfcFvqRMN1f
B+dVOWueloUeVAnzcUznOKmUEs7LP9ObJhYHHgup4IAU2wARAQABwsB2BBgBCAAg
FiEEOwTXU8kFDZpdND85hDxIpWX48EsFAmGTvTQCGwwACgkQhDxIpWX48EvXHQf/
Q0nZsGDXnZHiBoojeSdpkO7WBjMIP3w1GdLvRpPQrS8TfOPbZuoevzCNh38Y3gwF
yelJspvzDQrBXhgkzAGlucYg8Y7KHa5Ebm7iDgMzc37L1hYSZTYCqwd7aowfgy34
hOk3B67LffkJpIh738Oa9CtlwxQ9xcytmBmQ1fBBOwm/9IhAwHPQuydYIs4DxWbj
0MGSP4fDntU7e4UjsHNmhudDcYol0FaqdHHIIB9C/G4CzetRwHFOn3b4JwXMU7YU
6aJA3mXhi3hggMC3wkT2HHZ/TquuOdNc02fypWOCDOHz0alBBJNqoVUNFNqU3tfJ
wI4qF/KKq9BfyfucAs0ykA==
=XLag
-----END PGP PUBLIC KEY BLOCK-----`;
// EXTERNAL MODULE: ./src/distributions/base-installer.ts + 2 modules
var base_installer = __webpack_require__(6242);
// EXTERNAL MODULE: ./src/constants.ts
var constants = __webpack_require__(7242);
// EXTERNAL MODULE: ./src/util.ts
var util = __webpack_require__(4527);
;// CONCATENATED MODULE: ./src/distributions/temurin/installer.ts
var TemurinImplementation;
(function (TemurinImplementation) {
TemurinImplementation["Hotspot"] = "Hotspot";
})(TemurinImplementation || (TemurinImplementation = {}));
class TemurinDistribution extends base_installer/* JavaBase */.O {
jvmImpl;
includeJmods;
constructor(installerOptions, jvmImpl) {
super(`Temurin-${jvmImpl}`, installerOptions);
this.jvmImpl = jvmImpl;
this.includeJmods = this.packageType === 'jdk+jmods';
}
/**
* @internal For cross-distribution reuse only. Not intended as a public API.
*/
async findPackageForDownload(version) {
return this.resolvePackage(version, this.includeJmods ? 'jdk' : this.packageType);
}
async resolvePackage(version, imageType) {
const availableVersionsRaw = await this.getAvailableVersions(imageType);
const availableVersionsWithBinaries = availableVersionsRaw
.filter(item => item.binaries.length > 0)
.map(item => {
// normalize 17.0.0-beta+33.0.202107301459 to 17.0.0+33.0.202107301459 for earlier access versions
const formattedVersion = this.stable
? item.version_data.semver
: item.version_data.semver.replace('-beta+', '+');
return {
version: formattedVersion,
url: item.binaries[0].package.link,
signatureUrl: item.binaries[0].package.signature_link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
};
});
const satisfiedVersions = availableVersionsWithBinaries
.filter(item => (0,util/* isVersionSatisfies */.y)(version, item.version))
.sort((a, b) => {
return -semver_default().compareBuild(a.version, b.version);
});
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
return resolvedFullVersion;
}
async downloadTool(javaRelease) {
core/* info */.pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadPackage(javaRelease);
core/* info */.pq(`Extracting Java archive...`);
const extension = (0,util/* getDownloadArchiveExtension */.ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,util/* renameWinArchive */.n2)(javaArchivePath);
}
const extractedJavaPath = await (0,util/* extractJdkFile */.PE)(javaArchivePath, extension);
const archiveName = external_fs_default().readdirSync(extractedJavaPath)[0];
const archivePath = external_path_default().join(extractedJavaPath, archiveName);
const javaHome = process.platform === 'darwin'
? external_path_default().join(archivePath, constants/* MACOS_JAVA_CONTENT_POSTFIX */.PG)
: archivePath;
if (this.includeJmods && !external_fs_default().existsSync(external_path_default().join(javaHome, 'jmods'))) {
await this.installJmods(javaRelease.version, javaHome);
}
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await tool_cache/* cacheDir */.e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
supportsSignatureVerification() {
return true;
}
async downloadPackage(release) {
const archivePath = await this.downloadAndVerify(release);
if (this.verifySignature) {
if (!release.signatureUrl) {
throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`);
}
core/* info */.pq(`Verifying Java package signature...`);
try {
await gpg/* verifyPackageSignature */.Yi(archivePath, release.signatureUrl, this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY);
}
catch (error) {
throw new Error(`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${error.message}`, { cause: error });
}
}
return archivePath;
}
async installJmods(version, javaHome) {
const jmodsRelease = await this.resolvePackage(version, 'jmods');
core/* info */.pq(`Downloading JMODs ${jmodsRelease.version} (${this.distribution}) from ${jmodsRelease.url} ...`);
let jmodsArchivePath = await this.downloadPackage(jmodsRelease);
if (process.platform === 'win32') {
jmodsArchivePath = (0,util/* renameWinArchive */.n2)(jmodsArchivePath);
}
const extractedJmodsPath = await (0,util/* extractJdkFile */.PE)(jmodsArchivePath, (0,util/* getDownloadArchiveExtension */.ag)());
const jmodsDirectory = external_path_default().join(extractedJmodsPath, external_fs_default().readdirSync(extractedJmodsPath)[0]);
external_fs_default().cpSync(jmodsDirectory, external_path_default().join(javaHome, 'jmods'), { recursive: true });
}
async getAvailableVersions(imageType = this.includeJmods ? 'jdk' : this.packageType) {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
const versionRange = encodeURI('[1.0,100.0]'); // retrieve all available versions
const releaseType = this.stable ? 'ga' : 'ea';
if (core/* isDebug */._o()) {
console.time('Retrieving available versions for Temurin took'); // eslint-disable-line no-console
}
const baseRequestArguments = [
`project=jdk`,
'vendor=adoptium',
`heap_size=normal`,
'sort_method=DEFAULT',
'sort_order=DESC',
`os=${platform}`,
`architecture=${arch}`,
`image_type=${imageType}`,
`release_type=${releaseType}`,
`jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&');
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
let availableVersionsUrl = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions = [];
let pageCount = 0;
if (core/* isDebug */._o()) {
core/* debug */.Yz(`Gathering available versions from '${availableVersionsUrl}'`);
}
while (availableVersionsUrl) {
pageCount++;
const response = await this.http.getJson(availableVersionsUrl);
const paginationPage = response.result;
const nextUrl = (0,util/* getNextPageUrlFromLinkHeader */.rC)(response.headers);
if (nextUrl &&
!(0,util/* validatePaginationUrl */.SA)(nextUrl, 'https://api.adoptium.net')) {
core/* warning */.$e(`Ignoring pagination link with unexpected origin: ${nextUrl}`);
availableVersionsUrl = null;
}
else {
availableVersionsUrl = nextUrl;
}
if (paginationPage === null || paginationPage.length === 0) {
break;
}
availableVersions.push(...paginationPage);
if (pageCount >= util/* MAX_PAGINATION_PAGES */.Tp) {
core/* warning */.$e(`Reached pagination safeguard limit (${util/* MAX_PAGINATION_PAGES */.Tp} pages) while listing Temurin releases.`);
break;
}
}
if (core/* isDebug */._o()) {
core/* startGroup */.Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Temurin took'); // eslint-disable-line no-console
core/* debug */.Yz(`Available versions: [${availableVersions.length}]`);
core/* debug */.Yz(availableVersions.map(item => item.version_data.semver).join(', '));
core/* endGroup */.N4();
}
return availableVersions;
}
getPlatformOption() {
// Adoptium has own platform names so need to map them
switch (process.platform) {
case 'darwin':
return 'mac';
case 'win32':
return 'windows';
case 'linux':
if (external_fs_default().existsSync('/etc/alpine-release')) {
return 'alpine-linux';
}
return 'linux';
default:
return process.platform;
}
}
distributionArchitecture() {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}
/***/ })
};
+177
View File
@@ -0,0 +1,177 @@
export const id = 939;
export const ids = [939];
export const modules = {
/***/ 9939:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ SemeruDistribution: () => (/* binding */ SemeruDistribution)
/* harmony export */ });
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6242);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4527);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_6__);
const supportedArchitectures = [
'x64',
'x86',
'ppc64le',
'ppc64',
's390x',
'aarch64'
];
class SemeruDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_0__/* .JavaBase */ .O {
constructor(installerOptions) {
super('IBM_Semeru', installerOptions);
}
async findPackageForDownload(version) {
const arch = this.distributionArchitecture();
if (!supportedArchitectures.includes(arch)) {
throw new Error(`Unsupported architecture for IBM Semeru: ${this.architecture} for your current OS version, the following are supported: ${supportedArchitectures.join(', ')}`);
}
if (!this.stable) {
throw new Error('IBM Semeru does not provide builds for early access versions');
}
if (this.packageType !== 'jdk' && this.packageType !== 'jre') {
throw new Error('IBM Semeru only provide `jdk` and `jre` package types');
}
const availableVersionsRaw = await this.getAvailableVersions();
const availableVersionsWithBinaries = availableVersionsRaw
.filter(item => item.binaries.length > 0)
.map(item => {
// normalize 17.0.0-beta+33.0.202107301459 to 17.0.0+33.0.202107301459 for earlier access versions
const formattedVersion = this.stable
? item.version_data.semver
: item.version_data.semver.replace('-beta+', '+');
return {
version: formattedVersion,
url: item.binaries[0].package.link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
};
});
const satisfiedVersions = availableVersionsWithBinaries
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .isVersionSatisfies */ .y)(version, item.version))
.sort((a, b) => {
return -semver__WEBPACK_IMPORTED_MODULE_1___default().compareBuild(a.version, b.version);
});
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version);
// Include platform context to help users understand OS-specific version availability
// IBM Semeru builds are OS-specific, so platform info aids in troubleshooting
const platformContext = `Platform: ${process.platform}`;
throw this.createVersionNotFoundError(version, availableVersionStrings, platformContext);
}
return resolvedFullVersion;
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_5___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_6___default().join(extractedJavaPath, archiveName);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
get toolcacheFolderName() {
return super.toolcacheFolderName;
}
async getAvailableVersions() {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
const imageType = this.packageType;
const versionRange = encodeURI('[1.0,100.0]'); // retrieve all available versions
const releaseType = this.stable ? 'ga' : 'ea';
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for Semeru took'); // eslint-disable-line no-console
}
const baseRequestArguments = [
`project=jdk`,
'vendor=ibm',
`heap_size=normal`,
'sort_method=DEFAULT',
'sort_order=DESC',
`os=${platform}`,
`architecture=${arch}`,
`image_type=${imageType}`,
`release_type=${releaseType}`,
`jvm_impl=openj9`
].join('&');
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
let availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions = [];
let pageCount = 0;
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`Gathering available versions from '${availableVersionsUrl}'`);
}
while (availableVersionsUrl) {
pageCount++;
const response = await this.http.getJson(availableVersionsUrl);
const paginationPage = response.result;
const nextUrl = (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .getNextPageUrlFromLinkHeader */ .rC)(response.headers);
if (nextUrl &&
!(0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .validatePaginationUrl */ .SA)(nextUrl, 'https://api.adoptopenjdk.net')) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .warning */ .$e(`Ignoring pagination link with unexpected origin: ${nextUrl}`);
availableVersionsUrl = null;
}
else {
availableVersionsUrl = nextUrl;
}
if (paginationPage === null || paginationPage.length === 0) {
break;
}
availableVersions.push(...paginationPage);
if (pageCount >= _util_js__WEBPACK_IMPORTED_MODULE_2__/* .MAX_PAGINATION_PAGES */ .Tp) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .warning */ .$e(`Reached pagination safeguard limit (${_util_js__WEBPACK_IMPORTED_MODULE_2__/* .MAX_PAGINATION_PAGES */ .Tp} pages) while listing Semeru releases.`);
break;
}
}
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .startGroup */ .Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Semeru took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(`Available versions: [${availableVersions.length}]`);
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .debug */ .Yz(availableVersions.map(item => item.version_data.semver).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .endGroup */ .N4();
}
return availableVersions;
}
getPlatformOption() {
// Adopt has own platform names so need to map them
switch (process.platform) {
case 'darwin':
return 'mac';
case 'win32':
return 'windows';
default:
return process.platform;
}
}
}
/***/ })
};
+341
View File
@@ -0,0 +1,341 @@
export const id = 968;
export const ids = [968];
export const modules = {
/***/ 6968:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ GraalVMCommunityDistribution: () => (/* binding */ GraalVMCommunityDistribution),
/* harmony export */ GraalVMDistribution: () => (/* binding */ GraalVMDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
/* harmony import */ var _actions_http_client__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4942);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(4527);
const GRAALVM_DL_BASE = 'https://download.oracle.com/graalvm';
const GRAALVM_DOWNLOAD_URL = 'https://www.graalvm.org/downloads/';
const GRAALVM_COMMUNITY_RELEASES_URL = 'https://api.github.com/repos/graalvm/graalvm-ce-builds/releases?per_page=100';
const GRAALVM_COMMUNITY_RELEASES_PAGE_ORIGIN = 'https://api.github.com';
const GRAALVM_COMMUNITY_DOWNLOAD_URL = 'https://github.com/graalvm/graalvm-ce-builds/releases';
const GRAALVM_COMMUNITY_ASSET_PREFIX = 'graalvm-community-jdk-';
const GRAALVM_COMMUNITY_VERSION_PATTERN = /^\d+(?:\.\d+)*$/;
const IS_WINDOWS = process.platform === 'win32';
const GRAALVM_PLATFORM = IS_WINDOWS ? 'windows' : process.platform;
const GRAALVM_MIN_VERSION = 17;
const SUPPORTED_ARCHITECTURES = ['x64', 'aarch64'];
class GraalVMDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
constructor(installerOptions, distributionName = 'GraalVM') {
super(distributionName, installerOptions);
}
async downloadTool(javaRelease) {
try {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .getDownloadArchiveExtension */ .ag)();
if (IS_WINDOWS) {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
// Add validation for extracted path
if (!fs__WEBPACK_IMPORTED_MODULE_2___default().existsSync(extractedJavaPath)) {
throw new Error(`Extraction failed: path ${extractedJavaPath} does not exist`);
}
const dirContents = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath);
if (dirContents.length === 0) {
throw new Error('Extraction failed: no files found in extracted directory');
}
const archivePath = path__WEBPACK_IMPORTED_MODULE_3___default().join(extractedJavaPath, dirContents[0]);
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, version, this.architecture);
return { version: javaRelease.version, path: javaPath };
}
catch (error) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .error */ .z3(`Failed to download and extract GraalVM: ${error}`);
throw error;
}
}
setJavaDefault(version, toolPath) {
super.setJavaDefault(version, toolPath);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .exportVariable */ .dN('GRAALVM_HOME', toolPath);
}
async findPackageForDownload(range) {
this.validateVersionRange(range);
const arch = this.getSupportedArchitecture();
if (!this.stable) {
return this.findEABuildDownloadUrl(`${range}-ea`);
}
// The `latest` alias is normalized to the SemVer wildcard. Oracle GraalVM
// builds its download URLs from a concrete major and has no endpoint to list
// releases, so resolve the newest available GA major from the Adoptium API.
if (this.latest) {
range = (await (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .getLatestMajorVersion */ .ri)(this.http)).toString();
}
const { platform, extension, major } = this.validateStableBuildRequest(range);
const fileUrl = this.constructFileUrl(range, major, platform, arch, extension);
const response = await this.http.head(fileUrl);
this.handleHttpResponse(response, range);
return {
url: fileUrl,
version: range,
checksum: await this.fetchChecksum(`${fileUrl}.sha256`, 'sha256')
};
}
validateVersionRange(range) {
if (!range || typeof range !== 'string') {
throw new Error('Version range is required and must be a string');
}
}
getSupportedArchitecture() {
const arch = this.distributionArchitecture();
if (!SUPPORTED_ARCHITECTURES.includes(arch)) {
throw new Error(`Unsupported architecture: ${this.architecture}. Supported architectures are: ${SUPPORTED_ARCHITECTURES.join(', ')}`);
}
return arch;
}
validateStableBuildRequest(range) {
if (this.packageType !== 'jdk') {
throw new Error(`${this.distribution} provides only the \`jdk\` package type`);
}
const platform = this.getPlatform();
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .getDownloadArchiveExtension */ .ag)();
const major = range.includes('.') ? range.split('.')[0] : range;
const majorVersion = parseInt(major);
if (isNaN(majorVersion)) {
throw new Error(`Invalid version format: ${range}`);
}
if (majorVersion < GRAALVM_MIN_VERSION) {
throw new Error(`${this.distribution} is only supported for JDK ${GRAALVM_MIN_VERSION} and later. Requested version: ${major}`);
}
return {
platform,
major,
extension
};
}
constructFileUrl(range, major, platform, arch, extension) {
return range.includes('.')
? `${GRAALVM_DL_BASE}/${major}/archive/graalvm-jdk-${range}_${platform}-${arch}_bin.${extension}`
: `${GRAALVM_DL_BASE}/${range}/latest/graalvm-jdk-${range}_${platform}-${arch}_bin.${extension}`;
}
handleHttpResponse(response, range) {
const statusCode = response.message.statusCode;
if (statusCode === _actions_http_client__WEBPACK_IMPORTED_MODULE_6__/* .HttpCodes */ .Hv.NotFound) {
// Create the standard error with additional hint about checking the download URL
const error = this.createVersionNotFoundError(range);
if (this.latest) {
error.message += `\nThe latest Java major version (${range}) is not yet available for the ${this.distribution} distribution. Please specify a concrete version instead of 'latest'.`;
}
error.message += `\nPlease check if this version is available at ${GRAALVM_DOWNLOAD_URL} . Pick a version from the list.`;
throw error;
}
if (statusCode === _actions_http_client__WEBPACK_IMPORTED_MODULE_6__/* .HttpCodes */ .Hv.Unauthorized ||
statusCode === _actions_http_client__WEBPACK_IMPORTED_MODULE_6__/* .HttpCodes */ .Hv.Forbidden) {
throw new Error(`Access denied when downloading GraalVM. Status code: ${statusCode}. Please check your credentials or permissions.`);
}
if (statusCode !== _actions_http_client__WEBPACK_IMPORTED_MODULE_6__/* .HttpCodes */ .Hv.OK) {
throw new Error(`HTTP request for GraalVM failed with status code: ${statusCode} (${response.message.statusMessage || 'Unknown error'})`);
}
}
async findEABuildDownloadUrl(javaEaVersion) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Searching for EA build: ${javaEaVersion}`);
const versions = await this.fetchEAJson(javaEaVersion);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Found ${versions.length} EA versions`);
const latestVersion = versions.find(v => v.latest);
if (!latestVersion) {
const availableVersions = versions.map(v => v.version);
throw this.createVersionNotFoundError(javaEaVersion, availableVersions, 'Note: No EA build is marked as latest for this version.');
}
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Latest version found: ${latestVersion.version}`);
const arch = this.distributionArchitecture();
const file = latestVersion.files.find(f => f.arch === arch && f.platform === GRAALVM_PLATFORM);
if (!file) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .error */ .z3(`Available files for architecture ${arch}: ${JSON.stringify(latestVersion.files)}`);
throw new Error(`Unable to find file for architecture '${arch}' and platform '${GRAALVM_PLATFORM}'`);
}
if (!file.filename.startsWith('graalvm-jdk-')) {
throw new Error(`Invalid filename format: ${file.filename}. Expected to start with 'graalvm-jdk-'`);
}
const downloadUrl = `${latestVersion.download_base_url}${file.filename}`;
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Download URL: ${downloadUrl}`);
return {
url: downloadUrl,
version: latestVersion.version,
checksum: await this.fetchChecksum(`${downloadUrl}.sha256`, 'sha256')
};
}
async fetchEAJson(javaEaVersion) {
const url = `https://api.github.com/repos/graalvm/oracle-graalvm-ea-builds/contents/versions/${javaEaVersion}.json?ref=main`;
const headers = (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .getGitHubHttpHeaders */ .U_)();
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Trying to fetch available version info for GraalVM EA builds from '${url}'`);
try {
const response = await this.http.getJson(url, headers);
if (!response.result) {
throw new Error(`No GraalVM EA build found for version '${javaEaVersion}'. Please check if the version is correct.`);
}
return response.result;
}
catch (error) {
if (error instanceof Error) {
// Check if it's a 404 error (file not found)
if (error.message?.includes('404')) {
throw new Error(`GraalVM EA version '${javaEaVersion}' not found. Please verify the version exists in the EA builds repository.`, { cause: error });
}
// Re-throw with more context
throw new Error(`Failed to fetch GraalVM EA version information for '${javaEaVersion}': ${error.message}`, { cause: error });
}
// If it's not an Error instance, throw a generic error
throw new Error(`Failed to fetch GraalVM EA version information for '${javaEaVersion}'`, { cause: error });
}
}
getPlatform(platform = process.platform) {
const platformMap = {
darwin: 'macos',
win32: 'windows',
linux: 'linux'
};
const result = platformMap[platform];
if (!result) {
throw new Error(`Platform '${platform}' is not supported. Supported platforms: 'linux', 'macos', 'windows'`);
}
return result;
}
}
class GraalVMCommunityDistribution extends GraalVMDistribution {
constructor(installerOptions) {
super(installerOptions, 'GraalVM Community');
}
get toolcacheFolderName() {
return `Java_GraalVM_Community_${this.packageType}`;
}
async findPackageForDownload(range) {
this.validateVersionRange(range);
if (!this.stable) {
throw new Error('GraalVM Community does not provide early access builds');
}
const arch = this.getSupportedArchitecture();
// GraalVM Community publishes its releases on GitHub, so the `latest` alias
// (normalized to the SemVer wildcard `x`) can float to the newest GA it
// actually ships. Unlike Oracle GraalVM (which has no listing endpoint and
// must derive the newest major from the Adoptium API), we match against the
// real release list here, so `latest` never fails when GraalVM lags behind a
// brand-new Java major.
let platform;
let extension;
if (this.latest) {
if (this.packageType !== 'jdk') {
throw new Error(`${this.distribution} provides only the \`jdk\` package type`);
}
platform = this.getPlatform();
extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .getDownloadArchiveExtension */ .ag)();
}
else {
({ platform, extension } = this.validateStableBuildRequest(range));
}
// GraalVM Community asset names embed the platform, architecture and
// archive type, e.g. `graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz`.
const assetSuffix = `_${platform}-${arch}_bin.${extension}`;
const availableVersions = await this.getAvailableVersions(assetSuffix);
const satisfiedVersion = availableVersions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .isVersionSatisfies */ .y)(range, item.version))
.sort((a, b) => -semver__WEBPACK_IMPORTED_MODULE_4___default().compareBuild(a.version, b.version))[0];
if (!satisfiedVersion) {
const error = this.createVersionNotFoundError(range, availableVersions.map(item => item.version), `Platform: ${platform}`);
error.message += `\nPlease check if this version is available at ${GRAALVM_COMMUNITY_DOWNLOAD_URL}.`;
throw error;
}
return satisfiedVersion;
}
async getAvailableVersions(assetSuffix) {
const headers = (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .getGitHubHttpHeaders */ .U_)();
const versions = new Map();
let releasesUrl = GRAALVM_COMMUNITY_RELEASES_URL;
for (let pageIndex = 0; releasesUrl && pageIndex < _util_js__WEBPACK_IMPORTED_MODULE_7__/* .MAX_PAGINATION_PAGES */ .Tp; pageIndex++) {
const response = await this.http.getJson(releasesUrl, headers);
// A successful GitHub releases listing is always a JSON array (possibly
// empty). Anything else indicates an unexpected/error payload (rate
// limiting, auth failure, etc.) that must be surfaced instead of being
// silently treated as "no releases", which would later look like a
// misleading "version not found" error.
if (!Array.isArray(response.result)) {
throw new Error(`Unexpected response while listing GraalVM Community releases from ${releasesUrl} ` +
`(HTTP status code: ${response.statusCode}). Expected a JSON array of releases. ` +
`Please check if the service is available at ${GRAALVM_COMMUNITY_DOWNLOAD_URL}.`);
}
const releases = response.result;
if (releases.length === 0) {
break;
}
for (const release of releases) {
if (release.draft || release.prerelease) {
continue;
}
for (const asset of release.assets ?? []) {
const version = this.extractAssetVersion(asset.name, assetSuffix);
if (version) {
const digest = asset.digest?.match(/^sha256:([a-f0-9]{64})$/i)?.[1];
if (!digest) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`No authoritative sha256 digest is available for ${asset.name}; skipping checksum verification for this asset.`);
}
versions.set(version, {
version,
url: asset.browser_download_url,
checksum: digest
? {
algorithm: 'sha256',
value: digest,
source: GRAALVM_COMMUNITY_RELEASES_URL
}
: undefined
});
}
}
}
releasesUrl = this.getNextReleasesUrl(response.headers);
}
return [...versions.values()];
}
// Returns the GraalVM JDK version encoded in a release asset name when it
// matches the requested platform/architecture/archive suffix, otherwise null.
extractAssetVersion(assetName, assetSuffix) {
if (!assetName.startsWith(GRAALVM_COMMUNITY_ASSET_PREFIX) ||
!assetName.endsWith(assetSuffix)) {
return null;
}
const rawVersion = assetName.slice(GRAALVM_COMMUNITY_ASSET_PREFIX.length, -assetSuffix.length);
if (!GRAALVM_COMMUNITY_VERSION_PATTERN.test(rawVersion)) {
return null;
}
return (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .convertVersionToSemver */ .ZY)(rawVersion);
}
getNextReleasesUrl(headers) {
const nextUrl = (0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .getNextPageUrlFromLinkHeader */ .rC)(headers);
if (nextUrl &&
!(0,_util_js__WEBPACK_IMPORTED_MODULE_7__/* .validatePaginationUrl */ .SA)(nextUrl, GRAALVM_COMMUNITY_RELEASES_PAGE_ORIGIN)) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .warning */ .$e(`Ignoring pagination link with unexpected origin: ${nextUrl}`);
return null;
}
return nextUrl;
}
}
/***/ })
};
+197
View File
@@ -0,0 +1,197 @@
export const id = 978;
export const ids = [978];
export const modules = {
/***/ 9597:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ ZuluDistribution: () => (/* binding */ ZuluDistribution)
/* harmony export */ });
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3838);
/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9805);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_5__/* .JavaBase */ .O {
constructor(installerOptions) {
super('Zulu', installerOptions);
}
async findPackageForDownload(version) {
const availableVersionsRaw = await this.getAvailableVersions();
const availableVersions = availableVersionsRaw.map(item => {
// The Azul Metadata API reports the JDK build number separately from
// java_version (e.g. java_version=[17,0,7], openjdk_build_number=7).
// Append it so the resulting semver retains the build (e.g. 17.0.7+7).
const javaVersion = item.openjdk_build_number != null
? [...item.java_version, item.openjdk_build_number]
: item.java_version;
return {
version: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(javaVersion),
url: item.download_url,
zuluVersion: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(item.distro_version),
packageUuid: item.package_uuid
};
});
const satisfiedVersions = availableVersions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.version))
.sort((a, b) => {
// Azul provides two versions: java_version and distro_version
// we should sort by both fields by descending
return (-semver__WEBPACK_IMPORTED_MODULE_4___default().compareBuild(a.version, b.version) ||
-semver__WEBPACK_IMPORTED_MODULE_4___default().compareBuild(a.zuluVersion, b.zuluVersion));
})
.map((item) => ({
version: item.version,
url: item.url,
packageUuid: item.packageUuid
}));
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = availableVersions.map(item => item.version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
const packageDetailsUrl = `https://api.azul.com/metadata/v1/zulu/packages/${resolvedFullVersion.packageUuid}`;
const packageDetails = (await this.http.getJson(packageDetailsUrl)).result;
const digest = packageDetails?.sha256_hash?.match(/^[a-f0-9]{64}$/i)?.[0];
if (!digest) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`No authoritative sha256 checksum is available for Zulu version ${resolvedFullVersion.version} from ${packageDetailsUrl}; skipping checksum verification.`);
}
return {
version: resolvedFullVersion.version,
url: resolvedFullVersion.url,
checksum: digest
? {
algorithm: 'sha256',
value: digest,
source: packageDetailsUrl
}
: undefined
};
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath);
}
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_3___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_2___default().join(extractedJavaPath, archiveName);
const javaPath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_1__/* .cacheDir */ .e8(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async getAvailableVersions() {
const arch = this.getArchitectureOptions();
const [bundleType, features] = this.packageType.split('+');
const platform = this.getPlatformOption();
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)();
const javafx = features?.includes('fx') ?? false;
const crac = features?.includes('crac') ?? false;
const releaseStatus = this.stable ? 'ga' : 'ea';
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
console.time('Retrieving available versions for Zulu took'); // eslint-disable-line no-console
}
const baseRequestArguments = [
`os=${platform}`,
`archive_type=${extension}`,
`java_package_type=${bundleType}`,
`javafx_bundled=${javafx}`,
`crac_supported=${crac}`,
`arch=${arch}`,
`release_status=${releaseStatus}`,
`availability_types=ca`
].join('&');
// Need to iterate through all pages to retrieve the list of all versions.
// The Azul API doesn't return a total page count, so paginate until a page
// comes back empty (or short), guarding against a runaway loop with a cap.
const pageSize = 100;
const maxPages = 100;
let pageIndex = 1;
const availableVersions = [];
while (pageIndex <= maxPages) {
const requestArguments = `${baseRequestArguments}&page=${pageIndex}&page_size=${pageSize}`;
const availableVersionsUrl = `https://api.azul.com/metadata/v1/zulu/packages/?${requestArguments}`;
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o() && pageIndex === 1) {
// the url is identical except for the page number, so print it once for debug
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Gathering available versions from '${availableVersionsUrl}'`);
}
const paginationPage = (await this.http.getJson(availableVersionsUrl)).result;
if (!paginationPage || paginationPage.length === 0) {
// stop paginating because we have reached the end of the results
break;
}
availableVersions.push(...paginationPage);
if (paginationPage.length < pageSize) {
// a short page means this was the last one; avoid an extra empty request
break;
}
pageIndex++;
}
if (pageIndex > maxPages) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .warning */ .$e(`Reached the maximum of ${maxPages} pages while listing Zulu versions; results may be truncated.`);
}
if (_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .isDebug */ ._o()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .startGroup */ .Oh('Print information about available versions');
console.timeEnd('Retrieving available versions for Zulu took'); // eslint-disable-line no-console
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(`Available versions: [${availableVersions.length}]`);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .debug */ .Yz(availableVersions.map(item => item.java_version.join('.')).join(', '));
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .endGroup */ .N4();
}
return availableVersions;
}
getArchitectureOptions() {
const arch = this.distributionArchitecture();
switch (arch) {
case 'x64':
return 'x64';
case 'x86':
// The Azul Metadata API's "x86" value returns both 32-bit (i686) and
// 64-bit (x64) packages, which are indistinguishable by version and
// 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';
default:
return arch;
}
}
getPlatformOption() {
// Azul has own platform names so need to map them
switch (process.platform) {
case 'darwin':
return 'macos';
case 'win32':
return 'windows';
case 'linux':
// The new Metadata API's "linux" value returns both glibc and musl packages;
// use "linux_glibc" to target only glibc, which is what standard runners use.
return 'linux_glibc';
default:
return process.platform;
}
}
}
/***/ })
};
+3275 -67877
View File
File diff suppressed because it is too large Load Diff
+38 -8
View File
@@ -321,12 +321,10 @@ The package types have these meanings:
| `graalvm-community` | `jdk` | Stable GraalVM Community releases for JDK 17 and later only. |
| `jetbrains` | `jdk`, `jre`, `jdk+jcef`, `jre+jcef`, `jdk+ft`, `jre+ft` | JetBrains publishes selected LTS-based releases rather than every OpenJDK patch. JDK/JRE and JCEF bundles start with the Java 11 release family; FreeType bundles start with Java 17. Exact package, LTS family, patch, OS, and architecture availability is determined from release assets. |
| `kona` | `jdk` | Stable Java 8, 11, 17, 21, and 25 releases only. |
| `jdkfile` | `jdk` (recommended) | The package contents and version are supplied by `jdk-file`; `setup-java` does not validate them. `java-package` only separates the local archive's tool-cache entry, so use `jdk` unless separate cache namespaces are required. |
| `jdkfile` | `jdk` | The package contents and version are supplied by `jdk-file`; `setup-java` validates the package type but does not inspect the archive contents. |
Values outside this table are unsupported even when a distribution forwards the
value to its vendor API instead of rejecting it immediately. In that case, the
action normally fails with a version-not-found error because no matching
artifact exists.
Values outside this table are unsupported. The action rejects them before
checking the tool cache or requesting a vendor catalog.
```yaml
steps:
@@ -486,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
@@ -892,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.
@@ -980,7 +1010,7 @@ steps:
- run: java --version
```
In case you install multiple versions of Java at once you can use the same syntax as used in `java-versions`. Please note that you have to declare an ID for all Java versions that will be installed or the `mvn-toolchain-id` instruction will be skipped wholesale due to mapping ambiguities.
When installing multiple Java versions, use the same multiline syntax as `java-version`. You must declare exactly one ID for every Java version that will be installed. The action fails before installing a JDK unless the number of `mvn-toolchain-id` entries matches the number of `java-version` entries, or is exactly one when `java-version-file` is used.
```yaml
steps:
+2 -2
View File
@@ -9,7 +9,7 @@
"node": ">=24.0.0"
},
"scripts": {
"build": "ncc build -o dist/setup src/setup-java.ts && ncc build -o dist/cleanup src/cleanup-java.ts",
"build": "node scripts/patch-is-unsafe.mjs && ncc build -o dist/setup src/setup-java.ts && ncc build -o dist/cleanup src/cleanup-java.ts",
"format": "prettier --no-error-on-unmatched-pattern --write \"**/*.{ts,yml,yaml}\"",
"format-check": "prettier --no-error-on-unmatched-pattern --check \"**/*.{ts,yml,yaml}\"",
"lint": "eslint \"**/*.ts\"",
@@ -18,7 +18,7 @@
"fix": "npm run format && npm run lint:fix && npm run build",
"prepare": "husky install",
"prerelease": "npm run-script build",
"release": "git add -f dist/setup/index.js dist/cleanup/index.js",
"release": "git add -f dist/setup/*.js dist/setup/package.json dist/cleanup/index.js",
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand --coverage"
},
"lint-staged": {
+20
View File
@@ -0,0 +1,20 @@
import {readFile, writeFile} from 'node:fs/promises';
const sourcePath = new URL('../node_modules/is-unsafe/src/contexts/xml.js', import.meta.url);
const vulnerablePattern = 'pattern: /-->/,';
const safePattern = 'pattern: /--!?>/,';
const source = await readFile(sourcePath, 'utf8');
// CodeQL treats this XML detector as an incomplete HTML comment-end filter.
if (source.includes(safePattern)) {
process.exit(0);
}
const occurrences = source.split(vulnerablePattern).length - 1;
if (occurrences !== 1) {
throw new Error(
`Expected one ${JSON.stringify(vulnerablePattern)} in ${sourcePath.pathname}, found ${occurrences}`
);
}
await writeFile(sourcePath, source.replace(vulnerablePattern, safePattern));
+21
View File
@@ -0,0 +1,21 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import {isGhes} from './util.js';
export function isCacheFeatureAvailable(): boolean {
if (cache.isFeatureAvailable()) {
return true;
}
if (isGhes()) {
core.warning(
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.'
);
return false;
}
core.warning(
'The runner was not able to contact the cache service. Caching will be skipped'
);
return false;
}
+107 -27
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;
+83
View File
@@ -0,0 +1,83 @@
import {createHash, timingSafeEqual} from 'crypto';
import {createReadStream} from 'fs';
import {pipeline} from 'stream/promises';
import {ChecksumMetadata} from './distributions/base-models.js';
export interface ChecksumVerificationContext {
distribution: string;
version: string;
}
function sanitizedSource(source: string | undefined): string {
if (!source) {
return '';
}
try {
const url = new URL(source);
return ` from ${url.origin}${url.pathname}`;
} catch {
return ' from an invalid checksum source';
}
}
// Length, in hex characters, of a digest produced by each supported algorithm.
// Exported so callers (e.g. fetchChecksum) can infer which algorithm a vendor
// actually used when it doesn't disclose it via the checksum URL/filename.
export function expectedDigestLength(
algorithm: ChecksumMetadata['algorithm']
): number {
return algorithm === 'sha256' ? 64 : algorithm === 'sha512' ? 128 : 0;
}
function normalizeExpectedDigest(checksum: ChecksumMetadata): string {
const algorithm = checksum.algorithm;
const digest =
typeof checksum.value === 'string'
? checksum.value.trim().toLowerCase()
: '';
const expectedLength = expectedDigestLength(algorithm);
if (expectedLength === 0) {
throw new Error(
`Unsupported checksum algorithm '${String(algorithm)}'${sanitizedSource(checksum.source)}. Supported algorithms are sha256 and sha512.`
);
}
if (!new RegExp(`^[a-f0-9]{${expectedLength}}$`).test(digest)) {
throw new Error(
`Malformed ${algorithm} checksum metadata${sanitizedSource(checksum.source)}: expected a ${expectedLength}-character hexadecimal digest.`
);
}
return digest;
}
export async function calculateChecksum(
filePath: string,
algorithm: ChecksumMetadata['algorithm']
): Promise<string> {
const hash = createHash(algorithm);
await pipeline(createReadStream(filePath), hash);
return hash.digest('hex');
}
export async function verifyChecksum(
filePath: string,
checksum: ChecksumMetadata,
context: ChecksumVerificationContext
): Promise<void> {
const expected = normalizeExpectedDigest(checksum);
const actual = await calculateChecksum(filePath, checksum.algorithm);
const matches = timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(actual, 'hex')
);
if (!matches) {
throw new Error(
`Checksum verification failed for ${context.distribution} version ${context.version}: ${checksum.algorithm} expected ${expected}, actual ${actual}.`
);
}
}
+12 -3
View File
@@ -1,8 +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 {save} from './cache.js';
import {getBooleanInput, isJobStatusSuccess} from './util.js';
import {fileURLToPath} from 'url';
async function removePrivateKeyFromKeychain() {
@@ -28,7 +27,17 @@ 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;
}
const {save} = await import('./cache.js');
await save(cache);
}
/**
+2
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';
+12 -2
View File
@@ -105,7 +105,12 @@ export class AdoptDistribution extends JavaBase {
.map(item => {
return {
version: item.version_data.semver,
url: item.binaries[0].package.link
url: item.binaries[0].package.link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
} as JavaDownloadRelease;
});
@@ -133,7 +138,7 @@ export class AdoptDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -255,4 +260,9 @@ export class AdoptDistribution extends JavaBase {
return process.platform;
}
}
protected distributionArchitecture(): string {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}
+179 -128
View File
@@ -10,12 +10,17 @@ import {
isVersionSatisfies
} from '../util.js';
import {
ChecksumAlgorithm,
ChecksumMetadata,
JavaDownloadRelease,
JavaInstallerOptions,
JavaInstallerResults
} from './base-models.js';
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;
@@ -34,17 +39,16 @@ export abstract class JavaBase {
protected distribution: string,
installerOptions: JavaInstallerOptions
) {
this.http = new httpm.HttpClient('actions/setup-java', undefined, {
allowRetries: true,
maxRetries: 3
});
this.http = new RetryingHttpClient('actions/setup-java');
({
version: this.version,
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;
@@ -63,6 +67,101 @@ export abstract class JavaBase {
range: string
): Promise<JavaDownloadRelease>;
protected async downloadAndVerify(
javaRelease: JavaDownloadRelease
): Promise<string> {
const archivePath = await tc.downloadTool(javaRelease.url);
const checksum = javaRelease.checksum;
if (!checksum || !checksum.value?.trim()) {
core.debug(
`No authoritative checksum is available for ${this.distribution} version ${javaRelease.version}; skipping checksum verification.`
);
return archivePath;
}
try {
await verifyChecksum(archivePath, checksum, {
distribution: this.distribution,
version: javaRelease.version
});
core.debug(
`Verified ${checksum.algorithm} checksum for ${this.distribution} version ${javaRelease.version}.`
);
return archivePath;
} catch (error) {
let cleanupError: unknown;
let cleanupFailed = false;
try {
await fs.promises.rm(archivePath, {force: true});
} catch (caughtCleanupError) {
cleanupError = caughtCleanupError;
cleanupFailed = true;
}
if (cleanupFailed) {
throw new Error(
`${(error as Error).message} Failed to remove the downloaded archive after verification failure: ${(cleanupError as Error).message}`,
{cause: error}
);
}
throw error;
}
}
protected async fetchChecksum(
checksumUrl: string,
algorithm: ChecksumAlgorithm | ChecksumAlgorithm[]
): Promise<ChecksumMetadata | undefined> {
// Some vendors (e.g. JetBrains) publish a single, generically-named
// checksum sibling (`.checksum`) whose digest algorithm isn't disclosed
// by the URL and has changed across releases. Accepting a list of
// candidate algorithms lets callers pass every algorithm the vendor is
// known to use; the actual algorithm is then inferred from the length of
// the returned digest.
const algorithms = Array.isArray(algorithm) ? algorithm : [algorithm];
const algorithmLabel = algorithms.join(' or ');
const response = await this.http.get(checksumUrl);
const statusCode = response.message.statusCode;
const source = (() => {
try {
const url = new URL(checksumUrl);
return `${url.origin}${url.pathname}`;
} catch {
return 'an invalid checksum URL';
}
})();
if (statusCode === httpm.HttpCodes.NotFound) {
core.debug(
`No authoritative ${algorithmLabel} checksum is available for ${this.distribution} from ${source}; skipping checksum verification.`
);
return undefined;
}
if (statusCode !== httpm.HttpCodes.OK) {
throw new Error(
`Failed to fetch the authoritative ${algorithmLabel} checksum for ${this.distribution} from ${source} (HTTP ${statusCode}).`
);
}
const body = await response.readBody();
const value = body.trim().split(/\s+/, 1)[0] ?? '';
if (!value) {
throw new Error(
`Received an empty authoritative ${algorithmLabel} checksum for ${this.distribution} from ${source}.`
);
}
// Prefer the strongest algorithm whose digest length matches what was
// actually returned; fall back to the first candidate (preserving prior
// behavior/error messages) when the digest doesn't match any of them.
const resolvedAlgorithm =
algorithms.find(algo => value.length === expectedDigestLength(algo)) ??
algorithms[0];
return {algorithm: resolvedAlgorithm, value, source: checksumUrl};
}
public async setupJava(): Promise<JavaInstallerResults> {
if (this.verifySignature && !this.supportsSignatureVerification()) {
throw new Error(
@@ -75,113 +174,19 @@ export abstract class JavaBase {
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
} else {
core.info('Trying to resolve the latest version from remote');
const MAX_RETRIES = 4;
const RETRY_DELAY_MS = 2000;
const retryableCodes = [
'ETIMEDOUT',
'ECONNRESET',
'ENOTFOUND',
'ECONNREFUSED'
];
let retries = MAX_RETRIES;
while (retries > 0) {
try {
// Clear console timers before each attempt to prevent conflicts
if (retries < MAX_RETRIES && core.isDebug()) {
const consoleAny = console as any;
consoleAny._times?.clear?.();
}
const javaRelease = await this.findPackageForDownload(this.version);
core.info(`Resolved latest version as ${javaRelease.version}`);
if (
!this.forceDownload &&
foundJava?.version === javaRelease.version
) {
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
} else {
core.info('Trying to download...');
foundJava = await this.downloadTool(javaRelease);
core.info(`Java ${foundJava.version} was downloaded`);
}
break;
} catch (error: any) {
retries--;
// Check if error is retryable (including aggregate errors)
const isRetryable =
(error instanceof tc.HTTPError &&
error.httpStatusCode &&
[429, 502, 503, 504, 522].includes(error.httpStatusCode)) ||
retryableCodes.includes(error?.code) ||
(error?.errors &&
Array.isArray(error.errors) &&
error.errors.some((err: any) =>
retryableCodes.includes(err?.code)
));
if (retries > 0 && isRetryable) {
core.debug(
`Attempt failed due to network or timeout issues, initiating retry... (${retries} attempts left)`
);
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
continue;
}
if (error instanceof tc.HTTPError) {
if (error.httpStatusCode === 403) {
core.error('HTTP 403: Permission denied or access restricted.');
} else if (error.httpStatusCode === 429) {
core.warning(
'HTTP 429: Rate limit exceeded. Please retry later.'
);
} else {
core.error(`HTTP ${error.httpStatusCode}: ${error.message}`);
}
} else if (error && error.errors && Array.isArray(error.errors)) {
core.error(
`Java setup failed due to network or configuration error(s)`
);
if (error instanceof Error && error.stack) {
core.debug(error.stack);
}
for (const err of error.errors) {
const endpoint = err?.address || err?.hostname || '';
const port = err?.port ? `:${err.port}` : '';
const message = err?.message || 'Aggregate error';
const endpointInfo = !message.includes(endpoint)
? ` ${endpoint}${port}`
: '';
const localInfo =
err.localAddress && err.localPort
? ` - Local (${err.localAddress}:${err.localPort})`
: '';
const logMessage = `${message}${endpointInfo}${localInfo}`;
core.error(logMessage);
core.debug(`${err.stack || err.message}`);
Object.entries(err).forEach(([key, value]) => {
core.debug(`"${key}": ${JSON.stringify(value)}`);
});
}
} else {
const message =
error instanceof Error ? error.message : JSON.stringify(error);
core.error(`Java setup process failed due to: ${message}`);
if (typeof error?.code === 'string') {
core.debug(error.stack);
}
const errorDetails = {
name: error.name,
message: error.message,
...Object.getOwnPropertyNames(error)
.filter(prop => !['name', 'message', 'stack'].includes(prop))
.reduce<{[key: string]: any}>((acc, prop) => {
acc[prop] = error[prop];
return acc;
}, {})
};
Object.entries(errorDetails).forEach(([key, value]) => {
core.debug(`"${key}": ${JSON.stringify(value)}`);
});
}
throw error;
try {
const javaRelease = await this.findPackageForDownload(this.version);
core.info(`Resolved latest version as ${javaRelease.version}`);
if (!this.forceDownload && foundJava?.version === javaRelease.version) {
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
} else {
core.info('Trying to download...');
foundJava = await this.downloadTool(javaRelease);
core.info(`Java ${foundJava.version} was downloaded`);
}
} catch (error: any) {
this.logSetupError(error);
throw error;
}
}
if (!foundJava) {
@@ -209,6 +214,68 @@ export abstract class JavaBase {
return foundJava;
}
private logSetupError(error: any): void {
const httpStatusCode =
error instanceof tc.HTTPError
? error.httpStatusCode
: error instanceof httpm.HttpClientError
? error.statusCode
: undefined;
if (httpStatusCode) {
if (httpStatusCode === 403) {
core.error('HTTP 403: Permission denied or access restricted.');
} else if (httpStatusCode === 429) {
core.warning('HTTP 429: Rate limit exceeded. Please retry later.');
} else {
core.error(`HTTP ${httpStatusCode}: ${error.message}`);
}
} else if (error && error.errors && Array.isArray(error.errors)) {
core.error(`Java setup failed due to network or configuration error(s)`);
if (error instanceof Error && error.stack) {
core.debug(error.stack);
}
for (const err of error.errors) {
const endpoint = err?.address || err?.hostname || '';
const port = err?.port ? `:${err.port}` : '';
const message = err?.message || 'Aggregate error';
const endpointInfo = !message.includes(endpoint)
? ` ${endpoint}${port}`
: '';
const localInfo =
err.localAddress && err.localPort
? ` - Local (${err.localAddress}:${err.localPort})`
: '';
const logMessage = `${message}${endpointInfo}${localInfo}`;
core.error(logMessage);
core.debug(`${err.stack || err.message}`);
Object.entries(err).forEach(([key, value]) => {
core.debug(`"${key}": ${JSON.stringify(value)}`);
});
}
} else {
const message =
error instanceof Error ? error.message : JSON.stringify(error);
core.error(`Java setup process failed due to: ${message}`);
if (typeof error?.code === 'string') {
core.debug(error.stack);
}
const errorDetails = {
name: error.name,
message: error.message,
...Object.getOwnPropertyNames(error)
.filter(prop => !['name', 'message', 'stack'].includes(prop))
.reduce<{[key: string]: any}>((acc, prop) => {
acc[prop] = error[prop];
return acc;
}, {})
};
Object.entries(errorDetails).forEach(([key, value]) => {
core.debug(`"${key}": ${JSON.stringify(value)}`);
});
}
}
protected get toolcacheFolderName(): string {
return `Java_${this.distribution}_${this.packageType}`;
}
@@ -387,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;
}
}
+9
View File
@@ -14,8 +14,17 @@ export interface JavaInstallerResults {
path: string;
}
export type ChecksumAlgorithm = 'sha256' | 'sha512';
export interface ChecksumMetadata {
algorithm: ChecksumAlgorithm;
value: string;
source?: string;
}
export interface JavaDownloadRelease {
version: string;
url: string;
signatureUrl?: string;
checksum?: ChecksumMetadata;
}
+17 -6
View File
@@ -19,6 +19,9 @@ import {
ICorrettoAvailableVersions
} from './models.js';
const CORRETTO_VERSIONS_URL =
'https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json';
export class CorrettoDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('Corretto', installerOptions);
@@ -30,7 +33,7 @@ export class CorrettoDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -86,7 +89,12 @@ export class CorrettoDistribution extends JavaBase {
.map(item => {
return {
version: convertVersionToSemver(item.correttoVersion),
url: item.downloadLink
url: item.downloadLink,
checksum: {
algorithm: 'sha256',
value: item.checksum_sha256,
source: CORRETTO_VERSIONS_URL
}
} as JavaDownloadRelease;
});
@@ -110,16 +118,14 @@ export class CorrettoDistribution extends JavaBase {
console.time('Retrieving available versions for Corretto took'); // eslint-disable-line no-console
}
const availableVersionsUrl =
'https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json';
const fetchCurrentVersions =
await this.http.getJson<ICorrettoAllAvailableVersions>(
availableVersionsUrl
CORRETTO_VERSIONS_URL
);
const fetchedCurrentVersions = fetchCurrentVersions.result;
if (!fetchedCurrentVersions) {
throw Error(
`Could not fetch latest corretto versions from ${availableVersionsUrl}`
`Could not fetch latest corretto versions from ${CORRETTO_VERSIONS_URL}`
);
}
@@ -188,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);
+99 -90
View File
@@ -1,111 +1,120 @@
import {JavaBase} from './base-installer.js';
import {JavaInstallerOptions} from './base-models.js';
import {LocalDistribution} from './local/installer.js';
import {ZuluDistribution} from './zulu/installer.js';
import {AdoptDistribution, AdoptImplementation} from './adopt/installer.js';
import {
TemurinDistribution,
TemurinImplementation
} from './temurin/installer.js';
import {LibericaDistributions} from './liberica/installer.js';
import {LibericaNikDistributions} from './liberica-nik/installer.js';
import {MicrosoftDistributions} from './microsoft/installer.js';
import {SemeruDistribution} from './semeru/installer.js';
import {CorrettoDistribution} from './corretto/installer.js';
import {OracleDistribution} from './oracle/installer.js';
import {DragonwellDistribution} from './dragonwell/installer.js';
import {SapMachineDistribution} from './sapmachine/installer.js';
import {
GraalVMCommunityDistribution,
GraalVMDistribution
} from './graalvm/installer.js';
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';
enum JavaDistribution {
Adopt = 'adopt',
AdoptHotspot = 'adopt-hotspot',
AdoptOpenJ9 = 'adopt-openj9',
Temurin = 'temurin',
Zulu = 'zulu',
Liberica = 'liberica',
LibericaNik = 'liberica-nik',
JdkFile = 'jdkfile',
Microsoft = 'microsoft',
Semeru = 'semeru',
Corretto = 'corretto',
Oracle = 'oracle',
Dragonwell = 'dragonwell',
SapMachine = 'sapmachine',
GraalVM = 'graalvm',
GraalVMCommunity = 'graalvm-community',
JetBrains = 'jetbrains',
Kona = 'kona',
OracleOpenJdk = 'oracle-openjdk'
}
export function getJavaDistribution(
export async function getJavaDistribution(
distributionName: string,
installerOptions: JavaInstallerOptions,
jdkFile?: string
): JavaBase | null {
if (
installerOptions.packageType === 'jdk+jmods' &&
distributionName !== JavaDistribution.Temurin
) {
throw new Error(
"java-package 'jdk+jmods' is only supported for distribution 'temurin'."
);
}
): Promise<JavaBase | null> {
validateJavaPackage(
distributionName,
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);
case JavaDistribution.JdkFile: {
const {LocalDistribution} = await import('./local/installer.js');
return new LocalDistribution(normalizedInstallerOptions, jdkFile);
}
case JavaDistribution.Adopt:
case JavaDistribution.AdoptHotspot:
case JavaDistribution.AdoptHotspot: {
const {AdoptDistribution, AdoptImplementation} =
await import('./adopt/installer.js');
return new AdoptDistribution(
installerOptions,
normalizedInstallerOptions,
AdoptImplementation.Hotspot
);
case JavaDistribution.AdoptOpenJ9:
}
case JavaDistribution.AdoptOpenJ9: {
const {AdoptDistribution, AdoptImplementation} =
await import('./adopt/installer.js');
return new AdoptDistribution(
installerOptions,
normalizedInstallerOptions,
AdoptImplementation.OpenJ9
);
case JavaDistribution.Temurin:
}
case JavaDistribution.Temurin: {
const {TemurinDistribution, TemurinImplementation} =
await import('./temurin/installer.js');
return new TemurinDistribution(
installerOptions,
normalizedInstallerOptions,
TemurinImplementation.Hotspot
);
case JavaDistribution.Zulu:
return new ZuluDistribution(installerOptions);
case JavaDistribution.Liberica:
return new LibericaDistributions(installerOptions);
case JavaDistribution.LibericaNik:
return new LibericaNikDistributions(installerOptions);
case JavaDistribution.Microsoft:
return new MicrosoftDistributions(installerOptions);
case JavaDistribution.Semeru:
return new SemeruDistribution(installerOptions);
case JavaDistribution.Corretto:
return new CorrettoDistribution(installerOptions);
case JavaDistribution.Oracle:
return new OracleDistribution(installerOptions);
case JavaDistribution.Dragonwell:
return new DragonwellDistribution(installerOptions);
case JavaDistribution.SapMachine:
return new SapMachineDistribution(installerOptions);
case JavaDistribution.GraalVM:
return new GraalVMDistribution(installerOptions);
case JavaDistribution.GraalVMCommunity:
return new GraalVMCommunityDistribution(installerOptions);
case JavaDistribution.JetBrains:
return new JetBrainsDistribution(installerOptions);
case JavaDistribution.Kona:
return new KonaDistribution(installerOptions);
case JavaDistribution.OracleOpenJdk:
return new OpenJdkDistribution(installerOptions);
}
case JavaDistribution.Zulu: {
const {ZuluDistribution} = await import('./zulu/installer.js');
return new ZuluDistribution(normalizedInstallerOptions);
}
case JavaDistribution.Liberica: {
const {LibericaDistributions} = await import('./liberica/installer.js');
return new LibericaDistributions(normalizedInstallerOptions);
}
case JavaDistribution.LibericaNik: {
const {LibericaNikDistributions} =
await import('./liberica-nik/installer.js');
return new LibericaNikDistributions(normalizedInstallerOptions);
}
case JavaDistribution.Microsoft: {
const {MicrosoftDistributions} = await import('./microsoft/installer.js');
return new MicrosoftDistributions(normalizedInstallerOptions);
}
case JavaDistribution.Semeru: {
const {SemeruDistribution} = await import('./semeru/installer.js');
return new SemeruDistribution(normalizedInstallerOptions);
}
case JavaDistribution.Corretto: {
const {CorrettoDistribution} = await import('./corretto/installer.js');
return new CorrettoDistribution(normalizedInstallerOptions);
}
case JavaDistribution.Oracle: {
const {OracleDistribution} = await import('./oracle/installer.js');
return new OracleDistribution(normalizedInstallerOptions);
}
case JavaDistribution.Dragonwell: {
const {DragonwellDistribution} =
await import('./dragonwell/installer.js');
return new DragonwellDistribution(normalizedInstallerOptions);
}
case JavaDistribution.SapMachine: {
const {SapMachineDistribution} =
await import('./sapmachine/installer.js');
return new SapMachineDistribution(normalizedInstallerOptions);
}
case JavaDistribution.GraalVM: {
const {GraalVMDistribution} = await import('./graalvm/installer.js');
return new GraalVMDistribution(normalizedInstallerOptions);
}
case JavaDistribution.GraalVMCommunity: {
const {GraalVMCommunityDistribution} =
await import('./graalvm/installer.js');
return new GraalVMCommunityDistribution(normalizedInstallerOptions);
}
case JavaDistribution.JetBrains: {
const {JetBrainsDistribution} = await import('./jetbrains/installer.js');
return new JetBrainsDistribution(normalizedInstallerOptions);
}
case JavaDistribution.Kona: {
const {KonaDistribution} = await import('./kona/installer.js');
return new KonaDistribution(normalizedInstallerOptions);
}
case JavaDistribution.OracleOpenJdk: {
const {OpenJdkDistribution} = await import('./openjdk/installer.js');
return new OpenJdkDistribution(normalizedInstallerOptions);
}
default:
return null;
}
+8 -2
View File
@@ -46,7 +46,13 @@ export class DragonwellDistribution extends JavaBase {
.map(item => {
return {
version: item.jdk_version,
url: item.download_link
url: item.download_link,
checksum: item.checksum
? {
algorithm: 'sha256',
value: item.checksum
}
: undefined
} as JavaDownloadRelease;
});
@@ -102,7 +108,7 @@ export class DragonwellDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+23 -4
View File
@@ -43,6 +43,7 @@ type OsVersions = 'linux' | 'macos' | 'windows';
interface GraalVMCommunityAsset {
name: string;
browser_download_url: string;
digest?: string;
}
interface GraalVMCommunityRelease {
@@ -66,7 +67,7 @@ export class GraalVMDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -145,7 +146,11 @@ export class GraalVMDistribution extends JavaBase {
const response = await this.http.head(fileUrl);
this.handleHttpResponse(response, range);
return {url: fileUrl, version: range};
return {
url: fileUrl,
version: range,
checksum: await this.fetchChecksum(`${fileUrl}.sha256`, 'sha256')
};
}
protected validateVersionRange(range: string): void {
@@ -284,7 +289,8 @@ export class GraalVMDistribution extends JavaBase {
return {
url: downloadUrl,
version: latestVersion.version
version: latestVersion.version,
checksum: await this.fetchChecksum(`${downloadUrl}.sha256`, 'sha256')
};
}
@@ -456,9 +462,22 @@ export class GraalVMCommunityDistribution extends GraalVMDistribution {
for (const asset of release.assets ?? []) {
const version = this.extractAssetVersion(asset.name, assetSuffix);
if (version) {
const digest = asset.digest?.match(/^sha256:([a-f0-9]{64})$/i)?.[1];
if (!digest) {
core.debug(
`No authoritative sha256 digest is available for ${asset.name}; skipping checksum verification for this asset.`
);
}
versions.set(version, {
version,
url: asset.browser_download_url
url: asset.browser_download_url,
checksum: digest
? {
algorithm: 'sha256',
value: digest,
source: GRAALVM_COMMUNITY_RELEASES_URL
}
: undefined
});
}
}
+12 -2
View File
@@ -50,7 +50,17 @@ export class JetBrainsDistribution extends JavaBase {
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return resolvedFullVersion;
return {
...resolvedFullVersion,
// JetBrains' `.checksum` sibling doesn't disclose its algorithm via the
// filename, and older JBR builds (e.g. JBR 11) publish a SHA-256 digest
// there while newer builds publish SHA-512. Accept either, preferring
// the stronger SHA-512 when the digest length is ambiguous.
checksum: await this.fetchChecksum(
`${resolvedFullVersion.url}.checksum`,
['sha512', 'sha256']
)
};
}
protected async downloadTool(
@@ -60,7 +70,7 @@ export class JetBrainsDistribution extends JavaBase {
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
const javaArchivePath = await tc.downloadTool(javaRelease.url);
const javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extractedJavaPath = await extractJdkFile(javaArchivePath, 'tar.gz');
+15 -8
View File
@@ -19,6 +19,9 @@ import {
renameWinArchive
} from '../../util.js';
const KONA_RELEASES_URL =
'https://tencent.github.io/konajdk/releases/kona-v1.json';
export class KonaDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('Kona', installerOptions);
@@ -30,7 +33,7 @@ export class KonaDistribution extends JavaBase {
core.info(
`Downloading Kona JDK ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
const javaArchivePath = await tc.downloadTool(javaRelease.url);
const javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
@@ -74,7 +77,14 @@ export class KonaDistribution extends JavaBase {
.map(item => {
return {
version: item.version,
url: item.downloadUrl
url: item.downloadUrl,
checksum: item.checksum
? {
algorithm: 'sha256',
value: item.checksum,
source: KONA_RELEASES_URL
}
: undefined
} as JavaDownloadRelease;
})
.sort((a, b) => -semver.compareBuild(a.version, b.version));
@@ -115,16 +125,13 @@ export class KonaDistribution extends JavaBase {
}
private async fetchReleaseInfo(): Promise<IKonaReleaseInfo | null> {
const releasesInfoUrl =
'https://tencent.github.io/konajdk/releases/kona-v1.json';
try {
core.debug(`Fetching Kona release info from URL: ${releasesInfoUrl}`);
return (await this.http.getJson<IKonaReleaseInfo>(releasesInfoUrl))
core.debug(`Fetching Kona release info from URL: ${KONA_RELEASES_URL}`);
return (await this.http.getJson<IKonaReleaseInfo>(KONA_RELEASES_URL))
.result;
} catch (err) {
core.debug(
`Fetching Kona release info from the URL: ${releasesInfoUrl} failed with the error: ${
`Fetching Kona release info from the URL: ${KONA_RELEASES_URL} failed with the error: ${
(err as Error).message
}`
);
+1 -1
View File
@@ -32,7 +32,7 @@ export class LibericaNikDistributions extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+1 -1
View File
@@ -32,7 +32,7 @@ export class LibericaDistributions extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+6 -2
View File
@@ -31,7 +31,7 @@ export class MicrosoftDistributions extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
if (this.verifySignature) {
if (!javaRelease.signatureUrl) {
@@ -114,7 +114,11 @@ export class MicrosoftDistributions extends JavaBase {
return {
url: file.download_url,
signatureUrl,
version: foundRelease.version
version: foundRelease.version,
checksum: await this.fetchChecksum(
`${file.download_url}.sha256sum.txt`,
'sha256'
)
};
}
+6 -2
View File
@@ -50,7 +50,11 @@ export class OpenJdkDistribution extends JavaBase {
);
}
return matchingReleases[0];
const release = matchingReleases[0];
return {
...release,
checksum: await this.fetchChecksum(`${release.url}.sha256`, 'sha256')
};
}
protected async downloadTool(
@@ -59,7 +63,7 @@ export class OpenJdkDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = javaRelease.url.endsWith('.zip') ? 'zip' : 'tar.gz';
+6 -2
View File
@@ -32,7 +32,7 @@ export class OracleDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -112,7 +112,11 @@ export class OracleDistribution extends JavaBase {
const response = await this.http.head(url);
if (response.message.statusCode === HttpCodes.OK) {
return {url, version: range};
return {
url,
version: range,
checksum: await this.fetchChecksum(`${url}.sha256`, 'sha256')
};
}
if (response.message.statusCode !== HttpCodes.NotFound) {
+135
View File
@@ -0,0 +1,135 @@
import semver from 'semver';
import {convertVersionToSemver} from '../util.js';
export enum JavaDistribution {
Adopt = 'adopt',
AdoptHotspot = 'adopt-hotspot',
AdoptOpenJ9 = 'adopt-openj9',
Temurin = 'temurin',
Zulu = 'zulu',
Liberica = 'liberica',
LibericaNik = 'liberica-nik',
JdkFile = 'jdkfile',
Microsoft = 'microsoft',
Semeru = 'semeru',
Corretto = 'corretto',
Oracle = 'oracle',
Dragonwell = 'dragonwell',
SapMachine = 'sapmachine',
GraalVM = 'graalvm',
GraalVMCommunity = 'graalvm-community',
JetBrains = 'jetbrains',
Kona = 'kona',
OracleOpenJdk = 'oracle-openjdk'
}
export 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']
} as const satisfies Record<JavaDistribution, readonly string[]>;
export function validateJavaPackage(
distributionName: string,
packageType: string,
version: string
): void {
if (!isJavaDistribution(distributionName)) {
return;
}
const supportedPackages: readonly string[] =
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: string): value is JavaDistribution {
return Object.prototype.hasOwnProperty.call(JAVA_PACKAGE_CAPABILITIES, value);
}
function canResolveTemurinJmods(version: string): boolean {
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.validRange(normalizedRange)) {
// JavaBase owns general version validation and its targeted error messages.
return true;
}
return semver.intersects(normalizedRange, '>=24.0.0', {
includePrerelease: true
});
}
function createUnsupportedPackageError(
distributionName: JavaDistribution,
packageType: string,
supportedPackages: readonly string[],
detail?: string
): Error {
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(' '));
}
+306
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('; ');
}
+9 -2
View File
@@ -56,7 +56,14 @@ export class SapMachineDistribution extends JavaBase {
}
const resolvedVersion = matchedVersions[0];
return resolvedVersion;
const checksumUrl = resolvedVersion.url.replace(
/\.(?:tar\.gz|zip)$/,
'.sha256.txt'
);
return {
...resolvedVersion,
checksum: await this.fetchChecksum(checksumUrl, 'sha256')
};
}
private async getAvailableVersions(): Promise<ISapMachineVersions[]> {
@@ -104,7 +111,7 @@ export class SapMachineDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+7 -2
View File
@@ -69,7 +69,12 @@ export class SemeruDistribution extends JavaBase {
: item.version_data.semver.replace('-beta+', '+');
return {
version: formattedVersion,
url: item.binaries[0].package.link
url: item.binaries[0].package.link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
} as JavaDownloadRelease;
});
@@ -104,7 +109,7 @@ export class SemeruDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
+12 -2
View File
@@ -69,7 +69,12 @@ export class TemurinDistribution extends JavaBase {
return {
version: formattedVersion,
url: item.binaries[0].package.link,
signatureUrl: item.binaries[0].package.signature_link
signatureUrl: item.binaries[0].package.signature_link,
checksum: {
algorithm: 'sha256',
value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link
}
} as JavaDownloadRelease;
});
@@ -132,7 +137,7 @@ export class TemurinDistribution extends JavaBase {
}
private async downloadPackage(release: JavaDownloadRelease): Promise<string> {
const archivePath = await tc.downloadTool(release.url);
const archivePath = await this.downloadAndVerify(release);
if (this.verifySignature) {
if (!release.signatureUrl) {
@@ -277,4 +282,9 @@ export class TemurinDistribution extends JavaBase {
return process.platform;
}
}
protected distributionArchitecture(): string {
const architecture = super.distributionArchitecture();
return architecture === 'armv7' ? 'arm' : architecture;
}
}
+43 -10
View File
@@ -6,7 +6,7 @@ import fs from 'fs';
import semver from 'semver';
import {JavaBase} from '../base-installer.js';
import {IZuluVersions} from './models.js';
import {IZuluPackageDetails, IZuluVersions} from './models.js';
import {
extractJdkFile,
getDownloadArchiveExtension,
@@ -20,6 +20,15 @@ import {
JavaInstallerResults
} from '../base-models.js';
// The Azul Metadata API only reports the sha256 checksum on the
// package-details endpoint, keyed by package_uuid, so the resolved candidate
// must retain its UUID after sorting until the single follow-up request is made.
interface ZuluResolvedRelease {
version: string;
url: string;
packageUuid: string;
}
export class ZuluDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('Zulu', installerOptions);
@@ -40,7 +49,8 @@ export class ZuluDistribution extends JavaBase {
return {
version: convertVersionToSemver(javaVersion),
url: item.download_url,
zuluVersion: convertVersionToSemver(item.distro_version)
zuluVersion: convertVersionToSemver(item.distro_version),
packageUuid: item.package_uuid
};
});
@@ -54,12 +64,11 @@ export class ZuluDistribution extends JavaBase {
-semver.compareBuild(a.zuluVersion, b.zuluVersion)
);
})
.map(item => {
return {
version: item.version,
url: item.url
} as JavaDownloadRelease;
});
.map((item): ZuluResolvedRelease => ({
version: item.version,
url: item.url,
packageUuid: item.packageUuid
}));
const resolvedFullVersion =
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
@@ -70,7 +79,29 @@ export class ZuluDistribution extends JavaBase {
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
return resolvedFullVersion;
const packageDetailsUrl = `https://api.azul.com/metadata/v1/zulu/packages/${resolvedFullVersion.packageUuid}`;
const packageDetails = (
await this.http.getJson<IZuluPackageDetails>(packageDetailsUrl)
).result;
const digest = packageDetails?.sha256_hash?.match(/^[a-f0-9]{64}$/i)?.[0];
if (!digest) {
core.debug(
`No authoritative sha256 checksum is available for Zulu version ${resolvedFullVersion.version} from ${packageDetailsUrl}; skipping checksum verification.`
);
}
return {
version: resolvedFullVersion.version,
url: resolvedFullVersion.url,
checksum: digest
? {
algorithm: 'sha256',
value: digest,
source: packageDetailsUrl
}
: undefined
};
}
protected async downloadTool(
@@ -79,7 +110,7 @@ export class ZuluDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -190,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';
+4
View File
@@ -10,3 +10,7 @@ export interface IZuluVersions {
latest: boolean;
availability_type: string;
}
export interface IZuluPackageDetails {
sha256_hash?: string;
}
+166
View File
@@ -0,0 +1,166 @@
import * as core from '@actions/core';
import * as httpm from '@actions/http-client';
import type {OutgoingHttpHeaders} from 'http';
const RETRYABLE_HTTP_STATUS_CODES = new Set([429, 502, 503, 504, 522]);
const RETRYABLE_NETWORK_ERROR_CODES = new Set([
'ETIMEDOUT',
'ECONNRESET',
'ENOTFOUND',
'ECONNREFUSED'
]);
const RETRYABLE_HTTP_VERBS = new Set(['OPTIONS', 'GET', 'DELETE', 'HEAD']);
export interface HttpRetryOptions {
maxAttempts?: number;
baseDelayMs?: number;
maxDelayMs?: number;
sleep?: (delayMs: number) => Promise<void>;
random?: () => number;
now?: () => number;
}
export class RetryingHttpClient extends httpm.HttpClient {
private readonly maxAttempts: number;
private readonly baseDelayMs: number;
private readonly maxDelayMs: number;
private readonly sleep: (delayMs: number) => Promise<void>;
private readonly random: () => number;
private readonly now: () => number;
constructor(userAgent?: string, retryOptions: HttpRetryOptions = {}) {
super(userAgent, undefined, {allowRetries: false});
this.maxAttempts = retryOptions.maxAttempts ?? 4;
this.baseDelayMs = retryOptions.baseDelayMs ?? 1000;
this.maxDelayMs = retryOptions.maxDelayMs ?? 10000;
this.sleep =
retryOptions.sleep ??
(delayMs => new Promise(resolve => setTimeout(resolve, delayMs)));
this.random = retryOptions.random ?? Math.random;
this.now = retryOptions.now ?? Date.now;
if (this.maxAttempts < 1) {
throw new Error('maxAttempts must be at least 1');
}
if (this.baseDelayMs < 0 || this.maxDelayMs < this.baseDelayMs) {
throw new Error(
'baseDelayMs must be non-negative and no greater than maxDelayMs'
);
}
}
public override async request(
verb: string,
requestUrl: string,
data: string | NodeJS.ReadableStream | null,
headers?: OutgoingHttpHeaders
): Promise<httpm.HttpClientResponse> {
if (!RETRYABLE_HTTP_VERBS.has(verb)) {
return super.request(verb, requestUrl, data, headers);
}
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
try {
const response = await super.request(verb, requestUrl, data, headers);
const statusCode = response.message.statusCode;
if (
!statusCode ||
!RETRYABLE_HTTP_STATUS_CODES.has(statusCode) ||
attempt === this.maxAttempts
) {
return response;
}
const delayMs = this.getDelayMs(
attempt,
response.message.headers['retry-after']
);
await response.readBody();
this.logRetry(attempt, delayMs, `HTTP ${statusCode}`);
await this.sleep(delayMs);
} catch (error) {
if (!isRetryableNetworkError(error) || attempt === this.maxAttempts) {
throw error;
}
const delayMs = this.getDelayMs(attempt);
this.logRetry(attempt, delayMs, getErrorMessage(error));
await this.sleep(delayMs);
}
}
throw new Error('HTTP retry attempts exhausted unexpectedly');
}
private getDelayMs(
failedAttempt: number,
retryAfter?: string | string[]
): number {
const exponentialDelay = Math.min(
this.maxDelayMs,
this.baseDelayMs * 2 ** (failedAttempt - 1)
);
const jitteredDelay = Math.floor(
exponentialDelay / 2 + this.random() * (exponentialDelay / 2)
);
const retryAfterDelay = parseRetryAfter(retryAfter, this.now());
return Math.min(
this.maxDelayMs,
Math.max(jitteredDelay, retryAfterDelay ?? 0)
);
}
private logRetry(
failedAttempt: number,
delayMs: number,
reason: string
): void {
core.info(
`Request attempt ${failedAttempt} of ${this.maxAttempts} failed (${reason}); retrying in ${delayMs} ms`
);
}
}
export function parseRetryAfter(
value: string | string[] | undefined,
nowMs: number
): number | undefined {
const retryAfter = Array.isArray(value) ? value[0] : value;
if (!retryAfter) {
return undefined;
}
if (/^\d+$/.test(retryAfter.trim())) {
return Number(retryAfter) * 1000;
}
const retryAt = Date.parse(retryAfter);
if (Number.isNaN(retryAt) || retryAt <= nowMs) {
return undefined;
}
return retryAt - nowMs;
}
export function isRetryableNetworkError(error: unknown): boolean {
if (!isErrorRecord(error)) {
return false;
}
if (
typeof error.code === 'string' &&
RETRYABLE_NETWORK_ERROR_CODES.has(error.code)
) {
return true;
}
return (
Array.isArray(error.errors) &&
error.errors.some(nestedError => isRetryableNetworkError(nestedError))
);
}
function isErrorRecord(error: unknown): error is Record<string, unknown> {
return typeof error === 'object' && error !== null;
}
function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : 'network error';
}
+71 -41
View File
@@ -1,14 +1,9 @@
import fs from 'fs';
import * as core from '@actions/core';
import * as auth from './auth.js';
import {
getBooleanInput,
isCacheFeatureAvailable,
getVersionFromFileContent
} from './util.js';
import {getBooleanInput, getVersionFromFileContent} from './util.js';
import * as toolchains from './toolchains.js';
import * as constants from './constants.js';
import {restore} from './cache.js';
import * as path from 'path';
import {fileURLToPath} from 'url';
import {getJavaDistribution} from './distributions/distribution-factory.js';
@@ -16,42 +11,41 @@ import {JavaInstallerOptions} from './distributions/base-models.js';
import {configureMavenArgs} from './maven-args.js';
import {configureProblemMatcher} from './problem-matcher.js';
async function run() {
export async function run() {
const versions = core.getMultilineInput(constants.INPUT_JAVA_VERSION);
let distributionName = core.getInput(constants.INPUT_DISTRIBUTION);
const versionFile = core.getInput(constants.INPUT_JAVA_VERSION_FILE);
const architecture = core.getInput(constants.INPUT_ARCHITECTURE);
const packageType = core.getInput(constants.INPUT_JAVA_PACKAGE);
const jdkFile = getJdkFileInput();
const cache = core.getInput(constants.INPUT_CACHE);
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, false);
const setDefault = getBooleanInput(constants.INPUT_SET_DEFAULT, true);
const verifySignature = getBooleanInput(
constants.INPUT_VERIFY_SIGNATURE,
false
);
const verifySignaturePublicKey =
core.getInput(constants.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY) || undefined;
const toolchainIds = core.getMultilineInput(constants.INPUT_MVN_TOOLCHAIN_ID);
let actionError: Error | undefined;
let cacheRestore: Promise<void> | undefined;
try {
const versions = core.getMultilineInput(constants.INPUT_JAVA_VERSION);
let distributionName = core.getInput(constants.INPUT_DISTRIBUTION);
const versionFile = core.getInput(constants.INPUT_JAVA_VERSION_FILE);
const architecture = core.getInput(constants.INPUT_ARCHITECTURE);
const packageType = core.getInput(constants.INPUT_JAVA_PACKAGE);
const jdkFile = getJdkFileInput();
const cache = core.getInput(constants.INPUT_CACHE);
const cacheDependencyPath = core.getInput(
constants.INPUT_CACHE_DEPENDENCY_PATH
);
const checkLatest = getBooleanInput(constants.INPUT_CHECK_LATEST, false);
const forceDownload = getBooleanInput(
constants.INPUT_FORCE_DOWNLOAD,
false
);
const setDefault = getBooleanInput(constants.INPUT_SET_DEFAULT, true);
const verifySignature = getBooleanInput(
constants.INPUT_VERIFY_SIGNATURE,
false
);
const verifySignaturePublicKey =
core.getInput(constants.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY) || undefined;
let toolchainIds = core.getMultilineInput(constants.INPUT_MVN_TOOLCHAIN_ID);
core.startGroup('Installed distributions');
if (versions.length !== toolchainIds.length) {
toolchainIds = [];
}
if (!versions.length && !versionFile) {
throw new Error('java-version or java-version-file input expected');
}
toolchains.validateToolchainIds(versions, versionFile, toolchainIds);
if (!versions.length) {
core.debug(
'java-version input is empty, looking for java-version-file input'
@@ -96,6 +90,9 @@ async function run() {
toolchainIds
};
cacheRestore = cache
? startCacheRestore(cache, cacheDependencyPath, cachePath)
: undefined;
await installVersion(versionInfo.version, installerInputsOptions);
} else {
// When using java-version input, distribution is still required
@@ -116,6 +113,9 @@ async function run() {
toolchainIds
};
cacheRestore = cache
? startCacheRestore(cache, cacheDependencyPath, cachePath)
: undefined;
for (const [index, version] of versions.entries()) {
await installVersion(version, installerInputsOptions, index);
}
@@ -131,15 +131,31 @@ async function run() {
await auth.configureAuthentication();
configureMavenArgs();
if (cache && isCacheFeatureAvailable()) {
await restore(cache, cacheDependencyPath);
}
} catch (error) {
core.setFailed((error as Error).message);
actionError = error as Error;
}
if (cacheRestore) {
try {
await cacheRestore;
} catch (error) {
if (!actionError) {
actionError = error as Error;
}
}
}
if (actionError) {
core.setFailed(actionError.message);
}
}
run();
if (process.argv[1] === fileURLToPath(import.meta.url)) {
run();
} else {
// https://nodejs.org/api/modules.html#modules_accessing_the_main_module
core.info('the script is loaded as a module, so skipping the execution');
}
function getJdkFileInput(): string {
const jdkFile = core.getInput(constants.INPUT_JDK_FILE);
@@ -183,7 +199,7 @@ async function installVersion(
version
};
const distribution = getJavaDistribution(
const distribution = await getJavaDistribution(
distributionName,
installerOptions,
jdkFile
@@ -228,3 +244,17 @@ interface installerInputsOptions {
jdkFile: string;
toolchainIds: Array<string>;
}
async function startCacheRestore(
cache: string,
cacheDependencyPath: string,
cachePath: string[]
): Promise<void> {
const {isCacheFeatureAvailable} = await import('./cache-feature.js');
if (!isCacheFeatureAvailable()) {
return;
}
const {restore} = await import('./cache.js');
await restore(cache, cacheDependencyPath, cachePath);
}
+17
View File
@@ -14,6 +14,23 @@ interface JdkInfo {
jdkHome: string;
}
export function validateToolchainIds(
versions: string[],
versionFile: string,
toolchainIds: string[]
) {
if (!toolchainIds.length) {
return;
}
const versionCount = versions.length || (versionFile ? 1 : 0);
if (versionCount !== toolchainIds.length) {
throw new Error(
`The number of Maven toolchain IDs (${toolchainIds.length}) must match the number of Java versions (${versionCount})`
);
}
}
export async function configureToolchains(
version: string,
distributionName: string,
+15 -21
View File
@@ -2,7 +2,6 @@ import os from 'os';
import path from 'path';
import * as fs from 'fs';
import * as semver from 'semver';
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
@@ -20,8 +19,21 @@ export function getTempDir() {
}
export function getBooleanInput(inputName: string, defaultValue = false) {
return (
(core.getInput(inputName) || String(defaultValue)).toUpperCase() === 'TRUE'
const inputValue = core.getInput(inputName);
const normalizedValue = inputValue.trim().toLowerCase();
if (!normalizedValue) {
return defaultValue;
}
if (normalizedValue === 'true') {
return true;
}
if (normalizedValue === 'false') {
return false;
}
throw new Error(
`Invalid value '${inputValue}' for boolean input '${inputName}'. Expected 'true' or 'false'.`
);
}
@@ -113,24 +125,6 @@ export function isGhes(): boolean {
return !isGitHubHost && !isGitHubEnterpriseCloudHost && !isLocalHost;
}
export function isCacheFeatureAvailable(): boolean {
if (cache.isFeatureAvailable()) {
return true;
}
if (isGhes()) {
core.warning(
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.'
);
return false;
}
core.warning(
'The runner was not able to contact the cache service. Caching will be skipped'
);
return false;
}
export interface VersionInfo {
version: string;
distribution?: string;