mirror of
https://github.com/actions/setup-java.git
synced 2026-06-26 23:17:54 +00:00
Compare commits
14 Commits
ebfd7f7438
...
a286f95387
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a286f95387 | ||
|
|
f300429fba | ||
|
|
ad2b38190b | ||
|
|
b24df5bba5 | ||
|
|
43120bc3c3 | ||
|
|
ad9d6a6320 | ||
|
|
039af37997 | ||
|
|
1756ab6acd | ||
|
|
662bb59f48 | ||
|
|
1071fc12d6 | ||
|
|
576b821f29 | ||
|
|
307d3a25a0 | ||
|
|
e518b2fd8d | ||
|
|
58d36cecb0 |
26
.github/workflows/e2e-versions.yml
vendored
26
.github/workflows/e2e-versions.yml
vendored
@ -86,6 +86,32 @@ jobs:
|
||||
run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}"
|
||||
shell: bash
|
||||
|
||||
setup-java-alpine-linux:
|
||||
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-x64) - alpine-linux - ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
container:
|
||||
image: alpine:latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
distribution: ['temurin', 'sapmachine']
|
||||
version: ['21', '17']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Install bash
|
||||
run: apk add --no-cache bash
|
||||
- name: setup-java
|
||||
uses: ./
|
||||
id: setup-java
|
||||
with:
|
||||
java-version: ${{ matrix.version }}
|
||||
distribution: ${{ matrix.distribution }}
|
||||
- name: Verify Java
|
||||
run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}"
|
||||
shell: bash
|
||||
|
||||
setup-java-major-minor-versions:
|
||||
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-x64) - ${{ matrix.os }}
|
||||
needs: setup-java-major-versions
|
||||
|
||||
BIN
.licenses/npm/@nodable/entities.dep.yml
generated
Normal file
BIN
.licenses/npm/@nodable/entities.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/anynum.dep.yml
generated
Normal file
BIN
.licenses/npm/anynum.dep.yml
generated
Normal file
Binary file not shown.
BIN
.licenses/npm/debug.dep.yml
generated
BIN
.licenses/npm/debug.dep.yml
generated
Binary file not shown.
BIN
.licenses/npm/fast-xml-builder.dep.yml
generated
BIN
.licenses/npm/fast-xml-builder.dep.yml
generated
Binary file not shown.
BIN
.licenses/npm/fast-xml-parser.dep.yml
generated
BIN
.licenses/npm/fast-xml-parser.dep.yml
generated
Binary file not shown.
BIN
.licenses/npm/ms.dep.yml
generated
BIN
.licenses/npm/ms.dep.yml
generated
Binary file not shown.
BIN
.licenses/npm/path-expression-matcher.dep.yml
generated
BIN
.licenses/npm/path-expression-matcher.dep.yml
generated
Binary file not shown.
Binary file not shown.
BIN
.licenses/npm/strnum.dep.yml
generated
BIN
.licenses/npm/strnum.dep.yml
generated
Binary file not shown.
BIN
.licenses/npm/xml-naming.dep.yml
generated
Normal file
BIN
.licenses/npm/xml-naming.dep.yml
generated
Normal file
Binary file not shown.
@ -131,6 +131,8 @@ Currently, the following distributions are supported:
|
||||
|
||||
**NOTE:** Oracle JDK 17 licensing varies by patch level. As shown on the [JDK 17 Archive](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) (versions up to 17.0.12 are under the [NFTC](https://www.oracle.com/downloads/licenses/no-fee-license.html) license) and the [JDK 17.0.13+ Archive](https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html) (versions 17.0.13 and later are under the [OTN](https://www.oracle.com/downloads/licenses/javase-license1.html) license). To stay on the free NFTC license, use `distribution: 'oracle'` with `java-version: '17.0.12'` (or earlier) instead of the floating `'17'`. Alternatively, upgrade to Oracle JDK 21+, which remains under the NFTC license.
|
||||
|
||||
**NOTE:** On Ubuntu runners, commands executed via `sudo` do not inherit the `JAVA_HOME` and `PATH` set by `setup-java` and will fall back to the runner image's system-default JDK.
|
||||
|
||||
### Caching packages dependencies
|
||||
The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under hood for caching dependencies but requires less configuration settings. Supported package managers are gradle, maven and sbt. The format of the used cache key is `setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}`, where the hash is based on the following files:
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
AdoptDistribution,
|
||||
AdoptImplementation
|
||||
} from '../../src/distributions/adopt/installer';
|
||||
import {TemurinDistribution} from '../../src/distributions/temurin/installer';
|
||||
import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
||||
|
||||
import os from 'os';
|
||||
@ -14,6 +15,7 @@ import * as core from '@actions/core';
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
let spyCoreWarning: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@ -26,6 +28,8 @@ describe('getAvailableVersions', () => {
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
spyCoreWarning = jest.spyOn(core, 'warning');
|
||||
spyCoreWarning.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -136,22 +140,19 @@ describe('getAvailableVersions', () => {
|
||||
);
|
||||
|
||||
it('load available versions', async () => {
|
||||
const nextPageUrl =
|
||||
'https://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
spyHttpClient
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
const distribution = new AdoptDistribution(
|
||||
@ -166,6 +167,34 @@ describe('getAvailableVersions', () => {
|
||||
const availableVersions = await distribution['getAvailableVersions']();
|
||||
expect(availableVersions).not.toBeNull();
|
||||
expect(availableVersions.length).toBe(manifestData.length * 2);
|
||||
expect(spyHttpClient).toHaveBeenNthCalledWith(2, nextPageUrl);
|
||||
});
|
||||
|
||||
it('stops pagination after 1000 pages as a safeguard', async () => {
|
||||
const nextPageUrl =
|
||||
'https://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=2&page_size=20';
|
||||
spyHttpClient.mockReturnValue({
|
||||
statusCode: 200,
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: [{version_data: {semver: '17.0.1'}, binaries: []}] as any
|
||||
});
|
||||
|
||||
const distribution = new AdoptDistribution(
|
||||
{
|
||||
version: '11',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
},
|
||||
AdoptImplementation.Hotspot
|
||||
);
|
||||
|
||||
await distribution['getAvailableVersions']();
|
||||
|
||||
expect(spyHttpClient).toHaveBeenCalledTimes(1000);
|
||||
expect(spyCoreWarning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Reached pagination safeguard limit (1000 pages)')
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@ -228,6 +257,38 @@ describe('getAvailableVersions', () => {
|
||||
});
|
||||
|
||||
describe('findPackageForDownload', () => {
|
||||
it('returns Temurin result and does not query Adopt API when Temurin succeeds', async () => {
|
||||
const temurinRelease = {
|
||||
version: '11.0.31+11',
|
||||
url: 'https://example.test/temurin-11.tar.gz'
|
||||
};
|
||||
const temurinFindPackageForDownload = jest
|
||||
.fn()
|
||||
.mockResolvedValue(temurinRelease);
|
||||
const temurinDistribution = {
|
||||
findPackageForDownload: temurinFindPackageForDownload
|
||||
} as unknown as TemurinDistribution;
|
||||
|
||||
const distribution = new AdoptDistribution(
|
||||
{
|
||||
version: '11',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
},
|
||||
AdoptImplementation.Hotspot,
|
||||
temurinDistribution
|
||||
);
|
||||
const adoptLookupSpy = jest.fn();
|
||||
distribution['getAvailableVersions'] = adoptLookupSpy;
|
||||
|
||||
const resolvedVersion = await distribution['findPackageForDownload']('11');
|
||||
|
||||
expect(resolvedVersion).toEqual(temurinRelease);
|
||||
expect(temurinFindPackageForDownload).toHaveBeenCalledWith('11');
|
||||
expect(adoptLookupSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['9', '9.0.7+10'],
|
||||
['15', '15.0.2+7'],
|
||||
@ -250,6 +311,11 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
AdoptImplementation.Hotspot
|
||||
);
|
||||
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
|
||||
distribution['temurinDistribution']!['findPackageForDownload'] =
|
||||
async () => {
|
||||
throw new Error('No matching version found for SemVer');
|
||||
};
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
const resolvedVersion = await distribution['findPackageForDownload'](input);
|
||||
expect(resolvedVersion.version).toBe(expected);
|
||||
@ -265,6 +331,11 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
AdoptImplementation.Hotspot
|
||||
);
|
||||
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
|
||||
distribution['temurinDistribution']!['findPackageForDownload'] =
|
||||
async () => {
|
||||
throw new Error('No matching version found for SemVer');
|
||||
};
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
await expect(
|
||||
distribution['findPackageForDownload']('9.0.8')
|
||||
@ -281,6 +352,11 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
AdoptImplementation.Hotspot
|
||||
);
|
||||
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
|
||||
distribution['temurinDistribution']!['findPackageForDownload'] =
|
||||
async () => {
|
||||
throw new Error('No matching version found for SemVer');
|
||||
};
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow(
|
||||
/No matching version found for SemVer */
|
||||
@ -297,6 +373,11 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
AdoptImplementation.Hotspot
|
||||
);
|
||||
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
|
||||
distribution['temurinDistribution']!['findPackageForDownload'] =
|
||||
async () => {
|
||||
throw new Error('No matching version found for SemVer');
|
||||
};
|
||||
distribution['getAvailableVersions'] = async () => [];
|
||||
await expect(distribution['findPackageForDownload']('11')).rejects.toThrow(
|
||||
/No matching version found for SemVer */
|
||||
|
||||
@ -9,6 +9,7 @@ import * as core from '@actions/core';
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
let spyCoreWarning: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@ -20,6 +21,8 @@ describe('getAvailableVersions', () => {
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
spyCoreWarning = jest.spyOn(core, 'warning');
|
||||
spyCoreWarning.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -82,22 +85,19 @@ describe('getAvailableVersions', () => {
|
||||
);
|
||||
|
||||
it('load available versions', async () => {
|
||||
const nextPageUrl =
|
||||
'https://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
spyHttpClient
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
const distribution = new SemeruDistribution({
|
||||
@ -109,6 +109,31 @@ describe('getAvailableVersions', () => {
|
||||
const availableVersions = await distribution['getAvailableVersions']();
|
||||
expect(availableVersions).not.toBeNull();
|
||||
expect(availableVersions.length).toBe(manifestData.length * 2);
|
||||
expect(spyHttpClient).toHaveBeenNthCalledWith(2, nextPageUrl);
|
||||
});
|
||||
|
||||
it('stops pagination after 1000 pages as a safeguard', async () => {
|
||||
const nextPageUrl =
|
||||
'https://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=2&page_size=20';
|
||||
spyHttpClient.mockReturnValue({
|
||||
statusCode: 200,
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: [{version_data: {semver: '17.0.1'}, binaries: []}] as any
|
||||
});
|
||||
|
||||
const distribution = new SemeruDistribution({
|
||||
version: '8',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
await distribution['getAvailableVersions']();
|
||||
|
||||
expect(spyHttpClient).toHaveBeenCalledTimes(1000);
|
||||
expect(spyCoreWarning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Reached pagination safeguard limit (1000 pages)')
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@ -12,6 +12,7 @@ import * as core from '@actions/core';
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
let spyCoreWarning: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@ -23,6 +24,8 @@ describe('getAvailableVersions', () => {
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
spyCoreWarning = jest.spyOn(core, 'warning');
|
||||
spyCoreWarning.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -93,22 +96,19 @@ describe('getAvailableVersions', () => {
|
||||
);
|
||||
|
||||
it('load available versions', async () => {
|
||||
const nextPageUrl =
|
||||
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
spyHttpClient
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
const distribution = new TemurinDistribution(
|
||||
@ -123,6 +123,34 @@ describe('getAvailableVersions', () => {
|
||||
const availableVersions = await distribution['getAvailableVersions']();
|
||||
expect(availableVersions).not.toBeNull();
|
||||
expect(availableVersions.length).toBe(manifestData.length * 2);
|
||||
expect(spyHttpClient).toHaveBeenNthCalledWith(2, nextPageUrl);
|
||||
});
|
||||
|
||||
it('stops pagination after 1000 pages as a safeguard', async () => {
|
||||
const nextPageUrl =
|
||||
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=2&page_size=20';
|
||||
spyHttpClient.mockReturnValue({
|
||||
statusCode: 200,
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: [{version_data: {semver: '17.0.1'}, binaries: []}] as any
|
||||
});
|
||||
|
||||
const distribution = new TemurinDistribution(
|
||||
{
|
||||
version: '8',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
},
|
||||
TemurinImplementation.Hotspot
|
||||
);
|
||||
|
||||
await distribution['getAvailableVersions']();
|
||||
|
||||
expect(spyHttpClient).toHaveBeenCalledTimes(1000);
|
||||
expect(spyCoreWarning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Reached pagination safeguard limit (1000 pages)')
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@ -4,10 +4,12 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
convertVersionToSemver,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getVersionFromFileContent,
|
||||
isVersionSatisfies,
|
||||
isCacheFeatureAvailable,
|
||||
isGhes
|
||||
isGhes,
|
||||
validatePaginationUrl
|
||||
} from '../src/util';
|
||||
|
||||
jest.mock('@actions/cache');
|
||||
@ -85,6 +87,78 @@ describe('convertVersionToSemver', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNextPageUrlFromLinkHeader', () => {
|
||||
it.each([
|
||||
[
|
||||
{
|
||||
link: '<https://api.adoptium.net/v3/info/release_versions?page=1&page_size=10>; rel="next"'
|
||||
},
|
||||
'https://api.adoptium.net/v3/info/release_versions?page=1&page_size=10'
|
||||
],
|
||||
[
|
||||
{
|
||||
Link: '<https://example.com/last?page=5>; rel="last", <https://example.com/next?page=2>; rel="next"'
|
||||
},
|
||||
'https://example.com/next?page=2'
|
||||
],
|
||||
[
|
||||
{
|
||||
link: '<https://api.adoptium.net/v3/versions?page=3>; type="application/json"; rel="next"'
|
||||
},
|
||||
'https://api.adoptium.net/v3/versions?page=3'
|
||||
],
|
||||
[{link: '<https://example.com/last?page=5>; rel="last"'}, null],
|
||||
[{link: '<https://example.com/page?p=2>; rel="nextsomething"'}, null],
|
||||
[undefined, null]
|
||||
])('returns %s -> %s', (headers, expected) => {
|
||||
expect(getNextPageUrlFromLinkHeader(headers)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validatePaginationUrl', () => {
|
||||
it('accepts URL with matching origin', () => {
|
||||
expect(
|
||||
validatePaginationUrl(
|
||||
'https://api.adoptium.net/v3/assets?page=2',
|
||||
'https://api.adoptium.net'
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects URL with different host', () => {
|
||||
expect(
|
||||
validatePaginationUrl(
|
||||
'https://evil.example.com/steal?data=1',
|
||||
'https://api.adoptium.net'
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects URL with different protocol', () => {
|
||||
expect(
|
||||
validatePaginationUrl(
|
||||
'http://api.adoptium.net/v3/assets?page=2',
|
||||
'https://api.adoptium.net'
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for invalid URL', () => {
|
||||
expect(validatePaginationUrl('not-a-url', 'https://api.adoptium.net')).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts URL with explicit default port', () => {
|
||||
expect(
|
||||
validatePaginationUrl(
|
||||
'https://api.adoptium.net:443/v3/assets?page=2',
|
||||
'https://api.adoptium.net'
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getVersionFromFileContent', () => {
|
||||
describe('.sdkmanrc', () => {
|
||||
it.each([
|
||||
|
||||
4758
dist/cleanup/index.js
vendored
4758
dist/cleanup/index.js
vendored
File diff suppressed because one or more lines are too long
6585
dist/setup/index.js
vendored
6585
dist/setup/index.js
vendored
File diff suppressed because one or more lines are too long
@ -356,14 +356,13 @@ jobs:
|
||||
server-id: maven # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
server-username: MAVEN_USERNAME # env variable for username in deploy
|
||||
server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
|
||||
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
|
||||
|
||||
- name: Publish to Apache Maven Central
|
||||
run: mvn deploy
|
||||
run: mvn deploy -Dgpg.signer=bc
|
||||
env:
|
||||
MAVEN_USERNAME: maven_username123
|
||||
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
|
||||
MAVEN_GPG_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
|
||||
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
|
||||
```
|
||||
|
||||
@ -399,10 +398,6 @@ The two `settings.xml` files created from the above example look like the follow
|
||||
<username>${env.MAVEN_USERNAME}</username>
|
||||
<password>${env.MAVEN_CENTRAL_TOKEN}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>gpg.passphrase</id>
|
||||
<passphrase>${env.MAVEN_GPG_PASSPHRASE}</passphrase>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
```
|
||||
@ -411,21 +406,6 @@ The two `settings.xml` files created from the above example look like the follow
|
||||
|
||||
If you don't want to overwrite the `settings.xml` file, you can set `overwrite-settings: false`
|
||||
|
||||
### Extra setup for pom.xml:
|
||||
|
||||
The Maven GPG Plugin configuration in the pom.xml file should contain the following structure to avoid possible issues like `Inappropriate ioctl for device` or `gpg: signing failed: No such file or directory`:
|
||||
|
||||
```xml
|
||||
<configuration>
|
||||
<!-- Prevent gpg from using pinentry programs -->
|
||||
<gpgArguments>
|
||||
<arg>--pinentry-mode</arg>
|
||||
<arg>loopback</arg>
|
||||
</gpgArguments>
|
||||
</configuration>
|
||||
```
|
||||
GPG 2.1 requires `--pinentry-mode` to be set to `loopback` in order to pick up the `gpg.passphrase` value defined in Maven `settings.xml`.
|
||||
|
||||
### GPG
|
||||
|
||||
If `gpg-private-key` input is provided, the private key will be written to a file in the runner's temp directory, the private key file will be imported into the GPG keychain, and then the file will be promptly removed before proceeding with the rest of the setup process. A cleanup step will remove the imported private key from the GPG keychain after the job completes regardless of the job status. This ensures that the private key is no longer accessible on self-hosted runners and cannot "leak" between jobs (hosted runners are always clean instances).
|
||||
|
||||
3820
package-lock.json
generated
3820
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -40,20 +40,20 @@
|
||||
"xmlbuilder2": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^25.9.3",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.35.1",
|
||||
"@typescript-eslint/parser": "^8.35.1",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.61.1",
|
||||
"@vercel/ncc": "^0.44.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-jest": "^29.0.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"jest": "^30.4.2",
|
||||
"jest-circus": "^30.4.2",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-jest": "^29.3.0",
|
||||
"ts-jest": "^29.4.11",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"bugs": {
|
||||
|
||||
@ -14,10 +14,14 @@ import {
|
||||
} from '../base-models';
|
||||
import {
|
||||
extractJdkFile,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getDownloadArchiveExtension,
|
||||
isVersionSatisfies,
|
||||
renameWinArchive
|
||||
renameWinArchive,
|
||||
MAX_PAGINATION_PAGES,
|
||||
validatePaginationUrl
|
||||
} from '../../util';
|
||||
import {TemurinDistribution, TemurinImplementation} from '../temurin/installer';
|
||||
|
||||
export enum AdoptImplementation {
|
||||
Hotspot = 'Hotspot',
|
||||
@ -25,15 +29,72 @@ export enum AdoptImplementation {
|
||||
}
|
||||
|
||||
export class AdoptDistribution extends JavaBase {
|
||||
private readonly temurinDistribution: TemurinDistribution | null;
|
||||
|
||||
constructor(
|
||||
installerOptions: JavaInstallerOptions,
|
||||
private readonly jvmImpl: AdoptImplementation
|
||||
private readonly jvmImpl: AdoptImplementation,
|
||||
temurinDistribution: TemurinDistribution | null = null
|
||||
) {
|
||||
super(`Adopt-${jvmImpl}`, installerOptions);
|
||||
|
||||
if (
|
||||
temurinDistribution !== null &&
|
||||
jvmImpl !== AdoptImplementation.Hotspot
|
||||
) {
|
||||
throw new Error('Only Hotspot JVM is supported by Temurin.');
|
||||
}
|
||||
|
||||
// Only use the temurin repo for Hotspot JVMs
|
||||
this.temurinDistribution =
|
||||
temurinDistribution ??
|
||||
(jvmImpl === AdoptImplementation.Hotspot
|
||||
? new TemurinDistribution(
|
||||
installerOptions,
|
||||
TemurinImplementation.Hotspot
|
||||
)
|
||||
: null);
|
||||
}
|
||||
|
||||
protected async findPackageForDownload(
|
||||
version: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
if (this.jvmImpl === AdoptImplementation.Hotspot) {
|
||||
core.notice(
|
||||
"AdoptOpenJDK has moved to Eclipse Temurin https://github.com/actions/setup-java#supported-distributions please consider changing to the 'temurin' distribution type in your setup-java configuration."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.jvmImpl === AdoptImplementation.Hotspot &&
|
||||
this.temurinDistribution !== null
|
||||
) {
|
||||
try {
|
||||
return await this.temurinDistribution.findPackageForDownload(version);
|
||||
} catch (error) {
|
||||
// Log the failure but always fall back to legacy AdoptOpenJDK for resilience
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
if (error instanceof Error && error.name === 'VersionNotFoundError') {
|
||||
core.notice(
|
||||
'The JVM you are looking for could not be found in the Temurin repository, this likely indicates ' +
|
||||
'that you are using an out of date version of Java, consider updating and moving to using the Temurin distribution type in setup-java.'
|
||||
);
|
||||
} else {
|
||||
// Log other errors for debugging but gracefully fall back
|
||||
core.debug(
|
||||
`Temurin lookup failed: ${errorMessage}. Falling back to AdoptOpenJDK API.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// failed to find a Temurin version, so fall back to AdoptOpenJDK
|
||||
return this.findPackageForDownloadOldAdoptOpenJdk(version);
|
||||
}
|
||||
|
||||
private async findPackageForDownloadOldAdoptOpenJdk(
|
||||
version: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
const availableVersionsRaw = await this.getAvailableVersions();
|
||||
const availableVersionsWithBinaries = availableVersionsRaw
|
||||
@ -125,30 +186,46 @@ export class AdoptDistribution extends JavaBase {
|
||||
`jvm_impl=${this.jvmImpl.toLowerCase()}`
|
||||
].join('&');
|
||||
|
||||
// need to iterate through all pages to retrieve the list of all versions
|
||||
// Adopt API doesn't provide way to retrieve the count of pages to iterate so infinity loop
|
||||
let page_index = 0;
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
|
||||
let availableVersionsUrl: string | null =
|
||||
`https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
const availableVersions: IAdoptAvailableVersions[] = [];
|
||||
while (true) {
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
|
||||
const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
if (core.isDebug() && page_index === 0) {
|
||||
// url is identical except page_index so print it once for debug
|
||||
core.debug(
|
||||
`Gathering available versions from '${availableVersionsUrl}'`
|
||||
);
|
||||
}
|
||||
let pageCount = 0;
|
||||
if (core.isDebug()) {
|
||||
core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
|
||||
}
|
||||
|
||||
const paginationPage = (
|
||||
await this.http.getJson<IAdoptAvailableVersions[]>(availableVersionsUrl)
|
||||
).result;
|
||||
while (availableVersionsUrl) {
|
||||
pageCount++;
|
||||
const response =
|
||||
await this.http.getJson<IAdoptAvailableVersions[]>(
|
||||
availableVersionsUrl
|
||||
);
|
||||
const paginationPage = response.result;
|
||||
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
|
||||
if (
|
||||
nextUrl &&
|
||||
!validatePaginationUrl(nextUrl, 'https://api.adoptopenjdk.net')
|
||||
) {
|
||||
core.warning(
|
||||
`Ignoring pagination link with unexpected origin: ${nextUrl}`
|
||||
);
|
||||
availableVersionsUrl = null;
|
||||
} else {
|
||||
availableVersionsUrl = nextUrl;
|
||||
}
|
||||
if (paginationPage === null || paginationPage.length === 0) {
|
||||
// break infinity loop because we have reached end of pagination
|
||||
break;
|
||||
}
|
||||
|
||||
availableVersions.push(...paginationPage);
|
||||
page_index++;
|
||||
|
||||
if (pageCount >= MAX_PAGINATION_PAGES) {
|
||||
core.warning(
|
||||
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Adopt releases.`
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (core.isDebug()) {
|
||||
|
||||
@ -292,7 +292,9 @@ export abstract class JavaBase {
|
||||
}
|
||||
}
|
||||
|
||||
return new Error(parts.join('\n'));
|
||||
const error = new Error(parts.join('\n'));
|
||||
error.name = 'VersionNotFoundError';
|
||||
return error;
|
||||
}
|
||||
|
||||
protected setJavaDefault(version: string, toolPath: string) {
|
||||
|
||||
@ -7,9 +7,12 @@ import {
|
||||
import semver from 'semver';
|
||||
import {
|
||||
extractJdkFile,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getDownloadArchiveExtension,
|
||||
isVersionSatisfies,
|
||||
renameWinArchive
|
||||
renameWinArchive,
|
||||
MAX_PAGINATION_PAGES,
|
||||
validatePaginationUrl
|
||||
} from '../../util';
|
||||
import * as core from '@actions/core';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
@ -155,32 +158,46 @@ export class SemeruDistribution extends JavaBase {
|
||||
`jvm_impl=openj9`
|
||||
].join('&');
|
||||
|
||||
// need to iterate through all pages to retrieve the list of all versions
|
||||
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop
|
||||
let page_index = 0;
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
|
||||
let availableVersionsUrl: string | null =
|
||||
`https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
const availableVersions: ISemeruAvailableVersions[] = [];
|
||||
while (true) {
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
|
||||
const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
if (core.isDebug() && page_index === 0) {
|
||||
// url is identical except page_index so print it once for debug
|
||||
core.debug(
|
||||
`Gathering available versions from '${availableVersionsUrl}'`
|
||||
);
|
||||
}
|
||||
let pageCount = 0;
|
||||
if (core.isDebug()) {
|
||||
core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
|
||||
}
|
||||
|
||||
const paginationPage = (
|
||||
while (availableVersionsUrl) {
|
||||
pageCount++;
|
||||
const response =
|
||||
await this.http.getJson<ISemeruAvailableVersions[]>(
|
||||
availableVersionsUrl
|
||||
)
|
||||
).result;
|
||||
);
|
||||
const paginationPage = response.result;
|
||||
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
|
||||
if (
|
||||
nextUrl &&
|
||||
!validatePaginationUrl(nextUrl, 'https://api.adoptopenjdk.net')
|
||||
) {
|
||||
core.warning(
|
||||
`Ignoring pagination link with unexpected origin: ${nextUrl}`
|
||||
);
|
||||
availableVersionsUrl = null;
|
||||
} else {
|
||||
availableVersionsUrl = nextUrl;
|
||||
}
|
||||
if (paginationPage === null || paginationPage.length === 0) {
|
||||
// break infinity loop because we have reached end of pagination
|
||||
break;
|
||||
}
|
||||
|
||||
availableVersions.push(...paginationPage);
|
||||
page_index++;
|
||||
|
||||
if (pageCount >= MAX_PAGINATION_PAGES) {
|
||||
core.warning(
|
||||
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Semeru releases.`
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (core.isDebug()) {
|
||||
|
||||
@ -14,9 +14,12 @@ import {
|
||||
} from '../base-models';
|
||||
import {
|
||||
extractJdkFile,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getDownloadArchiveExtension,
|
||||
isVersionSatisfies,
|
||||
renameWinArchive
|
||||
renameWinArchive,
|
||||
MAX_PAGINATION_PAGES,
|
||||
validatePaginationUrl
|
||||
} from '../../util';
|
||||
|
||||
export enum TemurinImplementation {
|
||||
@ -31,7 +34,10 @@ export class TemurinDistribution extends JavaBase {
|
||||
super(`Temurin-${jvmImpl}`, installerOptions);
|
||||
}
|
||||
|
||||
protected async findPackageForDownload(
|
||||
/**
|
||||
* @internal For cross-distribution reuse only. Not intended as a public API.
|
||||
*/
|
||||
public async findPackageForDownload(
|
||||
version: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
const availableVersionsRaw = await this.getAvailableVersions();
|
||||
@ -123,32 +129,47 @@ export class TemurinDistribution extends JavaBase {
|
||||
`jvm_impl=${this.jvmImpl.toLowerCase()}`
|
||||
].join('&');
|
||||
|
||||
// need to iterate through all pages to retrieve the list of all versions
|
||||
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop
|
||||
let page_index = 0;
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
|
||||
let availableVersionsUrl: string | null =
|
||||
`https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
const availableVersions: ITemurinAvailableVersions[] = [];
|
||||
while (true) {
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
|
||||
const availableVersionsUrl = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
if (core.isDebug() && page_index === 0) {
|
||||
// url is identical except page_index so print it once for debug
|
||||
core.debug(
|
||||
`Gathering available versions from '${availableVersionsUrl}'`
|
||||
);
|
||||
}
|
||||
let pageCount = 0;
|
||||
if (core.isDebug()) {
|
||||
core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
|
||||
}
|
||||
|
||||
const paginationPage = (
|
||||
while (availableVersionsUrl) {
|
||||
pageCount++;
|
||||
const response =
|
||||
await this.http.getJson<ITemurinAvailableVersions[]>(
|
||||
availableVersionsUrl
|
||||
)
|
||||
).result;
|
||||
);
|
||||
const paginationPage = response.result;
|
||||
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
|
||||
if (
|
||||
nextUrl &&
|
||||
!validatePaginationUrl(nextUrl, 'https://api.adoptium.net')
|
||||
) {
|
||||
core.warning(
|
||||
`Ignoring pagination link with unexpected origin: ${nextUrl}`
|
||||
);
|
||||
availableVersionsUrl = null;
|
||||
} else {
|
||||
availableVersionsUrl = nextUrl;
|
||||
}
|
||||
|
||||
if (paginationPage === null || paginationPage.length === 0) {
|
||||
// break infinity loop because we have reached end of pagination
|
||||
break;
|
||||
}
|
||||
|
||||
availableVersions.push(...paginationPage);
|
||||
page_index++;
|
||||
|
||||
if (pageCount >= MAX_PAGINATION_PAGES) {
|
||||
core.warning(
|
||||
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Temurin releases.`
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (core.isDebug()) {
|
||||
@ -171,6 +192,11 @@ export class TemurinDistribution extends JavaBase {
|
||||
return 'mac';
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
case 'linux':
|
||||
if (fs.existsSync('/etc/alpine-release')) {
|
||||
return 'alpine-linux';
|
||||
}
|
||||
return 'linux';
|
||||
default:
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
49
src/util.ts
49
src/util.ts
@ -201,6 +201,55 @@ export function getGitHubHttpHeaders(): OutgoingHttpHeaders {
|
||||
return headers;
|
||||
}
|
||||
|
||||
export const MAX_PAGINATION_PAGES = 1000;
|
||||
|
||||
export function getNextPageUrlFromLinkHeader(
|
||||
headers?: Record<string, string | string[] | undefined>
|
||||
): string | null {
|
||||
if (!headers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const linkHeader = headers.link ?? headers.Link;
|
||||
if (!linkHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedLinkHeader = Array.isArray(linkHeader)
|
||||
? linkHeader.join(',')
|
||||
: linkHeader;
|
||||
|
||||
// Split into individual link-values and find the one with rel="next"
|
||||
// RFC 8288 allows rel to appear anywhere among the parameters
|
||||
const linkValues = normalizedLinkHeader.split(/,(?=\s*<)/);
|
||||
for (const linkValue of linkValues) {
|
||||
const urlMatch = linkValue.match(/<([^>]+)>/);
|
||||
if (!urlMatch) continue;
|
||||
|
||||
const params = linkValue.slice(urlMatch[0].length);
|
||||
// Use word boundary to match "next" as a standalone relation type
|
||||
// RFC 8288 allows space-separated relation types like rel="next prev"
|
||||
if (/;\s*rel="?[^"]*\bnext\b/i.test(params)) {
|
||||
return urlMatch[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function validatePaginationUrl(
|
||||
url: string,
|
||||
allowedOrigin: string
|
||||
): boolean {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const allowed = new URL(allowedOrigin);
|
||||
return parsed.origin === allowed.origin;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Rename archive to add extension because after downloading
|
||||
// archive does not contain extension type and it leads to some issues
|
||||
// on Windows runners without PowerShell Core.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user