mirror of
https://github.com/actions/setup-java.git
synced 2026-08-24 17:43:08 +01:00
Compare commits
36 Commits
copilot/fi
...
27f2c62824
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27f2c62824 | ||
|
|
19c23b379e | ||
|
|
6e26972896 | ||
|
|
5894ef6b27 | ||
|
|
e1ce3a3428 | ||
|
|
ce75feb3d3 | ||
|
|
382d4b753d | ||
|
|
24d1ce4c2b | ||
|
|
1c3b3d28f0 | ||
|
|
0b0bd25927 | ||
|
|
089b010dc8 | ||
|
|
e07d36bbdd | ||
|
|
69304e5bab | ||
|
|
f75542ba2b | ||
|
|
c59dceb0cd | ||
|
|
8f48118d89 | ||
|
|
c79143eaae | ||
|
|
77c695c0d0 | ||
|
|
7b26641a55 | ||
|
|
e08df439e0 | ||
|
|
cd3b6000e4 | ||
|
|
c3d7ccbf81 | ||
|
|
46f6294045 | ||
|
|
e40a8e0642 | ||
|
|
6e2e32e729 | ||
|
|
38fa86f9e4 | ||
|
|
ecd5f809c2 | ||
|
|
4a24efdcc9 | ||
|
|
cc8cdedf67 | ||
|
|
4b0728bd0d | ||
|
|
7d485884af | ||
|
|
c3568cc9d9 | ||
|
|
81c13a41f9 | ||
|
|
3c85b14378 | ||
|
|
80c368b720 | ||
|
|
cd57b95697 |
102
.github/workflows/e2e-cache-dependency-path.yml
vendored
102
.github/workflows/e2e-cache-dependency-path.yml
vendored
@@ -1,102 +0,0 @@
|
||||
name: Validate cache with cache-dependency-path option
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- releases/*
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
gradle1-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for gradle
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '17'
|
||||
cache: gradle
|
||||
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
|
||||
- name: Create files to cache
|
||||
# Need to avoid using Gradle daemon to stabilize the save process on Windows
|
||||
# https://github.com/actions/cache/issues/454#issuecomment-840493935
|
||||
run: |
|
||||
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
|
||||
if [ ! -d ~/.gradle/caches ]; then
|
||||
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
gradle1-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
needs: gradle1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for gradle
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: gradle
|
||||
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
|
||||
- name: Confirm that ~/.gradle/caches directory has been made
|
||||
run: |
|
||||
if [ ! -d ~/.gradle/caches ]; then
|
||||
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
ls ~/.gradle/caches/
|
||||
gradle2-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
needs: gradle1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for gradle
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: gradle
|
||||
cache-dependency-path: __tests__/cache/gradle2/*.gradle*
|
||||
- name: Confirm that ~/.gradle/caches directory has not been made
|
||||
run: |
|
||||
if [ -d ~/.gradle/caches ]; then
|
||||
echo "::error::The ~/.gradle/caches directory exists unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
309
.github/workflows/e2e-cache.yml
vendored
309
.github/workflows/e2e-cache.yml
vendored
@@ -42,10 +42,7 @@ jobs:
|
||||
# https://github.com/actions/cache/issues/454#issuecomment-840493935
|
||||
run: |
|
||||
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
|
||||
if [ ! -d ~/.gradle/caches ]; then
|
||||
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||
gradle-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -66,12 +63,7 @@ jobs:
|
||||
java-version: '11'
|
||||
cache: gradle
|
||||
- name: Confirm that ~/.gradle/caches directory has been made
|
||||
run: |
|
||||
if [ ! -d ~/.gradle/caches ]; then
|
||||
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
ls ~/.gradle/caches/
|
||||
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||
maven-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -93,10 +85,7 @@ jobs:
|
||||
- name: Create files to cache
|
||||
run: |
|
||||
mvn verify -f __tests__/cache/maven/pom.xml
|
||||
if [ ! -d ~/.m2/repository ]; then
|
||||
echo "::error::The ~/.m2/repository directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||
maven-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -117,12 +106,7 @@ jobs:
|
||||
java-version: '11'
|
||||
cache: maven
|
||||
- name: Confirm that ~/.m2/repository directory has been made
|
||||
run: |
|
||||
if [ ! -d ~/.m2/repository ]; then
|
||||
echo "::error::The ~/.m2/repository directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
ls ~/.m2/repository
|
||||
run: bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||
sbt-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
@@ -155,25 +139,13 @@ jobs:
|
||||
|
||||
- name: Check files to cache on macos-latest
|
||||
if: matrix.os == 'macos-15-intel'
|
||||
run: |
|
||||
if [ ! -d ~/Library/Caches/Coursier ]; then
|
||||
echo "::error::The ~/Library/Caches/Coursier directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
|
||||
- name: Check files to cache on windows-latest
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
if [ ! -d ~/AppData/Local/Coursier/Cache ]; then
|
||||
echo "::error::The ~/AppData/Local/Coursier/Cache directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
|
||||
- name: Check files to cache on ubuntu-latest
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
if [ ! -d ~/.cache/coursier ]; then
|
||||
echo "::error::The ~/.cache/coursier directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
|
||||
sbt-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
@@ -200,25 +172,254 @@ jobs:
|
||||
|
||||
- name: Confirm that ~/Library/Caches/Coursier directory has been made
|
||||
if: matrix.os == 'macos-15-intel'
|
||||
run: |
|
||||
if [ ! -d ~/Library/Caches/Coursier ]; then
|
||||
echo "::error::The ~/Library/Caches/Coursier directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
ls ~/Library/Caches/Coursier
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
|
||||
- name: Confirm that ~/AppData/Local/Coursier/Cache directory has been made
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
if [ ! -d ~/AppData/Local/Coursier/Cache ]; then
|
||||
echo "::error::The ~/AppData/Local/Coursier/Cache directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
ls ~/AppData/Local/Coursier/Cache
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
|
||||
- name: Confirm that ~/.cache/coursier directory has been made
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
|
||||
gradle1-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for gradle
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '17'
|
||||
cache: gradle
|
||||
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
|
||||
- name: Create files to cache
|
||||
# Need to avoid using Gradle daemon to stabilize the save process on Windows
|
||||
# https://github.com/actions/cache/issues/454#issuecomment-840493935
|
||||
run: |
|
||||
if [ ! -d ~/.cache/coursier ]; then
|
||||
echo "::error::The ~/.cache/coursier directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
ls ~/.cache/coursier
|
||||
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
|
||||
bash __tests__/check-dir.sh "$HOME/.gradle/caches"
|
||||
gradle1-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
needs: gradle1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for gradle
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: gradle
|
||||
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"
|
||||
gradle2-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
needs: gradle1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for gradle
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: gradle
|
||||
cache-dependency-path: __tests__/cache/gradle2/*.gradle*
|
||||
- name: Confirm that ~/.gradle/caches directory has not been made
|
||||
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches" absent
|
||||
maven1-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-latest]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for maven
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: maven
|
||||
cache-dependency-path: __tests__/cache/maven/pom.xml
|
||||
- name: Create files to cache
|
||||
run: |
|
||||
mvn verify -f __tests__/cache/maven/pom.xml
|
||||
bash __tests__/check-dir.sh "$HOME/.m2/repository"
|
||||
maven1-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-latest]
|
||||
needs: maven1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for maven
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: maven
|
||||
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"
|
||||
maven2-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-latest]
|
||||
needs: maven1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for maven
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: maven
|
||||
cache-dependency-path: __tests__/cache/maven2/pom.xml
|
||||
- name: Confirm that ~/.m2/repository directory has not been made
|
||||
run: bash __tests__/check-dir.sh "$HOME/.m2/repository" absent
|
||||
sbt1-save:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: __tests__/cache/sbt
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-22.04]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for sbt
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: sbt
|
||||
cache-dependency-path: __tests__/cache/sbt/*.sbt
|
||||
- name: Setup SBT
|
||||
if: matrix.os == 'macos-15-intel'
|
||||
run: |
|
||||
echo ""Installing SBT...""
|
||||
brew install sbt
|
||||
- name: Create files to cache
|
||||
run: sbt update
|
||||
|
||||
- name: Check files to cache on macos-latest
|
||||
if: matrix.os == 'macos-15-intel'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
|
||||
- name: Check files to cache on windows-latest
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
|
||||
- name: Check files to cache on ubuntu-latest
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
|
||||
sbt1-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: __tests__/cache/sbt
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-22.04]
|
||||
needs: sbt1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for sbt
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: sbt
|
||||
cache-dependency-path: __tests__/cache/sbt/*.sbt
|
||||
|
||||
- name: Confirm that ~/Library/Caches/Coursier directory has been made
|
||||
if: matrix.os == 'macos-15-intel'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
|
||||
- name: Confirm that ~/AppData/Local/Coursier/Cache directory has been made
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
|
||||
- name: Confirm that ~/.cache/coursier directory has been made
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
|
||||
sbt2-restore:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: __tests__/cache/sbt2
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-22.04]
|
||||
needs: sbt1-save
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run setup-java with the cache for sbt
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
cache: sbt
|
||||
cache-dependency-path: __tests__/cache/sbt2/*.sbt
|
||||
|
||||
- name: Confirm that ~/Library/Caches/Coursier directory has not been made
|
||||
if: matrix.os == 'macos-15-intel'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier" absent
|
||||
- name: Confirm that ~/AppData/Local/Coursier/Cache directory has not been made
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache" absent
|
||||
- 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
|
||||
|
||||
48
.github/workflows/e2e-publishing.yml
vendored
48
.github/workflows/e2e-publishing.yml
vendored
@@ -38,21 +38,33 @@ jobs:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
server-id: maven
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_CENTRAL_TOKEN
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE
|
||||
server-username-env-var: MAVEN_USERNAME
|
||||
server-password-env-var: MAVEN_CENTRAL_TOKEN
|
||||
gpg-passphrase-env-var: MAVEN_GPG_PASSPHRASE
|
||||
- name: Validate settings.xml
|
||||
run: |
|
||||
$xmlPath = Join-Path $HOME ".m2" "settings.xml"
|
||||
Get-Content $xmlPath | ForEach-Object { Write-Host $_ }
|
||||
|
||||
[xml]$xml = Get-Content $xmlPath
|
||||
$servers = $xml.settings.servers.server
|
||||
if (($servers[0].id -ne 'maven') -or ($servers[0].username -ne '${env.MAVEN_USERNAME}') -or ($servers[0].password -ne '${env.MAVEN_CENTRAL_TOKEN}')) {
|
||||
throw "Generated XML file is incorrect"
|
||||
}
|
||||
$content = [System.IO.File]::ReadAllText($xmlPath)
|
||||
$expected = @(
|
||||
'<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"'
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
' xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">'
|
||||
' <interactiveMode>false</interactiveMode>'
|
||||
' <servers>'
|
||||
' <server>'
|
||||
' <id>maven</id>'
|
||||
' <username>${env.MAVEN_USERNAME}</username>'
|
||||
' <password>${env.MAVEN_CENTRAL_TOKEN}</password>'
|
||||
' </server>'
|
||||
' </servers>'
|
||||
'</settings>'
|
||||
) -join "`n"
|
||||
|
||||
if (($servers[1].id -ne 'gpg.passphrase') -or ($servers[1].passphrase -ne '${env.MAVEN_GPG_PASSPHRASE}')) {
|
||||
if ($content -ne $expected) {
|
||||
Write-Host "Expected settings.xml:"
|
||||
$expected -split "`n" | ForEach-Object { Write-Host $_ }
|
||||
throw "Generated XML file is incorrect"
|
||||
}
|
||||
|
||||
@@ -81,9 +93,9 @@ jobs:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
server-id: maven
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_CENTRAL_TOKEN
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE
|
||||
server-username-env-var: MAVEN_USERNAME
|
||||
server-password-env-var: MAVEN_CENTRAL_TOKEN
|
||||
gpg-passphrase-env-var: MAVEN_GPG_PASSPHRASE
|
||||
- name: Validate settings.xml is overwritten
|
||||
run: |
|
||||
$xmlPath = Join-Path $HOME ".m2" "settings.xml"
|
||||
@@ -119,10 +131,10 @@ jobs:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
server-id: maven
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_CENTRAL_TOKEN
|
||||
server-username-env-var: MAVEN_USERNAME
|
||||
server-password-env-var: MAVEN_CENTRAL_TOKEN
|
||||
overwrite-settings: false
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE
|
||||
gpg-passphrase-env-var: MAVEN_GPG_PASSPHRASE
|
||||
- name: Validate that settings.xml is not overwritten
|
||||
run: |
|
||||
$xmlPath = Join-Path $HOME ".m2" "settings.xml"
|
||||
@@ -152,9 +164,9 @@ jobs:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
server-id: maven
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_CENTRAL_TOKEN
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE
|
||||
server-username-env-var: MAVEN_USERNAME
|
||||
server-password-env-var: MAVEN_CENTRAL_TOKEN
|
||||
gpg-passphrase-env-var: MAVEN_GPG_PASSPHRASE
|
||||
settings-path: ${{ runner.temp }}
|
||||
- name: Validate settings.xml location
|
||||
run: |
|
||||
|
||||
236
.github/workflows/e2e-versions.yml
vendored
236
.github/workflows/e2e-versions.yml
vendored
@@ -83,6 +83,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
|
||||
@@ -96,7 +105,8 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
version: '24-ea'
|
||||
steps:
|
||||
- name: Checkout
|
||||
- &checkout_step
|
||||
name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -115,6 +125,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 }}
|
||||
@@ -127,10 +156,7 @@ jobs:
|
||||
distribution: ['temurin', 'sapmachine']
|
||||
version: ['21', '17']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: Install bash
|
||||
run: apk add --no-cache bash
|
||||
- name: setup-java
|
||||
@@ -153,7 +179,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: &default_os [macos-latest, windows-latest, ubuntu-latest]
|
||||
distribution: ['temurin', 'zulu', 'liberica']
|
||||
version:
|
||||
- '11.0'
|
||||
@@ -182,10 +208,7 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
version: '17.0.7'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
@@ -207,7 +230,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
distribution:
|
||||
[
|
||||
'temurin',
|
||||
@@ -221,10 +244,7 @@ jobs:
|
||||
- distribution: dragonwell
|
||||
os: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
@@ -247,7 +267,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
distribution:
|
||||
[
|
||||
'temurin',
|
||||
@@ -261,10 +281,7 @@ jobs:
|
||||
- distribution: dragonwell
|
||||
os: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
@@ -294,26 +311,37 @@ jobs:
|
||||
run: bash __tests__/verify-java.sh "17" "$JAVA_PATH"
|
||||
shell: bash
|
||||
|
||||
setup-java-ea-versions-zulu:
|
||||
name: zulu ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }}
|
||||
setup-java-ea-versions:
|
||||
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }}
|
||||
needs: setup-java-major-minor-versions
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-15-intel, windows-latest, ubuntu-latest]
|
||||
version: ['17-ea', '15.0.0-ea.14']
|
||||
include:
|
||||
- {os: macos-15-intel, version: '17-ea', distribution: zulu}
|
||||
- {os: windows-latest, version: '17-ea', distribution: zulu}
|
||||
- {os: ubuntu-latest, version: '17-ea', distribution: zulu}
|
||||
- {os: macos-15-intel, version: '15.0.0-ea.14', distribution: zulu}
|
||||
- {os: windows-latest, version: '15.0.0-ea.14', distribution: zulu}
|
||||
- {os: ubuntu-latest, version: '15.0.0-ea.14', distribution: zulu}
|
||||
- {os: macos-latest, version: '17-ea', distribution: temurin}
|
||||
- {os: windows-latest, version: '17-ea', distribution: temurin}
|
||||
- {os: ubuntu-latest, version: '17-ea', distribution: temurin}
|
||||
- {os: macos-latest, version: '17-ea', distribution: sapmachine}
|
||||
- {os: windows-latest, version: '17-ea', distribution: sapmachine}
|
||||
- {os: ubuntu-latest, version: '17-ea', distribution: sapmachine}
|
||||
- {os: macos-latest, version: '21-ea', distribution: sapmachine}
|
||||
- {os: windows-latest, version: '21-ea', distribution: sapmachine}
|
||||
- {os: ubuntu-latest, version: '21-ea', distribution: sapmachine}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
java-version: ${{ matrix.version }}
|
||||
distribution: zulu
|
||||
distribution: ${{ matrix.distribution }}
|
||||
- name: Verify Java
|
||||
env:
|
||||
JAVA_VERSION: ${{ matrix.version }}
|
||||
@@ -321,53 +349,24 @@ jobs:
|
||||
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
|
||||
shell: bash
|
||||
|
||||
setup-java-ea-versions-temurin:
|
||||
name: temurin ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }}
|
||||
setup-java-signature-verification:
|
||||
name: ${{ matrix.distribution }} ${{ matrix.version }} signature verification - ${{ matrix.os }}
|
||||
needs: setup-java-major-minor-versions
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
version: ['17-ea']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
java-version: ${{ matrix.version }}
|
||||
distribution: temurin
|
||||
- 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
|
||||
|
||||
setup-java-temurin-signature-verification:
|
||||
name: temurin ${{ matrix.version }} signature verification - ${{ matrix.os }}
|
||||
needs: setup-java-major-minor-versions
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
version: ['21', '17']
|
||||
distribution: [temurin, microsoft]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: setup-java with signature verification
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
java-version: ${{ matrix.version }}
|
||||
distribution: temurin
|
||||
distribution: ${{ matrix.distribution }}
|
||||
verify-signature: true
|
||||
- name: Verify Java
|
||||
env:
|
||||
@@ -376,61 +375,6 @@ jobs:
|
||||
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
|
||||
shell: bash
|
||||
|
||||
setup-java-microsoft-signature-verification:
|
||||
name: microsoft ${{ matrix.version }} signature verification - ${{ matrix.os }}
|
||||
needs: setup-java-major-minor-versions
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
version: ['21', '17']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: setup-java with signature verification
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
java-version: ${{ matrix.version }}
|
||||
distribution: microsoft
|
||||
verify-signature: true
|
||||
- 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
|
||||
|
||||
setup-java-ea-versions-sapmachine:
|
||||
name: sapmachine ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }}
|
||||
needs: setup-java-major-minor-versions
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
version: ['17-ea', '21-ea']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
java-version: ${{ matrix.version }}
|
||||
distribution: sapmachine
|
||||
- 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
|
||||
|
||||
setup-java-custom-package-type:
|
||||
name: ${{ matrix.distribution }} ${{ matrix.version }} (${{ matrix.java-package }}-x64) - ${{ matrix.os }}
|
||||
needs: setup-java-major-minor-versions
|
||||
@@ -510,10 +454,7 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
@@ -543,10 +484,7 @@ jobs:
|
||||
distribution: ['liberica', 'zulu', 'corretto']
|
||||
version: ['11']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
@@ -567,14 +505,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
distribution: ['temurin', 'microsoft', 'corretto']
|
||||
java-version-file: ['.java-version', '.tool-versions']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: Create .java-version file
|
||||
shell: bash
|
||||
run: echo "17" > .java-version
|
||||
@@ -600,14 +535,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
distribution: ['temurin', 'zulu', 'liberica', 'microsoft', 'corretto']
|
||||
java-version-file: ['.java-version', '.tool-versions']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: Create .java-version file
|
||||
shell: bash
|
||||
run: echo "11" > .java-version
|
||||
@@ -632,14 +564,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
distribution: ['adopt', 'adopt-openj9', 'zulu']
|
||||
java-version-file: ['.java-version', '.tool-versions']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: Create .java-version file
|
||||
shell: bash
|
||||
run: echo "17.0.10" > .java-version
|
||||
@@ -664,14 +593,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
distribution: ['adopt', 'zulu', 'liberica']
|
||||
java-version-file: ['.java-version', '.tool-versions', '.sdkmanrc']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- *checkout_step
|
||||
- name: Create .java-version file
|
||||
shell: bash
|
||||
run: echo "openjdk64-17.0.10" > .java-version
|
||||
@@ -700,10 +626,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
os: *default_os
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
- *checkout_step
|
||||
- name: Setup Java 17 as default
|
||||
uses: ./
|
||||
id: setup-java-17
|
||||
@@ -756,14 +681,17 @@ jobs:
|
||||
Write-Host "$envName=$JavaVersionPath"
|
||||
shell: pwsh
|
||||
- name: Verify Java 21 outputs are set
|
||||
env:
|
||||
JAVA_21_PATH: ${{ steps.setup-java-21.outputs.path }}
|
||||
JAVA_21_VERSION: ${{ steps.setup-java-21.outputs.version }}
|
||||
run: |
|
||||
echo "Java 21 path=${{ steps.setup-java-21.outputs.path }}"
|
||||
echo "Java 21 version=${{ steps.setup-java-21.outputs.version }}"
|
||||
if [ -z "${{ steps.setup-java-21.outputs.path }}" ]; then
|
||||
echo "Java 21 path=$JAVA_21_PATH"
|
||||
echo "Java 21 version=$JAVA_21_VERSION"
|
||||
if [ -z "$JAVA_21_PATH" ]; then
|
||||
echo "Java 21 path output should be set"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${{ steps.setup-java-21.outputs.version }}" ]; then
|
||||
if [ -z "$JAVA_21_VERSION" ]; then
|
||||
echo "Java 21 version output should be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
2
.github/workflows/zizmor.yml
vendored
2
.github/workflows/zizmor.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
|
||||
2
.licenses/npm/@actions/cache.dep.yml
generated
2
.licenses/npm/@actions/cache.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/cache"
|
||||
version: 6.1.0
|
||||
version: 6.2.0
|
||||
type: npm
|
||||
summary: Actions cache lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/cache
|
||||
|
||||
20
.licenses/npm/@actions/glob-0.6.1.dep.yml
generated
20
.licenses/npm/@actions/glob-0.6.1.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@actions/glob"
|
||||
version: 0.6.1
|
||||
type: npm
|
||||
summary: Actions glob lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/glob
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |-
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
notices: []
|
||||
8
.licenses/npm/@azure/abort-controller.dep.yml
generated
8
.licenses/npm/@azure/abort-controller.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/abort-controller"
|
||||
version: 2.1.2
|
||||
version: 2.2.0
|
||||
type: npm
|
||||
summary: Microsoft Azure SDK for JavaScript - Aborter
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/abort-controller/README.md
|
||||
@@ -8,9 +8,9 @@ license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Copyright (c) 2020 Microsoft
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,7 +22,7 @@ licenses:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
|
||||
4
.licenses/npm/@azure/core-auth.dep.yml
generated
4
.licenses/npm/@azure/core-auth.dep.yml
generated
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: "@azure/core-auth"
|
||||
version: 1.10.1
|
||||
version: 1.11.0
|
||||
type: npm
|
||||
summary: Provides low-level interfaces and helper methods for authentication in Azure
|
||||
SDK
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-auth/README.md
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-auth/README.md
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
2
.licenses/npm/@azure/core-client.dep.yml
generated
2
.licenses/npm/@azure/core-client.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-client"
|
||||
version: 1.10.2
|
||||
version: 1.11.0
|
||||
type: npm
|
||||
summary: Core library for interfacing with AutoRest generated code
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/
|
||||
|
||||
4
.licenses/npm/@azure/core-http-compat.dep.yml
generated
4
.licenses/npm/@azure/core-http-compat.dep.yml
generated
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: "@azure/core-http-compat"
|
||||
version: 2.4.0
|
||||
version: 2.5.0
|
||||
type: npm
|
||||
summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages.
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-http-compat/
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
8
.licenses/npm/@azure/core-paging.dep.yml
generated
8
.licenses/npm/@azure/core-paging.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-paging"
|
||||
version: 1.6.2
|
||||
version: 1.7.0
|
||||
type: npm
|
||||
summary: Core types for paging async iterable iterators
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-paging/README.md
|
||||
@@ -8,9 +8,9 @@ license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Copyright (c) 2020 Microsoft
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,7 +22,7 @@ licenses:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
|
||||
2
.licenses/npm/@azure/core-rest-pipeline.dep.yml
generated
2
.licenses/npm/@azure/core-rest-pipeline.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-rest-pipeline"
|
||||
version: 1.24.0
|
||||
version: 1.25.0
|
||||
type: npm
|
||||
summary: Isomorphic client library for making HTTP requests in node.js and browser.
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-rest-pipeline/README.md
|
||||
|
||||
2
.licenses/npm/@azure/core-tracing.dep.yml
generated
2
.licenses/npm/@azure/core-tracing.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-tracing"
|
||||
version: 1.3.1
|
||||
version: 1.4.0
|
||||
type: npm
|
||||
summary: Provides low-level interfaces and helper methods for tracing in Azure SDK
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-tracing/README.md
|
||||
|
||||
2
.licenses/npm/@azure/core-util.dep.yml
generated
2
.licenses/npm/@azure/core-util.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-util"
|
||||
version: 1.13.1
|
||||
version: 1.14.0
|
||||
type: npm
|
||||
summary: Core library for shared utility methods
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-util/
|
||||
|
||||
4
.licenses/npm/@azure/core-xml.dep.yml
generated
4
.licenses/npm/@azure/core-xml.dep.yml
generated
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: "@azure/core-xml"
|
||||
version: 1.5.1
|
||||
version: 1.6.0
|
||||
type: npm
|
||||
summary: Core library for interacting with XML payloads
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-xml/README.md
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
2
.licenses/npm/@azure/logger.dep.yml
generated
2
.licenses/npm/@azure/logger.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/logger"
|
||||
version: 1.3.0
|
||||
version: 1.4.0
|
||||
type: npm
|
||||
summary: Microsoft Azure SDK for JavaScript - Logger
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/logger/README.md
|
||||
|
||||
2
.licenses/npm/@nodable/entities.dep.yml
generated
2
.licenses/npm/@nodable/entities.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@nodable/entities"
|
||||
version: 2.2.0
|
||||
version: 3.0.0
|
||||
type: npm
|
||||
summary: Entity parser for XML, HTML, External entites with security and NCR control
|
||||
homepage:
|
||||
|
||||
2
.licenses/npm/@typespec/ts-http-runtime.dep.yml
generated
2
.licenses/npm/@typespec/ts-http-runtime.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@typespec/ts-http-runtime"
|
||||
version: 0.3.6
|
||||
version: 0.3.7
|
||||
type: npm
|
||||
summary: Isomorphic client library for making HTTP requests in node.js and browser.
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/ts-http-runtime/README.md
|
||||
|
||||
55
.licenses/npm/balanced-match-1.0.2.dep.yml
generated
55
.licenses/npm/balanced-match-1.0.2.dep.yml
generated
@@ -1,55 +0,0 @@
|
||||
---
|
||||
name: balanced-match
|
||||
version: 1.0.2
|
||||
type: npm
|
||||
summary: Match balanced character pairs, like "{" and "}"
|
||||
homepage: https://github.com/juliangruber/balanced-match
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
@@ -4,7 +4,7 @@ version: 4.0.4
|
||||
type: npm
|
||||
summary: Match balanced character pairs, like "{" and "}"
|
||||
homepage:
|
||||
license: other
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |
|
||||
55
.licenses/npm/brace-expansion-1.1.15.dep.yml
generated
55
.licenses/npm/brace-expansion-1.1.15.dep.yml
generated
@@ -1,55 +0,0 @@
|
||||
---
|
||||
name: brace-expansion
|
||||
version: 1.1.15
|
||||
type: npm
|
||||
summary: Brace expansion as known from sh/bash
|
||||
homepage: https://github.com/juliangruber/brace-expansion
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: brace-expansion
|
||||
version: 5.0.7
|
||||
version: 5.0.8
|
||||
type: npm
|
||||
summary: Brace expansion as known from sh/bash
|
||||
homepage:
|
||||
license: other
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
31
.licenses/npm/concat-map.dep.yml
generated
31
.licenses/npm/concat-map.dep.yml
generated
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: concat-map
|
||||
version: 0.0.1
|
||||
type: npm
|
||||
summary: concatenative mapdashery
|
||||
homepage: https://github.com/substack/node-concat-map#readme
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
This software is released under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
- sources: README.markdown
|
||||
text: MIT
|
||||
notices: []
|
||||
2
.licenses/npm/fast-xml-builder.dep.yml
generated
2
.licenses/npm/fast-xml-builder.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: fast-xml-builder
|
||||
version: 1.2.1
|
||||
version: 1.3.0
|
||||
type: npm
|
||||
summary: Build XML from JSON without C/C++ based libraries
|
||||
homepage:
|
||||
|
||||
2
.licenses/npm/fast-xml-parser.dep.yml
generated
2
.licenses/npm/fast-xml-parser.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: fast-xml-parser
|
||||
version: 5.9.3
|
||||
version: 5.10.1
|
||||
type: npm
|
||||
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
|
||||
homepage:
|
||||
|
||||
2
.licenses/npm/is-unsafe.dep.yml
generated
2
.licenses/npm/is-unsafe.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: is-unsafe
|
||||
version: 1.0.1
|
||||
version: 2.0.0
|
||||
type: npm
|
||||
summary: Zero-dependency, DOM-free, pure predicate for detecting unsafe strings across
|
||||
HTML, XML, SVG, SQL, SHELL, and REGEX contexts
|
||||
|
||||
26
.licenses/npm/minimatch-3.1.5.dep.yml
generated
26
.licenses/npm/minimatch-3.1.5.dep.yml
generated
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: minimatch
|
||||
version: 3.1.5
|
||||
type: npm
|
||||
summary: a glob matcher in javascript
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: minimatch
|
||||
version: 10.2.5
|
||||
version: 10.2.6
|
||||
type: npm
|
||||
summary: a glob matcher in javascript
|
||||
homepage:
|
||||
2
.licenses/npm/path-expression-matcher.dep.yml
generated
2
.licenses/npm/path-expression-matcher.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: path-expression-matcher
|
||||
version: 1.6.1
|
||||
version: 1.6.2
|
||||
type: npm
|
||||
summary: Efficient path tracking and pattern matching for XML/JSON parsers
|
||||
homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme
|
||||
|
||||
2
.licenses/npm/undici.dep.yml
generated
2
.licenses/npm/undici.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: undici
|
||||
version: 6.27.0
|
||||
version: 6.28.0
|
||||
type: npm
|
||||
summary: An HTTP/1.1 client, written from scratch for Node.js
|
||||
homepage: https://undici.nodejs.org
|
||||
|
||||
25
.licenses/npm/xml-naming.dep.yml
generated
25
.licenses/npm/xml-naming.dep.yml
generated
@@ -1,12 +1,35 @@
|
||||
---
|
||||
name: xml-naming
|
||||
version: 0.1.0
|
||||
version: 0.3.0
|
||||
type: npm
|
||||
summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens —
|
||||
for XML 1.0 and 1.1
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Natural Intelligence
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: MIT
|
||||
notices: []
|
||||
|
||||
205
README.md
205
README.md
@@ -20,7 +20,18 @@ This action allows you to work with Java and Scala projects.
|
||||
|
||||
## What's new in V6
|
||||
|
||||
- **Migrated to ESM** to enable support for the latest `@actions/*` package versions. This is an internal implementation change only. No changes are required to your workflow configuration, and the action's behavior is unchanged. Existing workflows continue to work as before.
|
||||
> [!NOTE]
|
||||
> V6 is still in development (`main` branch) and is not yet recommended for production workflows.
|
||||
|
||||
- **Migrated to ESM** to enable support for the latest `@actions/*` package versions. This is an internal implementation change.
|
||||
|
||||
## Breaking changes in V6
|
||||
|
||||
- **Renamed inputs that accept environment-variable names** to make it clear that their values are not credentials. Replace `server-username`, `server-password`, and `gpg-passphrase` with `server-username-env-var`, `server-password-env-var`, and `gpg-passphrase-env-var`, respectively. The old names remain as deprecated aliases and emit a warning when used.
|
||||
|
||||
- **The GPG passphrase is now passed to the Maven GPG Plugin through an environment variable (`gpg.passphraseEnvName`) instead of the deprecated `gpg.passphrase` server in `settings.xml`.** Set the environment variable name with `gpg-passphrase-env-var`, which defaults to `GPG_PASSPHRASE`. This requires `maven-gpg-plugin` **3.2.0 or newer**; older versions do not honor `gpg.passphraseEnvName` and, because the `gpg.passphrase` server is no longer written, will not pick up the passphrase. Upgrade the plugin to 3.2.0+.
|
||||
|
||||
See [GPG](docs/advanced-usage.md#gpg) for details.
|
||||
|
||||
## Breaking changes in V5
|
||||
|
||||
@@ -37,7 +48,7 @@ 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. 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.
|
||||
|
||||
@@ -45,12 +56,18 @@ For more details, see the full release notes on the [releases page](https://git
|
||||
|
||||
- `check-latest`: Setting this option makes the action to check for the latest available version for the version spec.
|
||||
|
||||
- `force-download`: Set to `true` to always download Java and replace any matching version in the tool cache. This can help make builds reproducible when a runner image has modified a pre-installed JDK, such as its `cacerts` file. Default value: `false`.
|
||||
|
||||
- `set-default`: Set to `false` to install a JDK without making it the default. When `false`, `JAVA_HOME` and `PATH` are not updated, but `JAVA_HOME_<major>_<arch>` is still set so the JDK remains discoverable. Default value: `true`. See [Installing JDK without setting as default](docs/advanced-usage.md#Installing-JDK-without-setting-as-default) for more details.
|
||||
|
||||
- `problem-matcher`: Set to `false` to disable Java problem matcher annotations (compiler diagnostics and uncaught exceptions). Default value: `true`. See [Java problem matcher](docs/advanced-usage.md#java-problem-matcher-compiler-annotations) for details and annotation limits.
|
||||
|
||||
- `verify-signature`: Verifies downloaded Java package signatures when supported by the selected distribution. Currently supported for `temurin` and `microsoft`. If set to `true` for unsupported distributions, the action fails.
|
||||
|
||||
- `verify-signature-public-key`: ASCII-armored GPG public key used to verify the downloaded package signature. Overrides the default bundled key for the selected distribution.
|
||||
|
||||
- `token`: The token used to authenticate when fetching version manifests hosted on GitHub.com. Defaults to `${{ github.token }}` when running on GitHub.com; defaults to an empty string on GitHub Enterprise Server. On GHES, provide a GitHub.com personal access token if manifest requests are rate-limited. See [Using Microsoft distribution on GHES](docs/advanced-usage.md#using-microsoft-distribution-on-ghes) for more details.
|
||||
|
||||
- `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predefined package managers. It can be one of "maven", "gradle" or "sbt".
|
||||
|
||||
- `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.
|
||||
@@ -62,42 +79,52 @@ For more details, see the full release notes on the [releases page](https://git
|
||||
|
||||
- `server-id`: ID of the distributionManagement repository in the pom.xml file. Default is `github`.
|
||||
|
||||
- `server-username`: Environment variable name for the username for authentication to the Apache Maven repository. Default is GITHUB_ACTOR.
|
||||
- `server-username-env-var`: Environment variable name for the username for authentication to the Apache Maven repository. Default is GITHUB\_ACTOR.
|
||||
|
||||
- `server-password`: Environment variable name for password or token for authentication to the Apache Maven repository. Default is GITHUB_TOKEN.
|
||||
- `server-password-env-var`: Environment variable name for password or token for authentication to the Apache Maven repository. Default is GITHUB\_TOKEN.
|
||||
|
||||
- `settings-path`: Maven related setting to point to the directory where the settings.xml file will be written. Default is ~/.m2.
|
||||
- `settings-path`: Maven related setting to point to the directory where the settings.xml file will be written. Default is \~/.m2.
|
||||
|
||||
- `gpg-private-key`: GPG private key to import. Default is empty string.
|
||||
|
||||
- `gpg-passphrase`: Environment variable name for the GPG private key passphrase. Default is GPG_PASSPHRASE.
|
||||
- `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
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '25'
|
||||
- run: java --version
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '25'
|
||||
- run: java --version
|
||||
```
|
||||
|
||||
#### Azul Zulu OpenJDK
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu' # See 'Supported distributions' for available options
|
||||
java-version: '25'
|
||||
- run: java --version
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: 'zulu' # See 'Supported distributions' for available options
|
||||
java-version: '25'
|
||||
- run: java --version
|
||||
```
|
||||
|
||||
#### Supported version syntax
|
||||
@@ -129,6 +156,7 @@ Currently, the following distributions are supported:
|
||||
| `corretto` | [Amazon Corretto Build of OpenJDK](https://aws.amazon.com/corretto/) | [`corretto` license](https://aws.amazon.com/corretto/faqs/)
|
||||
| `semeru` | [IBM Semeru Runtime Open Edition](https://developer.ibm.com/languages/java/semeru-runtimes/downloads/) | [`semeru` license](https://openjdk.java.net/legal/gplv2+ce.html) |
|
||||
| `oracle` | [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) | [`oracle` license](https://java.com/freeuselicense)
|
||||
| `oracle-openjdk` | [Oracle OpenJDK](https://jdk.java.net/) | [`oracle-openjdk` license](https://openjdk.org/legal/gplv2+ce.html)
|
||||
| `dragonwell` | [Alibaba Dragonwell JDK](https://dragonwell-jdk.io/) | [`dragonwell` license](https://www.aliyun.com/product/dragonwell/)
|
||||
| `sapmachine` | [SAP SapMachine JDK/JRE](https://sapmachine.io/) | [`sapmachine` license](https://github.com/SAP/SapMachine/blob/sapmachine/LICENSE)
|
||||
| `graalvm` | [Oracle GraalVM](https://www.graalvm.org/) | [`graalvm` license](https://www.oracle.com/downloads/licenses/graal-free-license.html)
|
||||
@@ -140,6 +168,7 @@ Currently, the following distributions are supported:
|
||||
> [!NOTE]
|
||||
> - The different distributors can provide discrepant list of available versions / supported configurations. Please refer to the official documentation to see the list of supported versions.
|
||||
> - AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates. See more details in the [Good-bye AdoptOpenJDK post](https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/).
|
||||
> - Oracle OpenJDK builds are created and hosted by Oracle. After a limited number of releases, Oracle archives these builds and no longer provides security updates. To continue receiving security patches, users must move to Oracle JDK or choose a different vendor.
|
||||
> - For Azul Zulu OpenJDK, architecture `arm64` is mapped to `aarch64` when querying the Azul Metadata API.
|
||||
> - To comply with the GraalVM Free Terms and Conditions (GFTC) license, it is recommended to use GraalVM JDK 17 version 17.0.12, as this is the only version of GraalVM JDK 17 available under the GFTC license. Additionally, it is encouraged to consider upgrading to GraalVM JDK 21, which offers the latest features and improvements.
|
||||
> - GraalVM Community is available as `distribution: 'graalvm-community'` for stable JDK 17 and later releases published on GitHub.
|
||||
@@ -163,24 +192,26 @@ The workflow output `cache-primary-key` exposes the primary cache key computed b
|
||||
|
||||
The cache input is optional, and caching is turned off by default.
|
||||
|
||||
**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 wrapper on every run. This is keyed on `**/.mvn/wrapper/maven-wrapper.properties` as shown above.
|
||||
**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
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'gradle'
|
||||
cache-dependency-path: | # optional
|
||||
sub-project/*.gradle*
|
||||
sub-project/**/gradle-wrapper.properties
|
||||
- run: ./gradlew build --no-daemon
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'gradle'
|
||||
cache-dependency-path: | # optional
|
||||
sub-project/*.gradle*
|
||||
sub-project/**/gradle-wrapper.properties
|
||||
- run: ./gradlew build --no-daemon
|
||||
```
|
||||
Using the `cache: gradle` provides a simple and effective way to cache Gradle dependencies with minimal configuration.
|
||||
|
||||
**Gradle Wrapper:** when `cache: 'gradle'` is enabled, the action also caches and restores the Gradle Wrapper distribution downloaded to `~/.gradle/wrapper` (in addition to the Gradle caches), so wrapper-based (`./gradlew`) builds don't re-download the Gradle distribution. The wrapper distribution is stored in a **separate** cache entry keyed only on `**/gradle-wrapper.properties`, so it stays cached across the frequent `*.gradle*` changes that rotate the main dependency cache key.
|
||||
|
||||
For projects that require more advanced `Gradle` caching features, such as caching build outputs, support for Gradle configuration cache, encrypted cache storage, fine-grained cache control (including options to enable or disable the cache, set it to read-only or write-only, perform automated cleanup, and define custom cache rules), or optimized performance for complex CI workflows, consider using [`gradle/actions/setup-gradle`](https://github.com/gradle/actions/tree/main/setup-gradle).
|
||||
|
||||
For setup details and a comprehensive overview of all available features, visit the [setup-gradle documentation](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md).
|
||||
@@ -188,15 +219,15 @@ For setup details and a comprehensive overview of all available features, visit
|
||||
#### Caching maven dependencies
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'maven'
|
||||
cache-dependency-path: 'sub-project/pom.xml' # optional
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'maven'
|
||||
cache-dependency-path: 'sub-project/pom.xml' # optional
|
||||
- name: Build with Maven
|
||||
run: mvn package --file pom.xml
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -209,17 +240,17 @@ steps:
|
||||
#### Caching sbt dependencies
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'sbt'
|
||||
cache-dependency-path: | # optional
|
||||
sub-project/build.sbt
|
||||
sub-project/project/build.properties
|
||||
- name: Build with SBT
|
||||
run: sbt package
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'sbt'
|
||||
cache-dependency-path: | # optional
|
||||
sub-project/build.sbt
|
||||
sub-project/project/build.properties
|
||||
- name: Build with SBT
|
||||
run: sbt package
|
||||
```
|
||||
|
||||
#### Cache segment restore timeout
|
||||
@@ -229,13 +260,13 @@ Usually, cache gets downloaded in multiple segments of fixed sizes. Sometimes, a
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'gradle'
|
||||
- run: ./gradlew build --no-daemon
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'gradle'
|
||||
- run: ./gradlew build --no-daemon
|
||||
```
|
||||
|
||||
### Check latest
|
||||
@@ -249,13 +280,13 @@ For Java distributions that are not cached on Hosted images, `check-latest` alwa
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
check-latest: true
|
||||
- run: java --version
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
check-latest: true
|
||||
- run: java --version
|
||||
```
|
||||
|
||||
### Testing against different Java versions
|
||||
@@ -268,9 +299,9 @@ jobs:
|
||||
java: [ '8', '11', '17', '21', '25' ]
|
||||
name: Java ${{ matrix.Java }} sample
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: '<distribution>'
|
||||
java-version: ${{ matrix.java }}
|
||||
@@ -279,11 +310,11 @@ jobs:
|
||||
|
||||
### Install multiple JDKs
|
||||
|
||||
All configured Java versions are added to the PATH. The last one added to the PATH (i.e., the last JDK set up by this action) will be used as the default and available globally. Other Java versions can be accessed through environment variables such as 'JAVA_HOME_{{ MAJOR_VERSION }}_{{ ARCHITECTURE }}'. To use a specific Java version, set the JAVA_HOME environment variable accordingly and prepend its bin directory to the PATH to ensure it takes priority during execution.
|
||||
All configured Java versions are added to the PATH. The last one added to the PATH (i.e., the last JDK set up by this action) will be used as the default and available globally. Other Java versions can be accessed through environment variables such as 'JAVA\_HOME\_{{ MAJOR\_VERSION }}\_{{ ARCHITECTURE }}'. To use a specific Java version, set the JAVA\_HOME environment variable accordingly and prepend its bin directory to the PATH to ensure it takes priority during execution.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/setup-java@v5
|
||||
steps:
|
||||
- uses: actions/setup-java@v6
|
||||
with:
|
||||
distribution: '<distribution>'
|
||||
java-version: |
|
||||
@@ -298,39 +329,31 @@ In the example above multiple JDKs are installed for the same job. The result af
|
||||
### Advanced Configuration
|
||||
|
||||
- [Selecting a Java distribution](docs/advanced-usage.md#Selecting-a-Java-distribution)
|
||||
- [Eclipse Temurin](docs/advanced-usage.md#Eclipse-Temurin)
|
||||
- [Adopt](docs/advanced-usage.md#Adopt)
|
||||
- [Zulu](docs/advanced-usage.md#Zulu)
|
||||
- [Liberica](docs/advanced-usage.md#Liberica)
|
||||
- [Liberica Native Image Kit](docs/advanced-usage.md#Liberica-Native-Image-Kit)
|
||||
- [Microsoft](docs/advanced-usage.md#Microsoft)
|
||||
- [Amazon Corretto](docs/advanced-usage.md#Amazon-Corretto)
|
||||
- [Oracle](docs/advanced-usage.md#Oracle)
|
||||
- [Alibaba Dragonwell](docs/advanced-usage.md#Alibaba-Dragonwell)
|
||||
- [SapMachine](docs/advanced-usage.md#SapMachine)
|
||||
- [GraalVM](docs/advanced-usage.md#GraalVM)
|
||||
- [JetBrains](docs/advanced-usage.md#JetBrains)
|
||||
- [Tencent Kona](docs/advanced-usage.md#Tencent-Kona)
|
||||
- [Eclipse Temurin](docs/advanced-usage.md#Eclipse-Temurin)
|
||||
- [Adopt](docs/advanced-usage.md#Adopt)
|
||||
- [Zulu](docs/advanced-usage.md#Zulu)
|
||||
- [Liberica](docs/advanced-usage.md#Liberica)
|
||||
- [Liberica Native Image Kit](docs/advanced-usage.md#Liberica-Native-Image-Kit)
|
||||
- [Microsoft](docs/advanced-usage.md#Microsoft)
|
||||
- [Amazon Corretto](docs/advanced-usage.md#Amazon-Corretto)
|
||||
- [Oracle](docs/advanced-usage.md#Oracle)
|
||||
- [Alibaba Dragonwell](docs/advanced-usage.md#Alibaba-Dragonwell)
|
||||
- [SapMachine](docs/advanced-usage.md#SapMachine)
|
||||
- [GraalVM](docs/advanced-usage.md#GraalVM)
|
||||
- [JetBrains](docs/advanced-usage.md#JetBrains)
|
||||
- [Tencent Kona](docs/advanced-usage.md#Tencent-Kona)
|
||||
- [Installing custom Java package type](docs/advanced-usage.md#Installing-custom-Java-package-type)
|
||||
- [Installing custom Java architecture](docs/advanced-usage.md#Installing-custom-Java-architecture)
|
||||
- [Installing custom Java distribution from local file](docs/advanced-usage.md#Installing-Java-from-local-file)
|
||||
- [Testing against different Java distributions](docs/advanced-usage.md#Testing-against-different-Java-distributions)
|
||||
- [Testing against different platforms](docs/advanced-usage.md#Testing-against-different-platforms)
|
||||
- [Publishing using Apache Maven](docs/advanced-usage.md#Publishing-using-Apache-Maven)
|
||||
- [Maven transfer progress (download logs)](docs/advanced-usage.md#maven-transfer-progress-download-logs)
|
||||
- [Publishing using Gradle](docs/advanced-usage.md#Publishing-using-Gradle)
|
||||
- [Hosted Tool Cache](docs/advanced-usage.md#Hosted-Tool-Cache)
|
||||
- [Modifying Maven Toolchains](docs/advanced-usage.md#Modifying-Maven-Toolchains)
|
||||
- [Java Version File](docs/advanced-usage.md#Java-version-file)
|
||||
|
||||
## V2 vs V1
|
||||
|
||||
Examples in this README use `actions/setup-java@v5`, but the main migration note from V1 still applies to all later major versions (`v2`, `v3`, `v4`, and `v5`):
|
||||
|
||||
- Starting with V2, the action supports custom distributions. V1 supports only Azul Zulu OpenJDK.
|
||||
- Starting with V2, you must specify distribution along with the version. V1 defaults to Azul Zulu OpenJDK, so only version input is required. Follow [the migration guide](docs/switching-to-v2.md) to switch from V1 to V2.
|
||||
|
||||
For information about the latest releases, recent updates, and newly supported distributions, please refer to the `setup-java` [Releases](https://github.com/actions/setup-java/releases).
|
||||
|
||||
## Recommended permissions
|
||||
|
||||
When using the `setup-java` action in your GitHub Actions workflow, it is recommended to set the following permissions to ensure proper functionality:
|
||||
|
||||
@@ -228,9 +228,40 @@ describe('auth tests', () => {
|
||||
<username>\${env.${username}}</username>
|
||||
<password>\${env.&<>"''"><&}</password>
|
||||
</server>
|
||||
</servers>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>setup-java-gpg</id>
|
||||
<properties>
|
||||
<gpg.passphraseEnvName>${gpgPassphrase}</gpg.passphraseEnvName>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
<activeProfiles>
|
||||
<activeProfile>setup-java-gpg</activeProfile>
|
||||
</activeProfiles>
|
||||
</settings>`;
|
||||
|
||||
expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(
|
||||
expectedSettings
|
||||
);
|
||||
});
|
||||
|
||||
it('does not add a gpg profile when the passphrase env var is the maven-gpg-plugin default', () => {
|
||||
const id = 'packages';
|
||||
const username = 'USER';
|
||||
const password = '&<>"\'\'"><&';
|
||||
const gpgPassphrase = 'MAVEN_GPG_PASSPHRASE';
|
||||
|
||||
const expectedSettings = `<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<interactiveMode>false</interactiveMode>
|
||||
<servers>
|
||||
<server>
|
||||
<id>gpg.passphrase</id>
|
||||
<passphrase>\${env.${gpgPassphrase}}</passphrase>
|
||||
<id>${id}</id>
|
||||
<username>\${env.${username}}</username>
|
||||
<password>\${env.&<>"''"><&}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>`;
|
||||
@@ -239,4 +270,55 @@ describe('auth tests', () => {
|
||||
expectedSettings
|
||||
);
|
||||
});
|
||||
|
||||
it('uses deprecated input aliases and warns', () => {
|
||||
const mockGetInput = core.getInput as jest.MockedFunction<
|
||||
typeof core.getInput
|
||||
>;
|
||||
const mockWarning = core.warning as jest.MockedFunction<
|
||||
typeof core.warning
|
||||
>;
|
||||
mockGetInput.mockImplementation(name =>
|
||||
name === 'server-username' ? 'LEGACY_USERNAME' : ''
|
||||
);
|
||||
|
||||
expect(
|
||||
auth.getInputWithDeprecatedAlias(
|
||||
'server-username-env-var',
|
||||
'server-username',
|
||||
'GITHUB_ACTOR'
|
||||
)
|
||||
).toBe('LEGACY_USERNAME');
|
||||
expect(mockWarning).toHaveBeenCalledWith(
|
||||
"The 'server-username' input is deprecated and may be removed in a future release. Please use 'server-username-env-var' instead."
|
||||
);
|
||||
|
||||
mockGetInput.mockReset();
|
||||
mockWarning.mockReset();
|
||||
});
|
||||
|
||||
it('prefers the replacement input over its deprecated alias', () => {
|
||||
const mockGetInput = core.getInput as jest.MockedFunction<
|
||||
typeof core.getInput
|
||||
>;
|
||||
mockGetInput.mockImplementation(name => {
|
||||
const inputs: Record<string, string> = {
|
||||
'server-password-env-var': 'NEW_PASSWORD',
|
||||
'server-password': 'LEGACY_PASSWORD'
|
||||
};
|
||||
return inputs[name] || '';
|
||||
});
|
||||
|
||||
expect(
|
||||
auth.getInputWithDeprecatedAlias(
|
||||
'server-password-env-var',
|
||||
'server-password',
|
||||
'GITHUB_TOKEN'
|
||||
)
|
||||
).toBe('NEW_PASSWORD');
|
||||
expect(core.warning).toHaveBeenCalled();
|
||||
|
||||
mockGetInput.mockReset();
|
||||
(core.warning as jest.Mock).mockReset();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,10 +166,7 @@ describe('dependency cache', () => {
|
||||
|
||||
await restore('maven', '');
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[
|
||||
join(os.homedir(), '.m2', 'repository'),
|
||||
join(os.homedir(), '.m2', 'wrapper', 'dists')
|
||||
],
|
||||
[join(os.homedir(), '.m2', 'repository')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyGlobHashFiles).toHaveBeenCalledWith(
|
||||
@@ -196,10 +193,7 @@ describe('dependency cache', () => {
|
||||
|
||||
await restore('maven', '');
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[
|
||||
join(os.homedir(), '.m2', 'repository'),
|
||||
join(os.homedir(), '.m2', 'wrapper', 'dists')
|
||||
],
|
||||
[join(os.homedir(), '.m2', 'repository')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyGlobHashFiles).toHaveBeenCalledWith(
|
||||
@@ -214,10 +208,7 @@ describe('dependency cache', () => {
|
||||
|
||||
await restore('maven', '');
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[
|
||||
join(os.homedir(), '.m2', 'repository'),
|
||||
join(os.homedir(), '.m2', 'wrapper', 'dists')
|
||||
],
|
||||
[join(os.homedir(), '.m2', 'repository')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyGlobHashFiles).toHaveBeenCalledWith(
|
||||
@@ -226,6 +217,47 @@ describe('dependency cache', () => {
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
|
||||
});
|
||||
it('restores the maven wrapper distribution cache independently of the main cache', async () => {
|
||||
createDirectory(join(workspace, '.mvn'));
|
||||
createDirectory(join(workspace, '.mvn', 'wrapper'));
|
||||
createFile(
|
||||
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
|
||||
);
|
||||
|
||||
await restore('maven', '');
|
||||
// Main dependency cache no longer carries the wrapper dists path.
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.m2', 'repository')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
|
||||
expect.stringContaining('maven-wrapper')
|
||||
);
|
||||
expect(spyGlobHashFiles).toHaveBeenCalledWith(
|
||||
'**/.mvn/wrapper/maven-wrapper.properties'
|
||||
);
|
||||
});
|
||||
it('skips the maven wrapper cache when no wrapper properties exist', async () => {
|
||||
createFile(join(workspace, 'pom.xml'));
|
||||
spyGlobHashFiles.mockImplementation((pattern: string) =>
|
||||
Promise.resolve(
|
||||
pattern === '**/.mvn/wrapper/maven-wrapper.properties'
|
||||
? ''
|
||||
: 'hash-stub'
|
||||
)
|
||||
);
|
||||
|
||||
await restore('maven', '');
|
||||
// Only the main dependency cache is restored; the wrapper cache path is
|
||||
// never touched because the project does not use mvnw.
|
||||
expect(spyCacheRestore).toHaveBeenCalledTimes(1);
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.m2', 'repository')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('for gradle', () => {
|
||||
it('throws error if no build.gradle found', async () => {
|
||||
@@ -282,6 +314,43 @@ describe('dependency cache', () => {
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
|
||||
});
|
||||
it('restores the gradle wrapper distribution cache independently of the main cache', async () => {
|
||||
createFile(join(workspace, 'build.gradle'));
|
||||
|
||||
await restore('gradle', '');
|
||||
// Main dependency cache no longer carries the wrapper path.
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.gradle', 'caches')],
|
||||
expect.any(String)
|
||||
);
|
||||
// Wrapper distribution is restored on its own, keyed only on the
|
||||
// wrapper properties file.
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.gradle', 'wrapper')],
|
||||
expect.stringContaining('setup-java-')
|
||||
);
|
||||
expect(spyGlobHashFiles).toHaveBeenCalledWith(
|
||||
'**/gradle-wrapper.properties'
|
||||
);
|
||||
});
|
||||
it('skips the gradle wrapper cache when no wrapper properties exist', async () => {
|
||||
createFile(join(workspace, 'build.gradle'));
|
||||
spyGlobHashFiles.mockImplementation((pattern: string) =>
|
||||
Promise.resolve(
|
||||
pattern === '**/gradle-wrapper.properties' ? '' : 'hash-stub'
|
||||
)
|
||||
);
|
||||
|
||||
await restore('gradle', '');
|
||||
// Only the main dependency cache is restored; the wrapper cache path is
|
||||
// never touched because the project does not use the gradle wrapper.
|
||||
expect(spyCacheRestore).toHaveBeenCalledTimes(1);
|
||||
expect(spyCacheRestore).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.gradle', 'caches')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('for sbt', () => {
|
||||
it('throws error if no build.sbt found', async () => {
|
||||
@@ -389,11 +458,16 @@ describe('dependency cache', () => {
|
||||
});
|
||||
describe('save', () => {
|
||||
let spyCacheSave: any;
|
||||
let spyGlobCreate: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
spyCacheSave = (cache.saveCache as any).mockImplementation(
|
||||
(paths: string[], key: string) => Promise.resolve(0)
|
||||
);
|
||||
spyGlobCreate = glob.create as jest.Mock;
|
||||
spyGlobCreate.mockResolvedValue({
|
||||
glob: jest.fn(() => Promise.resolve(['wrapper-path']))
|
||||
});
|
||||
spyWarning.mockImplementation(() => null);
|
||||
});
|
||||
|
||||
@@ -457,6 +531,84 @@ describe('dependency cache', () => {
|
||||
expect.stringMatching(/^Cache saved with the key:.*/)
|
||||
);
|
||||
});
|
||||
it('saves the maven wrapper distribution cache under its own key', 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 '';
|
||||
}
|
||||
});
|
||||
|
||||
await save('maven');
|
||||
expect(spyCacheSave).toHaveBeenCalledWith(
|
||||
['wrapper-path'],
|
||||
'setup-java-maven-wrapper-key'
|
||||
);
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
it('does not save the maven wrapper cache on an exact wrapper hit', 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':
|
||||
case 'cache-matched-key-maven-wrapper':
|
||||
return 'setup-java-maven-wrapper-key';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
await save('maven');
|
||||
expect(spyCacheSave).not.toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
it('does not fail the post step when the wrapper distribution path is missing', async () => {
|
||||
createFile(join(workspace, 'pom.xml'));
|
||||
createDirectory(join(workspace, '.mvn'));
|
||||
createDirectory(join(workspace, '.mvn', 'wrapper'));
|
||||
createFile(
|
||||
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
|
||||
);
|
||||
(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 '';
|
||||
}
|
||||
});
|
||||
spyGlobCreate.mockResolvedValue({
|
||||
glob: jest.fn(() => Promise.resolve([]))
|
||||
});
|
||||
|
||||
await expect(save('maven')).resolves.toBeUndefined();
|
||||
expect(spyCacheSave).not.toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyCacheSave).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.m2', 'repository')],
|
||||
'setup-java-cache-primary-key'
|
||||
);
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('for gradle', () => {
|
||||
it('uploads cache even if no build.gradle found', async () => {
|
||||
@@ -509,6 +661,80 @@ describe('dependency cache', () => {
|
||||
expect.stringMatching(/^Cache saved with the key:.*/)
|
||||
);
|
||||
});
|
||||
it('saves the gradle wrapper distribution cache under its own key', async () => {
|
||||
createFile(join(workspace, 'build.gradle'));
|
||||
(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-gradle-wrapper':
|
||||
return 'setup-java-gradle-wrapper-key';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
await save('gradle');
|
||||
expect(spyCacheSave).toHaveBeenCalledWith(
|
||||
['wrapper-path'],
|
||||
'setup-java-gradle-wrapper-key'
|
||||
);
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
it('does not save the gradle wrapper cache on an exact wrapper hit', async () => {
|
||||
createFile(join(workspace, 'build.gradle'));
|
||||
(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-gradle-wrapper':
|
||||
case 'cache-matched-key-gradle-wrapper':
|
||||
return 'setup-java-gradle-wrapper-key';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
await save('gradle');
|
||||
expect(spyCacheSave).not.toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.gradle', 'wrapper')],
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
it('does not fail the post step when the wrapper distribution path is missing', async () => {
|
||||
createFile(join(workspace, 'build.gradle'));
|
||||
createFile(join(workspace, 'gradle-wrapper.properties'));
|
||||
(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-gradle-wrapper':
|
||||
return 'setup-java-gradle-wrapper-key';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
spyGlobCreate.mockResolvedValue({
|
||||
glob: jest.fn(() => Promise.resolve([]))
|
||||
});
|
||||
|
||||
await expect(save('gradle')).resolves.toBeUndefined();
|
||||
expect(spyCacheSave).not.toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.gradle', 'wrapper')],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(spyCacheSave).toHaveBeenCalledWith(
|
||||
[join(os.homedir(), '.gradle', 'caches')],
|
||||
'setup-java-cache-primary-key'
|
||||
);
|
||||
expect(spyWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('for sbt', () => {
|
||||
it('uploads cache even if no build.sbt found', async () => {
|
||||
|
||||
11
__tests__/cache/maven2/.gitignore
vendored
Normal file
11
__tests__/cache/maven2/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
15
__tests__/cache/maven2/pom.xml
vendored
Normal file
15
__tests__/cache/maven2/pom.xml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.github.actions</groupId>
|
||||
<artifactId>setup-java-maven2-example</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
1
__tests__/cache/sbt2/.gitignore
vendored
Normal file
1
__tests__/cache/sbt2/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
3
__tests__/cache/sbt2/build.sbt
vendored
Normal file
3
__tests__/cache/sbt2/build.sbt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
ThisBuild / scalaVersion := "2.12.15"
|
||||
|
||||
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1"
|
||||
39
__tests__/check-dir.sh
Executable file
39
__tests__/check-dir.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
# Assert whether a directory exists, for use in the e2e cache workflows.
|
||||
#
|
||||
# Usage: check-dir.sh <dir> [present|absent]
|
||||
#
|
||||
# present (default): fail if <dir> does NOT exist, otherwise list its contents.
|
||||
# absent: fail if <dir> DOES exist.
|
||||
#
|
||||
# Call with already-expanded paths (e.g. "$HOME/.gradle/caches") to avoid
|
||||
# tilde-expansion pitfalls.
|
||||
set -eu
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage: check-dir.sh <dir> [present|absent]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
dir=$1
|
||||
mode=${2:-present}
|
||||
|
||||
case "$mode" in
|
||||
present)
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "::error::The $dir directory does not exist unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
ls "$dir"
|
||||
;;
|
||||
absent)
|
||||
if [ -d "$dir" ]; then
|
||||
echo "::error::The $dir directory exists unexpectedly"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unknown mode '$mode' (expected 'present' or 'absent')"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
130
__tests__/checksum.test.ts
Normal file
130
__tests__/checksum.test.ts
Normal 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'});
|
||||
});
|
||||
});
|
||||
@@ -357,6 +357,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', () => {
|
||||
@@ -443,6 +457,35 @@ describe('setupJava', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should download java when force-download is enabled, even if the version is cached', async () => {
|
||||
mockJavaBase = new EmptyJavaBase({
|
||||
version: actualJavaVersion,
|
||||
architecture: 'x86',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false,
|
||||
forceDownload: true
|
||||
});
|
||||
const findInToolcache = jest.fn(() => ({
|
||||
version: actualJavaVersion,
|
||||
path: javaPathInstalled
|
||||
}));
|
||||
mockJavaBase['findInToolcache'] = findInToolcache;
|
||||
|
||||
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
||||
version: actualJavaVersion,
|
||||
path: javaPathInstalled
|
||||
});
|
||||
|
||||
expect(findInToolcache).not.toHaveBeenCalled();
|
||||
expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...');
|
||||
expect(spyCoreInfo).toHaveBeenCalledWith(
|
||||
`Java ${actualJavaVersion} was downloaded`
|
||||
);
|
||||
expect(spyCoreInfo).not.toHaveBeenCalledWith(
|
||||
`Resolved Java ${actualJavaVersion} from tool-cache`
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
{
|
||||
@@ -576,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([
|
||||
[
|
||||
{
|
||||
@@ -763,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 () => {
|
||||
|
||||
114
__tests__/distributors/distribution-factory.test.ts
Normal file
114
__tests__/distributors/distribution-factory.test.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
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';
|
||||
|
||||
const installerOptions = (packageType: string, version = '25') => ({
|
||||
version,
|
||||
architecture: 'x64',
|
||||
packageType,
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
describe('getJavaDistribution', () => {
|
||||
it.each([
|
||||
'adopt',
|
||||
'adopt-hotspot',
|
||||
'adopt-openj9',
|
||||
'temurin',
|
||||
'zulu',
|
||||
'liberica',
|
||||
'liberica-nik',
|
||||
'microsoft',
|
||||
'semeru',
|
||||
'corretto',
|
||||
'oracle',
|
||||
'dragonwell',
|
||||
'sapmachine',
|
||||
'graalvm',
|
||||
'graalvm-community',
|
||||
'jetbrains',
|
||||
'kona',
|
||||
'oracle-openjdk'
|
||||
])('uses the shared retrying HTTP client for %s', distributionName => {
|
||||
const distribution = getJavaDistribution(distributionName, {
|
||||
version: '21',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
expect(distribution).not.toBeNull();
|
||||
expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient);
|
||||
});
|
||||
|
||||
it.each(
|
||||
Object.entries(JAVA_PACKAGE_CAPABILITIES).flatMap(
|
||||
([distributionName, packageTypes]) =>
|
||||
packageTypes.map(packageType => [distributionName, packageType])
|
||||
)
|
||||
)('accepts %s with java-package %s', (distributionName, packageType) => {
|
||||
expect(
|
||||
getJavaDistribution(
|
||||
distributionName,
|
||||
installerOptions(packageType as string)
|
||||
)
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it.each(Object.entries(JAVA_PACKAGE_CAPABILITIES))(
|
||||
'rejects unsupported java-package values for %s',
|
||||
(distributionName, packageTypes) => {
|
||||
expect(() =>
|
||||
getJavaDistribution(distributionName, installerOptions('jdk+typo'))
|
||||
).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", () => {
|
||||
expect(() =>
|
||||
getJavaDistribution('zulu', installerOptions('jdk+jmods'))
|
||||
).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",
|
||||
version => {
|
||||
expect(() =>
|
||||
getJavaDistribution(
|
||||
JavaDistribution.Temurin,
|
||||
installerOptions('jdk+jmods', version)
|
||||
)
|
||||
).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",
|
||||
version => {
|
||||
expect(
|
||||
getJavaDistribution(
|
||||
JavaDistribution.Temurin,
|
||||
installerOptions('jdk+jmods', version)
|
||||
)
|
||||
).not.toBeNull();
|
||||
}
|
||||
);
|
||||
|
||||
it('preserves unsupported distribution handling', () => {
|
||||
expect(
|
||||
getJavaDistribution(
|
||||
'not-a-distribution',
|
||||
installerOptions('not-a-package')
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -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'
|
||||
);
|
||||
@@ -166,6 +174,29 @@ describe('GraalVMDistribution', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setJavaDefault', () => {
|
||||
it('should set GRAALVM_HOME for Oracle GraalVM', () => {
|
||||
(distribution as any).setJavaDefault('17.0.5', '/cached/java/path');
|
||||
|
||||
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||
'GRAALVM_HOME',
|
||||
'/cached/java/path'
|
||||
);
|
||||
});
|
||||
|
||||
it('should set GRAALVM_HOME for GraalVM Community', () => {
|
||||
(communityDistribution as any).setJavaDefault(
|
||||
'17.0.5',
|
||||
'/cached/java/path'
|
||||
);
|
||||
|
||||
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||
'GRAALVM_HOME',
|
||||
'/cached/java/path'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadTool', () => {
|
||||
const javaRelease = {
|
||||
version: '17.0.5',
|
||||
@@ -384,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 () => {
|
||||
@@ -399,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'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -442,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'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -614,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 () => {
|
||||
@@ -853,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');
|
||||
@@ -953,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'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1128,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 () => {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -208,6 +208,29 @@ describe('setupJava', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('java is unpacked from jdkfile when force-download is enabled', async () => {
|
||||
const inputs = {
|
||||
version: actualJavaVersion,
|
||||
architecture: 'x86',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false,
|
||||
forceDownload: true
|
||||
};
|
||||
|
||||
mockJavaBase = new LocalDistribution(inputs, expectedJdkFile);
|
||||
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
||||
version: actualJavaVersion,
|
||||
path: javaPath
|
||||
});
|
||||
|
||||
expect(spyGetToolcachePath).not.toHaveBeenCalled();
|
||||
expect(spyUtilsExtractJdkFile).toHaveBeenCalledWith(expectedJdkFile);
|
||||
expect(spyTcCacheDir).toHaveBeenCalled();
|
||||
expect(spyCoreInfo).not.toHaveBeenCalledWith(
|
||||
`Resolved Java ${actualJavaVersion} from tool-cache`
|
||||
);
|
||||
});
|
||||
|
||||
it("java is resolved from toolcache, jdkfile doesn't exist", async () => {
|
||||
const inputs = {
|
||||
version: actualJavaVersion,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
246
__tests__/distributors/openjdk-installer.test.ts
Normal file
246
__tests__/distributors/openjdk-installer.test.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
|
||||
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)
|
||||
}));
|
||||
|
||||
const {OpenJdkDistribution} =
|
||||
await import('../../src/distributions/openjdk/installer.js');
|
||||
const {getJavaDistribution} =
|
||||
await import('../../src/distributions/distribution-factory.js');
|
||||
|
||||
const homePage = `
|
||||
<a href="/26/">JDK 26</a>
|
||||
<a href="/27/">JDK
|
||||
27</a>
|
||||
`;
|
||||
const currentPage = `
|
||||
<a href="https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz">tar.gz</a>
|
||||
<a href="https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-aarch64_bin.tar.gz">tar.gz</a>
|
||||
`;
|
||||
const earlyAccessPage = `
|
||||
<a href="https://download.java.net/java/early_access/jdk27/32/GPL/openjdk-27-ea+32_linux-x64_bin.tar.gz">tar.gz</a>
|
||||
`;
|
||||
const archivePage = `
|
||||
<a href="https://download.java.net/java/GA/jdk26.0.1/hash/8/GPL/openjdk-26.0.1_linux-x64_bin.tar.gz">tar.gz</a>
|
||||
<a href="https://download.java.net/java/GA/jdk25/hash/36/GPL/openjdk-25_linux-x64_bin.tar.gz">tar.gz</a>
|
||||
<a href="https://download.java.net/java/GA/jdk18.0.1.1/hash/2/GPL/openjdk-18.0.1.1_linux-x64_bin.tar.gz">tar.gz</a>
|
||||
<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',
|
||||
architecture = 'x64',
|
||||
packageType = 'jdk',
|
||||
useFixturePlatform = true
|
||||
) {
|
||||
const distribution = new OpenJdkDistribution({
|
||||
version,
|
||||
architecture,
|
||||
packageType,
|
||||
checkLatest: false
|
||||
});
|
||||
if (useFixturePlatform) {
|
||||
distribution['getPlatform'] = jest.fn(() => 'linux');
|
||||
}
|
||||
return distribution;
|
||||
}
|
||||
|
||||
describe('OpenJdkDistribution', () => {
|
||||
let getSpy: jest.SpiedFunction<HttpClient['get']>;
|
||||
|
||||
beforeEach(() => {
|
||||
getSpy = jest
|
||||
.spyOn(HttpClient.prototype, 'get')
|
||||
.mockImplementation(async url => {
|
||||
const pages: Record<string, string> = {
|
||||
'https://jdk.java.net/': homePage,
|
||||
'https://jdk.java.net/26/': currentPage,
|
||||
'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 {
|
||||
message: {statusCode: 200},
|
||||
readBody: async () => checksumPages[url] ?? 'e'.repeat(64)
|
||||
} as Awaited<ReturnType<HttpClient['get']>>;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('resolves the newest matching GA release', async () => {
|
||||
const result = await createDistribution()['findPackageForDownload']('26');
|
||||
|
||||
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',
|
||||
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 () => {
|
||||
const result =
|
||||
await createDistribution('26.0.1')['findPackageForDownload']('26.0.1');
|
||||
|
||||
expect(result.version).toBe('26.0.1+8');
|
||||
expect(result.url).toContain('/openjdk-26.0.1_linux-x64_bin.tar.gz');
|
||||
});
|
||||
|
||||
it('resolves an exact GA build', async () => {
|
||||
const result =
|
||||
await createDistribution('26.0.2+10')['findPackageForDownload'](
|
||||
'26.0.2+10'
|
||||
);
|
||||
|
||||
expect(result.version).toBe('26.0.2+10');
|
||||
});
|
||||
|
||||
it('resolves an exact build from a legacy archive heading', async () => {
|
||||
const result =
|
||||
await createDistribution('9.0.4+11')['findPackageForDownload'](
|
||||
'9.0.4+11'
|
||||
);
|
||||
|
||||
expect(result.version).toBe('9.0.4+11');
|
||||
expect(result.url).toContain('/binaries/openjdk-9.0.4_linux-x64_bin');
|
||||
});
|
||||
|
||||
it('resolves a four-field Java version', async () => {
|
||||
const result =
|
||||
await createDistribution('18.0.1.1')['findPackageForDownload'](
|
||||
'18.0.1+1'
|
||||
);
|
||||
|
||||
expect(result.version).toBe('18.0.1+1');
|
||||
expect(result.url).toContain('/openjdk-18.0.1.1_linux-x64_bin.tar.gz');
|
||||
});
|
||||
|
||||
it('resolves an early-access release without requesting the archive', async () => {
|
||||
const result =
|
||||
await createDistribution('27-ea')['findPackageForDownload']('27');
|
||||
|
||||
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',
|
||||
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 () => {
|
||||
await expect(
|
||||
createDistribution()['findPackageForDownload']('24')
|
||||
).rejects.toThrow(
|
||||
"No matching version found for SemVer '24'.\nDistribution: Oracle OpenJDK"
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['jre', 'Oracle OpenJDK provides only the `jdk` package type'],
|
||||
['jdk+fx', 'Oracle OpenJDK provides only the `jdk` package type']
|
||||
])('rejects the %s package type', async (packageType, message) => {
|
||||
await expect(
|
||||
createDistribution('26', 'x64', packageType)['findPackageForDownload'](
|
||||
'26'
|
||||
)
|
||||
).rejects.toThrow(message);
|
||||
});
|
||||
|
||||
it('rejects unsupported architectures', async () => {
|
||||
await expect(
|
||||
createDistribution('26', 'x86')['findPackageForDownload']('26')
|
||||
).rejects.toThrow('Unsupported architecture: x86');
|
||||
});
|
||||
|
||||
it('maps supported platforms', () => {
|
||||
const distribution = createDistribution('26', 'x64', 'jdk', false);
|
||||
|
||||
expect(distribution['getPlatform']('linux')).toBe('linux');
|
||||
expect(distribution['getPlatform']('darwin')).toBe('macos');
|
||||
expect(distribution['getPlatform']('win32')).toBe('windows');
|
||||
expect(() => distribution['getPlatform']('freebsd')).toThrow(
|
||||
"Platform 'freebsd' is not supported"
|
||||
);
|
||||
});
|
||||
|
||||
it('parses legacy platform names and archive formats', () => {
|
||||
const distribution = createDistribution();
|
||||
const macRelease = distribution['parseReleases'](
|
||||
'<a href="https://download.java.net/java/GA/jdk16/hash/7/GPL/openjdk-16_osx-x64_bin.tar.gz">tar.gz</a>',
|
||||
'macos',
|
||||
'x64'
|
||||
);
|
||||
const windowsRelease = distribution['parseReleases'](
|
||||
'<a href="https://download.java.net/java/GA/jdk10/hash/13/openjdk-10.0.2_windows-x64_bin.tar.gz">tar.gz</a>',
|
||||
'windows',
|
||||
'x64'
|
||||
);
|
||||
|
||||
expect(macRelease[0].version).toBe('16.0.0+7');
|
||||
expect(windowsRelease[0].version).toBe('10.0.2+13');
|
||||
expect(windowsRelease[0].url.endsWith('.tar.gz')).toBe(true);
|
||||
});
|
||||
|
||||
it('is registered in the distribution factory', () => {
|
||||
const distribution = getJavaDistribution('oracle-openjdk', {
|
||||
version: '26',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
expect(distribution).toBeInstanceOf(OpenJdkDistribution);
|
||||
});
|
||||
});
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {TemurinImplementation as TemurinImplementationType} from '../../src
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import manifestData from '../data/temurin.json' with {type: 'json'};
|
||||
|
||||
@@ -119,6 +120,16 @@ describe('getAvailableVersions', () => {
|
||||
TemurinImplementation.Hotspot,
|
||||
'os=mac&architecture=x64&image_type=jdk&release_type=ga&jvm_impl=hotspot&page_size=20&page=0'
|
||||
],
|
||||
[
|
||||
{
|
||||
version: '25',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk+jmods',
|
||||
checkLatest: false
|
||||
},
|
||||
TemurinImplementation.Hotspot,
|
||||
'os=mac&architecture=x64&image_type=jdk&release_type=ga&jvm_impl=hotspot&page_size=20&page=0'
|
||||
],
|
||||
[
|
||||
{
|
||||
version: '16',
|
||||
@@ -169,6 +180,27 @@ describe('getAvailableVersions', () => {
|
||||
}
|
||||
);
|
||||
|
||||
it('requests the JMOD image type', async () => {
|
||||
const distribution = new TemurinDistribution(
|
||||
{
|
||||
version: '25',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk+jmods',
|
||||
checkLatest: false
|
||||
},
|
||||
TemurinImplementation.Hotspot
|
||||
);
|
||||
distribution['getPlatformOption'] = () => 'linux';
|
||||
|
||||
await distribution['getAvailableVersions']('jmods');
|
||||
|
||||
expect(spyHttpClient).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'os=linux&architecture=x64&image_type=jmods&release_type=ga'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('load available versions', async () => {
|
||||
const nextPageUrl =
|
||||
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
|
||||
@@ -229,7 +261,12 @@ describe('getAvailableVersions', () => {
|
||||
|
||||
it.each([
|
||||
[TemurinImplementation.Hotspot, 'jdk', 'Java_Temurin-Hotspot_jdk'],
|
||||
[TemurinImplementation.Hotspot, 'jre', 'Java_Temurin-Hotspot_jre']
|
||||
[TemurinImplementation.Hotspot, 'jre', 'Java_Temurin-Hotspot_jre'],
|
||||
[
|
||||
TemurinImplementation.Hotspot,
|
||||
'jdk+jmods',
|
||||
'Java_Temurin-Hotspot_jdk+jmods'
|
||||
]
|
||||
])(
|
||||
'find right toolchain folder',
|
||||
(
|
||||
@@ -310,6 +347,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 () => {
|
||||
@@ -386,6 +431,7 @@ describe('downloadTool', () => {
|
||||
let spyCacheDir: any;
|
||||
let spyReadDirSync: any;
|
||||
let spyRenameWinArchive: any;
|
||||
let spyCopySync: any;
|
||||
|
||||
beforeEach(() => {
|
||||
spyDownloadTool = tc.downloadTool as jest.Mock;
|
||||
@@ -400,6 +446,8 @@ describe('downloadTool', () => {
|
||||
spyReadDirSync.mockReturnValue(['jdk-17'] as any);
|
||||
spyRenameWinArchive = util.renameWinArchive as jest.Mock;
|
||||
spyRenameWinArchive.mockReturnValue('/tmp/jdk.tar.gz.zip');
|
||||
spyCopySync = jest.spyOn(fs, 'cpSync');
|
||||
spyCopySync.mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -433,6 +481,60 @@ describe('downloadTool', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('downloads and adds matching JMODs to the JDK', async () => {
|
||||
spyDownloadTool
|
||||
.mockResolvedValueOnce('/tmp/jdk.tar.gz')
|
||||
.mockResolvedValueOnce('/tmp/jmods.tar.gz');
|
||||
spyExtractJdkFile
|
||||
.mockResolvedValueOnce('/tmp/extracted')
|
||||
.mockResolvedValueOnce('/tmp/extracted-jmods');
|
||||
spyReadDirSync
|
||||
.mockReturnValueOnce(['jdk-25'] as any)
|
||||
.mockReturnValueOnce(['jdk-25-jmods'] as any);
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
|
||||
|
||||
const distribution = new TemurinDistribution(
|
||||
{
|
||||
version: '25',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk+jmods',
|
||||
checkLatest: false
|
||||
},
|
||||
TemurinImplementation.Hotspot
|
||||
);
|
||||
distribution['resolvePackage'] = jest.fn().mockResolvedValue({
|
||||
version: '25.0.3+9',
|
||||
url: 'https://example.com/jmods.tar.gz'
|
||||
});
|
||||
|
||||
await distribution['downloadTool']({
|
||||
version: '25.0.3+9',
|
||||
url: 'https://example.com/jdk.tar.gz'
|
||||
});
|
||||
|
||||
expect(distribution['resolvePackage']).toHaveBeenCalledWith(
|
||||
'25.0.3+9',
|
||||
'jmods'
|
||||
);
|
||||
expect(spyDownloadTool).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'https://example.com/jmods.tar.gz'
|
||||
);
|
||||
expect(spyCopySync).toHaveBeenCalledWith(
|
||||
path.join('/tmp/extracted-jmods', 'jdk-25-jmods'),
|
||||
process.platform === 'darwin'
|
||||
? path.join('/tmp/extracted', 'jdk-25', 'Contents', 'Home', 'jmods')
|
||||
: path.join('/tmp/extracted', 'jdk-25', 'jmods'),
|
||||
{recursive: true}
|
||||
);
|
||||
expect(spyCacheDir).toHaveBeenCalledWith(
|
||||
path.join('/tmp/extracted', 'jdk-25'),
|
||||
'Java_Temurin-Hotspot_jdk+jmods',
|
||||
'25.0.3-9',
|
||||
'x64'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails when signature is missing and verification is enabled', async () => {
|
||||
const distribution = new TemurinDistribution(
|
||||
{
|
||||
|
||||
@@ -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
__tests__/java-package-contract.test.ts
Normal file
82
__tests__/java-package-contract.test.ts
Normal 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] ?? [];
|
||||
}
|
||||
44
__tests__/problem-matcher.test.ts
Normal file
44
__tests__/problem-matcher.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
|
||||
|
||||
const mockGetInput = jest.fn<(...args: any[]) => any>();
|
||||
const mockInfo = jest.fn<(...args: any[]) => any>();
|
||||
const mockDebug = jest.fn<(...args: any[]) => any>();
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
getInput: mockGetInput,
|
||||
info: mockInfo,
|
||||
debug: mockDebug,
|
||||
warning: jest.fn(),
|
||||
setSecret: jest.fn()
|
||||
}));
|
||||
|
||||
const {configureProblemMatcher} = await import('../src/problem-matcher.js');
|
||||
const {INPUT_PROBLEM_MATCHER} = await import('../src/constants.js');
|
||||
|
||||
describe('configureProblemMatcher', () => {
|
||||
let inputs: Record<string, string>;
|
||||
|
||||
beforeEach(() => {
|
||||
inputs = {};
|
||||
mockGetInput.mockImplementation((name: string) => inputs[name] ?? '');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('registers the Java problem matcher by default', () => {
|
||||
configureProblemMatcher('/matchers/java.json');
|
||||
|
||||
expect(mockInfo).toHaveBeenCalledWith('##[add-matcher]/matchers/java.json');
|
||||
});
|
||||
|
||||
it('does not register the Java problem matcher when disabled', () => {
|
||||
inputs[INPUT_PROBLEM_MATCHER] = 'false';
|
||||
|
||||
configureProblemMatcher('/matchers/java.json');
|
||||
|
||||
expect(mockInfo).not.toHaveBeenCalled();
|
||||
expect(mockDebug).toHaveBeenCalledWith('Java problem matcher is disabled');
|
||||
});
|
||||
});
|
||||
251
__tests__/retrying-http-client.test.ts
Normal file
251
__tests__/retrying-http-client.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
409
__tests__/setup-java.test.ts
Normal file
409
__tests__/setup-java.test.ts
Normal file
@@ -0,0 +1,409 @@
|
||||
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(),
|
||||
isCacheFeatureAvailable: 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/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 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;
|
||||
}
|
||||
);
|
||||
(util.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('configures post-install collaborators in order and restores the cache last', async () => {
|
||||
inputs.set('distribution', 'temurin');
|
||||
inputs.set('cache', 'maven');
|
||||
inputs.set('cache-dependency-path', '**/pom.xml');
|
||||
multilineInputs.set('java-version', ['21']);
|
||||
const setupJava = jest.fn(async () => ({
|
||||
version: '21.0.4+7',
|
||||
path: '/opt/java/21'
|
||||
}));
|
||||
(factory.getJavaDistribution as jest.Mock).mockReturnValue({setupJava});
|
||||
|
||||
await run();
|
||||
|
||||
expect(problemMatcher.configureProblemMatcher).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/\.github[/\\]java\.json$/)
|
||||
);
|
||||
expect(cache.restore).toHaveBeenCalledWith('maven', '**/pom.xml');
|
||||
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]
|
||||
);
|
||||
expect(
|
||||
(mavenArgs.configureMavenArgs as jest.Mock).mock.invocationCallOrder[0]
|
||||
).toBeLessThan((cache.restore as jest.Mock).mock.invocationCallOrder[0]);
|
||||
});
|
||||
|
||||
it('skips cache restoration when the cache feature is unavailable', async () => {
|
||||
inputs.set('distribution', 'temurin');
|
||||
inputs.set('cache', 'maven');
|
||||
multilineInputs.set('java-version', ['21']);
|
||||
(util.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('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).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,9 +57,73 @@ const {
|
||||
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],
|
||||
|
||||
29
action.yml
29
action.yml
@@ -13,7 +13,7 @@ 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)'
|
||||
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:
|
||||
@@ -30,6 +30,10 @@ inputs:
|
||||
description: 'Set this option if you want the action to check for the latest available version that satisfies the version spec'
|
||||
required: false
|
||||
default: false
|
||||
force-download:
|
||||
description: 'Set this option to always download Java and replace any matching version in the tool cache'
|
||||
required: false
|
||||
default: false
|
||||
set-default:
|
||||
description: 'Set this option to false if you want to install a JDK but not make it the default. When false, JAVA_HOME and PATH are not updated, but JAVA_HOME_<major>_<arch> is still set.'
|
||||
required: false
|
||||
@@ -46,16 +50,20 @@ inputs:
|
||||
file. Default is `github`'
|
||||
required: false
|
||||
default: 'github'
|
||||
server-username:
|
||||
server-username-env-var:
|
||||
description: 'Environment variable name for the username for authentication
|
||||
to the Apache Maven repository. Default is $GITHUB_ACTOR'
|
||||
required: false
|
||||
default: 'GITHUB_ACTOR'
|
||||
server-password:
|
||||
server-username:
|
||||
description: 'Deprecated alias for server-username-env-var'
|
||||
required: false
|
||||
server-password-env-var:
|
||||
description: 'Environment variable name for password or token for
|
||||
authentication to the Apache Maven repository. Default is $GITHUB_TOKEN'
|
||||
required: false
|
||||
default: 'GITHUB_TOKEN'
|
||||
server-password:
|
||||
description: 'Deprecated alias for server-password-env-var'
|
||||
required: false
|
||||
settings-path:
|
||||
description: 'Path to where the settings.xml file will be written. Default is ~/.m2.'
|
||||
required: false
|
||||
@@ -67,8 +75,11 @@ inputs:
|
||||
description: 'GPG private key to import. Default is empty string.'
|
||||
required: false
|
||||
default: ''
|
||||
gpg-passphrase-env-var:
|
||||
description: 'Environment variable name for the GPG private key passphrase. Defaults to GPG_PASSPHRASE when gpg-private-key is set.'
|
||||
required: false
|
||||
gpg-passphrase:
|
||||
description: 'Environment variable name for the GPG private key passphrase. Defaults to GPG_PASSPHRASE when gpg-private-key is set; ignored otherwise.'
|
||||
description: 'Deprecated alias for gpg-passphrase-env-var'
|
||||
required: false
|
||||
cache:
|
||||
description: 'Name of the build platform to cache dependencies. It can be "maven", "gradle" or "sbt".'
|
||||
@@ -85,7 +96,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'
|
||||
@@ -94,6 +105,10 @@ inputs:
|
||||
description: 'Whether Maven should print artifact download/transfer progress to the build log. When "false" (default) the action sets "-ntp" (--no-transfer-progress) in MAVEN_ARGS to produce cleaner logs. Set to "true" to keep the progress output. Has no effect on non-Maven builds.'
|
||||
required: false
|
||||
default: false
|
||||
problem-matcher:
|
||||
description: 'Whether to register the Java problem matcher (compiler errors/warnings and uncaught exceptions). Set to "false" to disable annotations.'
|
||||
required: false
|
||||
default: true
|
||||
outputs:
|
||||
distribution:
|
||||
description: 'Distribution of Java that has been installed'
|
||||
|
||||
8800
dist/cleanup/index.js
vendored
8800
dist/cleanup/index.js
vendored
File diff suppressed because it is too large
Load Diff
9912
dist/setup/index.js
vendored
9912
dist/setup/index.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,13 @@ The major breaking change in V2 is the new mandatory `distribution` input. This
|
||||
Use the `zulu` keyword if you would like to continue using the same distribution as in V1.
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: jdk # optional (jdk or jre) - defaults to jdk
|
||||
- run: java -cp java HelloWorldApp
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: jdk # optional (jdk or jre) - defaults to jdk
|
||||
- run: java -cp java HelloWorldApp
|
||||
```
|
||||
|
||||
**General recommendation** — configure CI with the same distribution that is used on your local dev machine.
|
||||
@@ -19,15 +19,15 @@ steps:
|
||||
Since the `distribution` input is required in V2, you should specify it using `jdkfile` to continue installing Java from a local file on the runner
|
||||
```yaml
|
||||
steps:
|
||||
- run: |
|
||||
download_url="https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz"
|
||||
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'jdkfile'
|
||||
jdkFile: ${{ runner.temp }}/java_package.tar.gz
|
||||
java-version: '11.0.0'
|
||||
architecture: x64
|
||||
- run: |
|
||||
download_url="https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz"
|
||||
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'jdkfile'
|
||||
jdkFile: ${{ runner.temp }}/java_package.tar.gz
|
||||
java-version: '11.0.0'
|
||||
architecture: x64
|
||||
```
|
||||
|
||||
## Dropping legacy Java version syntax 1.x
|
||||
|
||||
1837
package-lock.json
generated
1837
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -42,7 +42,7 @@
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "^6.1.0",
|
||||
"@actions/cache": "^6.2.0",
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/glob": "^0.7.0",
|
||||
@@ -57,8 +57,8 @@
|
||||
"@jest/globals": "^30.4.1",
|
||||
"@types/node": "^26.1.1",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/parser": "^8.62.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.64.0",
|
||||
"@typescript-eslint/parser": "^8.65.0",
|
||||
"@vercel/ncc": "^0.44.0",
|
||||
"eslint": "^10.7.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
@@ -67,7 +67,7 @@
|
||||
"globals": "^17.7.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^30.4.2",
|
||||
"lint-staged": "^17.0.8",
|
||||
"lint-staged": "^17.2.0",
|
||||
"prettier": "^3.9.5",
|
||||
"ts-jest": "^29.4.11",
|
||||
"typescript": "^6.0.3"
|
||||
@@ -75,5 +75,12 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/setup-java/issues"
|
||||
},
|
||||
"homepage": "https://github.com/actions/setup-java#readme"
|
||||
"homepage": "https://github.com/actions/setup-java#readme",
|
||||
"overrides": {
|
||||
"@actions/cache": {
|
||||
"@actions/glob": "0.7.0"
|
||||
},
|
||||
"glob": "^11.1.0",
|
||||
"test-exclude": "^7.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
86
src/auth.ts
86
src/auth.ts
@@ -12,8 +12,16 @@ import {getBooleanInput} from './util.js';
|
||||
|
||||
export async function configureAuthentication() {
|
||||
const id = core.getInput(constants.INPUT_SERVER_ID);
|
||||
const username = core.getInput(constants.INPUT_SERVER_USERNAME);
|
||||
const password = core.getInput(constants.INPUT_SERVER_PASSWORD);
|
||||
const usernameEnvVar = getInputWithDeprecatedAlias(
|
||||
constants.INPUT_SERVER_USERNAME_ENV_VAR,
|
||||
constants.INPUT_SERVER_USERNAME_DEPRECATED,
|
||||
constants.INPUT_DEFAULT_SERVER_USERNAME
|
||||
);
|
||||
const passwordEnvVar = getInputWithDeprecatedAlias(
|
||||
constants.INPUT_SERVER_PASSWORD_ENV_VAR,
|
||||
constants.INPUT_SERVER_PASSWORD_DEPRECATED,
|
||||
constants.INPUT_DEFAULT_SERVER_PASSWORD
|
||||
);
|
||||
const settingsDirectory =
|
||||
core.getInput(constants.INPUT_SETTINGS_PATH) ||
|
||||
path.join(os.homedir(), constants.M2_DIR);
|
||||
@@ -24,9 +32,11 @@ export async function configureAuthentication() {
|
||||
const gpgPrivateKey =
|
||||
core.getInput(constants.INPUT_GPG_PRIVATE_KEY) ||
|
||||
constants.INPUT_DEFAULT_GPG_PRIVATE_KEY;
|
||||
const gpgPassphrase =
|
||||
core.getInput(constants.INPUT_GPG_PASSPHRASE) ||
|
||||
(gpgPrivateKey ? constants.INPUT_DEFAULT_GPG_PASSPHRASE : undefined);
|
||||
const gpgPassphraseEnvVar = getInputWithDeprecatedAlias(
|
||||
constants.INPUT_GPG_PASSPHRASE_ENV_VAR,
|
||||
constants.INPUT_GPG_PASSPHRASE_DEPRECATED,
|
||||
gpgPrivateKey ? constants.INPUT_DEFAULT_GPG_PASSPHRASE : undefined
|
||||
);
|
||||
|
||||
if (gpgPrivateKey) {
|
||||
core.setSecret(gpgPrivateKey);
|
||||
@@ -34,11 +44,11 @@ export async function configureAuthentication() {
|
||||
|
||||
await createAuthenticationSettings(
|
||||
id,
|
||||
username,
|
||||
password,
|
||||
usernameEnvVar,
|
||||
passwordEnvVar,
|
||||
settingsDirectory,
|
||||
overwriteSettings,
|
||||
gpgPassphrase
|
||||
gpgPassphraseEnvVar
|
||||
);
|
||||
|
||||
if (gpgPrivateKey) {
|
||||
@@ -48,13 +58,30 @@ export async function configureAuthentication() {
|
||||
}
|
||||
}
|
||||
|
||||
export function getInputWithDeprecatedAlias(
|
||||
inputName: string,
|
||||
deprecatedInputName: string,
|
||||
defaultValue?: string
|
||||
): string {
|
||||
const value = core.getInput(inputName);
|
||||
const deprecatedValue = core.getInput(deprecatedInputName);
|
||||
|
||||
if (deprecatedValue) {
|
||||
core.warning(
|
||||
`The '${deprecatedInputName}' input is deprecated and may be removed in a future release. Please use '${inputName}' instead.`
|
||||
);
|
||||
}
|
||||
|
||||
return value || deprecatedValue || defaultValue || '';
|
||||
}
|
||||
|
||||
export async function createAuthenticationSettings(
|
||||
id: string,
|
||||
username: string,
|
||||
password: string,
|
||||
usernameEnvVar: string,
|
||||
passwordEnvVar: string,
|
||||
settingsDirectory: string,
|
||||
overwriteSettings: boolean,
|
||||
gpgPassphrase: string | undefined = undefined
|
||||
gpgPassphraseEnvVar: string | undefined = undefined
|
||||
) {
|
||||
core.info(`Creating ${constants.MVN_SETTINGS_FILE} with server-id: ${id}`);
|
||||
// when an alternate m2 location is specified use only that location (no .m2 directory)
|
||||
@@ -62,7 +89,7 @@ export async function createAuthenticationSettings(
|
||||
await io.mkdirP(settingsDirectory);
|
||||
await write(
|
||||
settingsDirectory,
|
||||
generate(id, username, password, gpgPassphrase),
|
||||
generate(id, usernameEnvVar, passwordEnvVar, gpgPassphraseEnvVar),
|
||||
overwriteSettings
|
||||
);
|
||||
}
|
||||
@@ -70,9 +97,9 @@ export async function createAuthenticationSettings(
|
||||
// only exported for testing purposes
|
||||
export function generate(
|
||||
id: string,
|
||||
username: string,
|
||||
password: string,
|
||||
gpgPassphrase?: string | undefined
|
||||
usernameEnvVar: string,
|
||||
passwordEnvVar: string,
|
||||
gpgPassphraseEnvVar?: string | undefined
|
||||
) {
|
||||
const xmlObj: {[key: string]: any} = {
|
||||
settings: {
|
||||
@@ -85,20 +112,35 @@ export function generate(
|
||||
server: [
|
||||
{
|
||||
id: id,
|
||||
username: `\${env.${username}}`,
|
||||
password: `\${env.${password}}`
|
||||
username: `\${env.${usernameEnvVar}}`,
|
||||
password: `\${env.${passwordEnvVar}}`
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (gpgPassphrase) {
|
||||
const gpgServer = {
|
||||
id: 'gpg.passphrase',
|
||||
passphrase: `\${env.${gpgPassphrase}}`
|
||||
// The maven-gpg-plugin reads the passphrase from the environment variable
|
||||
// named by the `gpg.passphraseEnvName` property (default MAVEN_GPG_PASSPHRASE).
|
||||
// Only configure it when the requested env var name differs from that default;
|
||||
// otherwise the plugin already reads the right variable and no extra settings
|
||||
// are needed. Writing `gpg.passphrase` to settings.xml is deprecated and fails
|
||||
// when the plugin's `bestPractices` mode is enabled.
|
||||
if (
|
||||
gpgPassphraseEnvVar &&
|
||||
gpgPassphraseEnvVar !== constants.MAVEN_GPG_PASSPHRASE_DEFAULT_ENV
|
||||
) {
|
||||
xmlObj.settings.profiles = {
|
||||
profile: {
|
||||
id: constants.GPG_PASSPHRASE_PROFILE_ID,
|
||||
properties: {
|
||||
'gpg.passphraseEnvName': gpgPassphraseEnvVar
|
||||
}
|
||||
}
|
||||
};
|
||||
xmlObj.settings.activeProfiles = {
|
||||
activeProfile: constants.GPG_PASSPHRASE_PROFILE_ID
|
||||
};
|
||||
xmlObj.settings.servers.server.push(gpgServer);
|
||||
}
|
||||
|
||||
return xmlCreate(xmlObj).end({
|
||||
|
||||
206
src/cache.ts
206
src/cache.ts
@@ -12,6 +12,30 @@ const STATE_CACHE_PRIMARY_KEY = 'cache-primary-key';
|
||||
const CACHE_MATCHED_KEY = 'cache-matched-key';
|
||||
const CACHE_KEY_PREFIX = 'setup-java';
|
||||
|
||||
/**
|
||||
* An additional cache entry that is restored and saved independently of the
|
||||
* main dependency cache. Used for build-tool wrapper distributions that rarely
|
||||
* change (e.g. the Maven wrapper distribution) so that they are not evicted
|
||||
* every time a volatile dependency file such as pom.xml changes. See
|
||||
* https://github.com/actions/setup-java/issues/1095.
|
||||
*/
|
||||
interface AdditionalCache {
|
||||
/**
|
||||
* Short identifier for the cache, used to build its cache key and to scope
|
||||
* the state keys that carry information from restore to save.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Paths that make up this cache entry.
|
||||
*/
|
||||
path: string[];
|
||||
/**
|
||||
* Glob patterns whose hash forms the cache key. If no file matches, the
|
||||
* cache is skipped silently (the project simply does not use this feature).
|
||||
*/
|
||||
pattern: string[];
|
||||
}
|
||||
|
||||
interface PackageManager {
|
||||
id: 'maven' | 'gradle' | 'sbt';
|
||||
/**
|
||||
@@ -19,27 +43,36 @@ interface PackageManager {
|
||||
*/
|
||||
path: string[];
|
||||
pattern: string[];
|
||||
/**
|
||||
* Additional caches keyed independently of the main dependency cache.
|
||||
*/
|
||||
additionalCaches?: AdditionalCache[];
|
||||
}
|
||||
const supportedPackageManager: PackageManager[] = [
|
||||
{
|
||||
id: 'maven',
|
||||
path: [
|
||||
join(os.homedir(), '.m2', 'repository'),
|
||||
join(os.homedir(), '.m2', 'wrapper', 'dists')
|
||||
],
|
||||
path: [join(os.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: [join(os.homedir(), '.m2', 'wrapper', 'dists')],
|
||||
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'gradle',
|
||||
path: [
|
||||
join(os.homedir(), '.gradle', 'caches'),
|
||||
join(os.homedir(), '.gradle', 'wrapper')
|
||||
],
|
||||
path: [join(os.homedir(), '.gradle', 'caches')],
|
||||
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
|
||||
pattern: [
|
||||
'**/*.gradle*',
|
||||
@@ -48,6 +81,17 @@ const supportedPackageManager: PackageManager[] = [
|
||||
'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: [join(os.homedir(), '.gradle', 'wrapper')],
|
||||
pattern: ['**/gradle-wrapper.properties']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -87,6 +131,21 @@ function findPackageManager(id: string): PackageManager {
|
||||
return packageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: string): string {
|
||||
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
|
||||
}
|
||||
function additionalCacheMatchedKeyState(name: string): string {
|
||||
return `${CACHE_MATCHED_KEY}-${name}`;
|
||||
}
|
||||
|
||||
function buildCacheKey(id: string, fileHash: string): string {
|
||||
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 }}"".
|
||||
@@ -105,7 +164,22 @@ async function computeCacheKey(
|
||||
`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`
|
||||
);
|
||||
}
|
||||
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${packageManager.id}-${fileHash}`;
|
||||
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: AdditionalCache
|
||||
): Promise<string | undefined> {
|
||||
const fileHash = await glob.hashFiles(additionalCache.pattern.join('\n'));
|
||||
if (!fileHash) {
|
||||
return undefined;
|
||||
}
|
||||
return buildCacheKey(additionalCache.name, fileHash);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,6 +204,40 @@ 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.
|
||||
*/
|
||||
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
|
||||
);
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +251,10 @@ export async function save(id: string) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (!primaryKey) {
|
||||
core.warning('Error retrieving key from state.');
|
||||
return;
|
||||
@@ -180,6 +292,82 @@ export async function save(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: PackageManager,
|
||||
additionalCache: 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 as 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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param packageManager the specified package manager by user
|
||||
* @param error the error thrown by the saveCache
|
||||
|
||||
83
src/checksum.ts
Normal file
83
src/checksum.ts
Normal 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}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,35 @@ export const INPUT_DISTRIBUTION = 'distribution';
|
||||
export const INPUT_JDK_FILE = 'jdk-file';
|
||||
export const INPUT_JDK_FILE_DEPRECATED = 'jdkFile';
|
||||
export const INPUT_CHECK_LATEST = 'check-latest';
|
||||
export const INPUT_FORCE_DOWNLOAD = 'force-download';
|
||||
export const INPUT_SET_DEFAULT = 'set-default';
|
||||
export const INPUT_PROBLEM_MATCHER = 'problem-matcher';
|
||||
export const INPUT_VERIFY_SIGNATURE = 'verify-signature';
|
||||
export const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
|
||||
export const INPUT_SERVER_ID = 'server-id';
|
||||
export const INPUT_SERVER_USERNAME = 'server-username';
|
||||
export const INPUT_SERVER_PASSWORD = 'server-password';
|
||||
export const INPUT_SERVER_USERNAME_ENV_VAR = 'server-username-env-var';
|
||||
export const INPUT_SERVER_PASSWORD_ENV_VAR = 'server-password-env-var';
|
||||
export const INPUT_SERVER_USERNAME_DEPRECATED = 'server-username';
|
||||
export const INPUT_SERVER_PASSWORD_DEPRECATED = 'server-password';
|
||||
export const INPUT_SETTINGS_PATH = 'settings-path';
|
||||
export const INPUT_OVERWRITE_SETTINGS = 'overwrite-settings';
|
||||
export const INPUT_GPG_PRIVATE_KEY = 'gpg-private-key';
|
||||
export const INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
|
||||
export const INPUT_GPG_PASSPHRASE_ENV_VAR = 'gpg-passphrase-env-var';
|
||||
export const INPUT_GPG_PASSPHRASE_DEPRECATED = 'gpg-passphrase';
|
||||
|
||||
export const INPUT_DEFAULT_SERVER_USERNAME = 'GITHUB_ACTOR';
|
||||
export const INPUT_DEFAULT_SERVER_PASSWORD = 'GITHUB_TOKEN';
|
||||
export const INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined;
|
||||
export const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
|
||||
|
||||
// The default name of the environment variable the maven-gpg-plugin reads the
|
||||
// passphrase from (property `gpg.passphraseEnvName`). When the configured
|
||||
// passphrase env var name matches this, no extra configuration is required.
|
||||
export const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
|
||||
|
||||
// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
|
||||
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_JOB_STATUS = 'job-status';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -10,12 +10,16 @@ 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';
|
||||
|
||||
export abstract class JavaBase {
|
||||
protected http: httpm.HttpClient;
|
||||
@@ -25,6 +29,7 @@ export abstract class JavaBase {
|
||||
protected stable: boolean;
|
||||
protected latest: boolean;
|
||||
protected checkLatest: boolean;
|
||||
protected forceDownload: boolean;
|
||||
protected setDefault: boolean;
|
||||
protected verifySignature: boolean;
|
||||
protected verifySignaturePublicKey: string | undefined;
|
||||
@@ -33,10 +38,7 @@ 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,
|
||||
@@ -46,6 +48,7 @@ export abstract class JavaBase {
|
||||
this.architecture = installerOptions.architecture || os.arch();
|
||||
this.packageType = installerOptions.packageType;
|
||||
this.checkLatest = installerOptions.checkLatest;
|
||||
this.forceDownload = installerOptions.forceDownload ?? false;
|
||||
this.setDefault =
|
||||
installerOptions.setDefault !== undefined
|
||||
? installerOptions.setDefault
|
||||
@@ -61,6 +64,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(
|
||||
@@ -68,115 +166,24 @@ export abstract class JavaBase {
|
||||
);
|
||||
}
|
||||
|
||||
let foundJava = this.findInToolcache();
|
||||
let foundJava = this.forceDownload ? null : this.findInToolcache();
|
||||
if (foundJava && !this.checkLatest && !this.latest) {
|
||||
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 (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) {
|
||||
@@ -204,6 +211,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}`;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ export interface JavaInstallerOptions {
|
||||
architecture: string;
|
||||
packageType: string;
|
||||
checkLatest: boolean;
|
||||
forceDownload?: boolean;
|
||||
setDefault?: boolean;
|
||||
verifySignature?: boolean;
|
||||
verifySignaturePublicKey?: string;
|
||||
@@ -13,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;
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,33 +21,20 @@ import {
|
||||
} from './graalvm/installer.js';
|
||||
import {JetBrainsDistribution} from './jetbrains/installer.js';
|
||||
import {KonaDistribution} from './kona/installer.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'
|
||||
}
|
||||
import {OpenJdkDistribution} from './openjdk/installer.js';
|
||||
import {JavaDistribution, validateJavaPackage} from './package-types.js';
|
||||
|
||||
export function getJavaDistribution(
|
||||
distributionName: string,
|
||||
installerOptions: JavaInstallerOptions,
|
||||
jdkFile?: string
|
||||
): JavaBase | null {
|
||||
validateJavaPackage(
|
||||
distributionName,
|
||||
installerOptions.packageType,
|
||||
installerOptions.version
|
||||
);
|
||||
|
||||
switch (distributionName) {
|
||||
case JavaDistribution.JdkFile:
|
||||
return new LocalDistribution(installerOptions, jdkFile);
|
||||
@@ -93,6 +80,8 @@ export function getJavaDistribution(
|
||||
return new JetBrainsDistribution(installerOptions);
|
||||
case JavaDistribution.Kona:
|
||||
return new KonaDistribution(installerOptions);
|
||||
case JavaDistribution.OracleOpenJdk:
|
||||
return new OpenJdkDistribution(installerOptions);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
@@ -110,6 +111,11 @@ export class GraalVMDistribution extends JavaBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected setJavaDefault(version: string, toolPath: string): void {
|
||||
super.setJavaDefault(version, toolPath);
|
||||
core.exportVariable('GRAALVM_HOME', toolPath);
|
||||
}
|
||||
|
||||
protected async findPackageForDownload(
|
||||
range: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
@@ -140,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 {
|
||||
@@ -279,7 +289,8 @@ export class GraalVMDistribution extends JavaBase {
|
||||
|
||||
return {
|
||||
url: downloadUrl,
|
||||
version: latestVersion.version
|
||||
version: latestVersion.version,
|
||||
checksum: await this.fetchChecksum(`${downloadUrl}.sha256`, 'sha256')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -451,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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
}`
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -28,7 +28,7 @@ export class LocalDistribution extends JavaBase {
|
||||
);
|
||||
}
|
||||
|
||||
let foundJava = this.findInToolcache();
|
||||
let foundJava = this.forceDownload ? null : this.findInToolcache();
|
||||
|
||||
if (foundJava) {
|
||||
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
185
src/distributions/openjdk/installer.ts
Normal file
185
src/distributions/openjdk/installer.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
|
||||
import {JavaBase} from '../base-installer.js';
|
||||
import {
|
||||
JavaDownloadRelease,
|
||||
JavaInstallerOptions,
|
||||
JavaInstallerResults
|
||||
} from '../base-models.js';
|
||||
import {
|
||||
convertVersionToSemver,
|
||||
extractJdkFile,
|
||||
isVersionSatisfies,
|
||||
renameWinArchive
|
||||
} from '../../util.js';
|
||||
|
||||
const OPENJDK_BASE_URL = 'https://jdk.java.net';
|
||||
|
||||
export class OpenJdkDistribution extends JavaBase {
|
||||
constructor(installerOptions: JavaInstallerOptions) {
|
||||
super('Oracle OpenJDK', installerOptions);
|
||||
}
|
||||
|
||||
protected async findPackageForDownload(
|
||||
range: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
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 => isVersionSatisfies(range, release.version))
|
||||
.sort((left, right) => -semver.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')
|
||||
};
|
||||
}
|
||||
|
||||
protected async downloadTool(
|
||||
javaRelease: JavaDownloadRelease
|
||||
): Promise<JavaInstallerResults> {
|
||||
core.info(
|
||||
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
|
||||
);
|
||||
let javaArchivePath = await this.downloadAndVerify(javaRelease);
|
||||
|
||||
core.info(`Extracting Java archive...`);
|
||||
const extension = javaRelease.url.endsWith('.zip') ? 'zip' : 'tar.gz';
|
||||
if (extension === 'zip') {
|
||||
javaArchivePath = renameWinArchive(javaArchivePath);
|
||||
}
|
||||
const extractedJavaPath = await extractJdkFile(javaArchivePath, extension);
|
||||
|
||||
const archiveName = fs.readdirSync(extractedJavaPath)[0];
|
||||
const archivePath = path.join(extractedJavaPath, archiveName);
|
||||
const javaPath = await tc.cacheDir(
|
||||
archivePath,
|
||||
this.toolcacheFolderName,
|
||||
this.getToolcacheVersionName(javaRelease.version),
|
||||
this.architecture
|
||||
);
|
||||
|
||||
return {version: javaRelease.version, path: javaPath};
|
||||
}
|
||||
|
||||
private async getAvailableVersions(
|
||||
platform: string,
|
||||
arch: string
|
||||
): Promise<JavaDownloadRelease[]> {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
private async fetchPage(url: string): Promise<string> {
|
||||
const response = await this.http.get(url);
|
||||
return response.readBody();
|
||||
}
|
||||
|
||||
private parseReleases(
|
||||
html: string,
|
||||
platform: string,
|
||||
arch: string
|
||||
): JavaDownloadRelease[] {
|
||||
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
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private findBuildInArchiveHeading(
|
||||
html: string,
|
||||
assetIndex: number,
|
||||
version: string
|
||||
): string | undefined {
|
||||
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;
|
||||
}
|
||||
|
||||
private toSemver(version: string, urlBuild?: string): string {
|
||||
const [javaVersion, filenameBuild] = version.replace('-ea', '').split('+');
|
||||
const versionParts = javaVersion.split('.');
|
||||
const normalizedVersion = convertVersionToSemver(
|
||||
versionParts.length === 1 ? `${javaVersion}.0.0` : javaVersion
|
||||
);
|
||||
const build =
|
||||
filenameBuild ?? (versionParts.length <= 3 ? urlBuild : undefined);
|
||||
return build ? `${normalizedVersion}+${build}` : normalizedVersion;
|
||||
}
|
||||
|
||||
private getPlatform(platform: NodeJS.Platform = process.platform): string {
|
||||
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'`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
src/distributions/package-types.ts
Normal file
135
src/distributions/package-types.ts
Normal 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(' '));
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as gpg from '../../gpg.js';
|
||||
import {ADOPTIUM_PUBLIC_KEY} from './adoptium-key.js';
|
||||
import {JavaBase} from '../base-installer.js';
|
||||
import {ITemurinAvailableVersions} from './models.js';
|
||||
import {MACOS_JAVA_CONTENT_POSTFIX} from '../../constants.js';
|
||||
import {
|
||||
JavaDownloadRelease,
|
||||
JavaInstallerOptions,
|
||||
@@ -31,11 +32,14 @@ export enum TemurinImplementation {
|
||||
}
|
||||
|
||||
export class TemurinDistribution extends JavaBase {
|
||||
private readonly includeJmods: boolean;
|
||||
|
||||
constructor(
|
||||
installerOptions: JavaInstallerOptions,
|
||||
private readonly jvmImpl: TemurinImplementation
|
||||
) {
|
||||
super(`Temurin-${jvmImpl}`, installerOptions);
|
||||
this.includeJmods = this.packageType === 'jdk+jmods';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +48,17 @@ export class TemurinDistribution extends JavaBase {
|
||||
public async findPackageForDownload(
|
||||
version: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
const availableVersionsRaw = await this.getAvailableVersions();
|
||||
return this.resolvePackage(
|
||||
version,
|
||||
this.includeJmods ? 'jdk' : this.packageType
|
||||
);
|
||||
}
|
||||
|
||||
private async resolvePackage(
|
||||
version: string,
|
||||
imageType: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
const availableVersionsRaw = await this.getAvailableVersions(imageType);
|
||||
const availableVersionsWithBinaries = availableVersionsRaw
|
||||
.filter(item => item.binaries.length > 0)
|
||||
.map(item => {
|
||||
@@ -55,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;
|
||||
});
|
||||
|
||||
@@ -83,30 +102,7 @@ export class TemurinDistribution extends JavaBase {
|
||||
core.info(
|
||||
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
|
||||
);
|
||||
let javaArchivePath = await tc.downloadTool(javaRelease.url);
|
||||
|
||||
if (this.verifySignature) {
|
||||
if (!javaRelease.signatureUrl) {
|
||||
throw new Error(
|
||||
`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${javaRelease.version}.`
|
||||
);
|
||||
}
|
||||
core.info(`Verifying Java package signature...`);
|
||||
try {
|
||||
await gpg.verifyPackageSignature(
|
||||
javaArchivePath,
|
||||
javaRelease.signatureUrl,
|
||||
this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to verify signature for Temurin version ${javaRelease.version} from ${javaRelease.signatureUrl}: ${
|
||||
(error as Error).message
|
||||
}`,
|
||||
{cause: error}
|
||||
);
|
||||
}
|
||||
}
|
||||
let javaArchivePath = await this.downloadPackage(javaRelease);
|
||||
|
||||
core.info(`Extracting Java archive...`);
|
||||
const extension = getDownloadArchiveExtension();
|
||||
@@ -117,6 +113,13 @@ export class TemurinDistribution extends JavaBase {
|
||||
|
||||
const archiveName = fs.readdirSync(extractedJavaPath)[0];
|
||||
const archivePath = path.join(extractedJavaPath, archiveName);
|
||||
const javaHome =
|
||||
process.platform === 'darwin'
|
||||
? path.join(archivePath, MACOS_JAVA_CONTENT_POSTFIX)
|
||||
: archivePath;
|
||||
if (this.includeJmods && !fs.existsSync(path.join(javaHome, 'jmods'))) {
|
||||
await this.installJmods(javaRelease.version, javaHome);
|
||||
}
|
||||
const version = this.getToolcacheVersionName(javaRelease.version);
|
||||
|
||||
const javaPath = await tc.cacheDir(
|
||||
@@ -129,18 +132,64 @@ export class TemurinDistribution extends JavaBase {
|
||||
return {version: javaRelease.version, path: javaPath};
|
||||
}
|
||||
|
||||
protected get toolcacheFolderName(): string {
|
||||
return super.toolcacheFolderName;
|
||||
}
|
||||
|
||||
protected supportsSignatureVerification(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async getAvailableVersions(): Promise<ITemurinAvailableVersions[]> {
|
||||
private async downloadPackage(release: JavaDownloadRelease): Promise<string> {
|
||||
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(`Verifying Java package signature...`);
|
||||
try {
|
||||
await gpg.verifyPackageSignature(
|
||||
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 as Error).message
|
||||
}`,
|
||||
{cause: error}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
private async installJmods(version: string, javaHome: string): Promise<void> {
|
||||
const jmodsRelease = await this.resolvePackage(version, 'jmods');
|
||||
core.info(
|
||||
`Downloading JMODs ${jmodsRelease.version} (${this.distribution}) from ${jmodsRelease.url} ...`
|
||||
);
|
||||
let jmodsArchivePath = await this.downloadPackage(jmodsRelease);
|
||||
if (process.platform === 'win32') {
|
||||
jmodsArchivePath = renameWinArchive(jmodsArchivePath);
|
||||
}
|
||||
const extractedJmodsPath = await extractJdkFile(
|
||||
jmodsArchivePath,
|
||||
getDownloadArchiveExtension()
|
||||
);
|
||||
const jmodsDirectory = path.join(
|
||||
extractedJmodsPath,
|
||||
fs.readdirSync(extractedJmodsPath)[0]
|
||||
);
|
||||
fs.cpSync(jmodsDirectory, path.join(javaHome, 'jmods'), {recursive: true});
|
||||
}
|
||||
|
||||
private async getAvailableVersions(
|
||||
imageType = this.includeJmods ? 'jdk' : this.packageType
|
||||
): Promise<ITemurinAvailableVersions[]> {
|
||||
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';
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -10,3 +10,7 @@ export interface IZuluVersions {
|
||||
latest: boolean;
|
||||
availability_type: string;
|
||||
}
|
||||
|
||||
export interface IZuluPackageDetails {
|
||||
sha256_hash?: string;
|
||||
}
|
||||
|
||||
14
src/problem-matcher.ts
Normal file
14
src/problem-matcher.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as core from '@actions/core';
|
||||
import {INPUT_PROBLEM_MATCHER} from './constants.js';
|
||||
import {getBooleanInput} from './util.js';
|
||||
|
||||
export function configureProblemMatcher(matcherPath: string): void {
|
||||
const problemMatcherEnabled = getBooleanInput(INPUT_PROBLEM_MATCHER, true);
|
||||
|
||||
if (!problemMatcherEnabled) {
|
||||
core.debug('Java problem matcher is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(`##[add-matcher]${matcherPath}`);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user