diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76b6b22..d491f39 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,9 @@ jobs: ] components: [''] include: + - java-version: '22-ea' + distribution: 'graalvm' + os: ubuntu-latest - java-version: '21' distribution: '' os: ubuntu-latest diff --git a/README.md b/README.md index bf349c0..9758a0e 100644 --- a/README.md +++ b/README.md @@ -15,34 +15,6 @@ This action: - has built-in support for GraalVM components and the [GraalVM Updater][gu] -## Migrating from GraalVM 22.3 or Earlier to the New GraalVM for JDK 17 and Later - -The [GraalVM for JDK 17 and JDK 20 release](https://medium.com/graalvm/a-new-graalvm-release-and-new-free-license-4aab483692f5) aligns the GraalVM version scheme with OpenJDK. -As a result, this action no longer requires the `version` option to select a specific GraalVM version. -At the same time, it introduces a new `distribution` option to select a specific GraalVM distribution (`graalvm`, `graalvm-community`, or `mandrel`). -Therefore, to migrate your workflow to use the latest GraalVM release, replace the `version` with the `distribution` option in the workflow `yml` config, for example: - -```yml -# ... -- uses: graalvm/setup-graalvm@v1 - with: - java-version: '17' - version: '22.3.2' # Old 'version' option for the GraalVM version - # ... -``` - -can be replaced with: - -```yml -# ... -- uses: graalvm/setup-graalvm@v1 - with: - java-version: '17.0.7' # for a specific JDK 17; or '17' for the latest JDK 17 - distribution: 'graalvm' # New 'distribution' option - # ... -``` - - ## Templates ### Quickstart Template @@ -162,6 +134,35 @@ jobs: + +## Migrating from GraalVM 22.3 or Earlier to the New GraalVM for JDK 17 and Later + +The [GraalVM for JDK 17 and JDK 20 release](https://medium.com/graalvm/a-new-graalvm-release-and-new-free-license-4aab483692f5) aligns the GraalVM version scheme with OpenJDK. +As a result, this action no longer requires the `version` option to select a specific GraalVM version. +At the same time, it introduces a new `distribution` option to select a specific GraalVM distribution (`graalvm`, `graalvm-community`, or `mandrel`). +Therefore, to migrate your workflow to use the latest GraalVM release, replace the `version` with the `distribution` option in the workflow `yml` config, for example: + +```yml +# ... +- uses: graalvm/setup-graalvm@v1 + with: + java-version: '17' + version: '22.3.2' # Old 'version' option for the GraalVM version + # ... +``` + +can be replaced with: + +```yml +# ... +- uses: graalvm/setup-graalvm@v1 + with: + java-version: '17.0.7' # for a specific JDK 17; or '17' for the latest JDK 17 + distribution: 'graalvm' # New 'distribution' option + # ... +``` + + ## Options | Name | Default | Description | diff --git a/__tests__/cache.test.ts b/__tests__/cache.test.ts index e59a546..cc73bf5 100644 --- a/__tests__/cache.test.ts +++ b/__tests__/cache.test.ts @@ -101,14 +101,14 @@ describe('dependency cache', () => { }) it('throws error if unsupported package manager specified', () => { - return expect(restore('ant')).rejects.toThrowError( + return expect(restore('ant')).rejects.toThrow( 'unknown package manager specified: ant' ) }) describe('for maven', () => { it('throws error if no pom.xml found', async () => { - await expect(restore('maven')).rejects.toThrowError( + await expect(restore('maven')).rejects.toThrow( `No file in ${projectRoot( workspace )} matched to [**/pom.xml], make sure you have checked out the target repository` @@ -118,14 +118,14 @@ describe('dependency cache', () => { createFile(join(workspace, 'pom.xml')) await restore('maven') - expect(spyCacheRestore).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith('maven cache is not found') + expect(spyCacheRestore).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith('maven cache is not found') }) }) describe('for gradle', () => { it('throws error if no build.gradle found', async () => { - await expect(restore('gradle')).rejects.toThrowError( + await expect(restore('gradle')).rejects.toThrow( `No file in ${projectRoot( workspace )} matched to [**/*.gradle*,**/gradle-wrapper.properties,buildSrc/**/Versions.kt,buildSrc/**/Dependencies.kt], make sure you have checked out the target repository` @@ -135,17 +135,17 @@ describe('dependency cache', () => { createFile(join(workspace, 'build.gradle')) await restore('gradle') - expect(spyCacheRestore).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith('gradle cache is not found') + expect(spyCacheRestore).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found') }) it('downloads cache based on build.gradle.kts', async () => { createFile(join(workspace, 'build.gradle.kts')) await restore('gradle') - expect(spyCacheRestore).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith('gradle cache is not found') + expect(spyCacheRestore).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found') }) }) it('downloads cache based on buildSrc/Versions.kt', async () => { @@ -153,13 +153,13 @@ describe('dependency cache', () => { createFile(join(workspace, 'buildSrc', 'Versions.kt')) await restore('gradle') - expect(spyCacheRestore).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith('gradle cache is not found') + expect(spyCacheRestore).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found') }) describe('for sbt', () => { it('throws error if no build.sbt found', async () => { - await expect(restore('sbt')).rejects.toThrowError( + await expect(restore('sbt')).rejects.toThrow( `No file in ${projectRoot( workspace )} matched to [**/*.sbt,**/project/build.properties,**/project/**.{scala,sbt}], make sure you have checked out the target repository` @@ -169,9 +169,9 @@ describe('dependency cache', () => { createFile(join(workspace, 'build.sbt')) await restore('sbt') - expect(spyCacheRestore).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith('sbt cache is not found') + expect(spyCacheRestore).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith('sbt cache is not found') }) }) }) @@ -191,7 +191,7 @@ describe('dependency cache', () => { }) it('throws error if unsupported package manager specified', () => { - return expect(save('ant')).rejects.toThrowError( + return expect(save('ant')).rejects.toThrow( 'unknown package manager specified: ant' ) }) @@ -201,10 +201,10 @@ describe('dependency cache', () => { createStateForMissingBuildFile() await save('maven') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalled() - expect(spyInfo).toBeCalledWith( + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith( expect.stringMatching(/^Cache saved with the key:.*/) ) }) @@ -225,24 +225,26 @@ describe('dependency cache', () => { it('uploads cache even if no pom.xml found', async () => { createStateForMissingBuildFile() await save('maven') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() }) it('does not upload cache if no restore run before', async () => { createFile(join(workspace, 'pom.xml')) await save('maven') - expect(spyCacheSave).not.toBeCalled() - expect(spyWarning).toBeCalledWith('Error retrieving key from state.') + expect(spyCacheSave).not.toHaveBeenCalled() + expect(spyWarning).toHaveBeenCalledWith( + 'Error retrieving key from state.' + ) }) it('uploads cache', async () => { createFile(join(workspace, 'pom.xml')) createStateForSuccessfulRestore() await save('maven') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith( + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith( expect.stringMatching(/^Cache saved with the key:.*/) ) }) @@ -252,24 +254,26 @@ describe('dependency cache', () => { createStateForMissingBuildFile() await save('gradle') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() }) it('does not upload cache if no restore run before', async () => { createFile(join(workspace, 'build.gradle')) await save('gradle') - expect(spyCacheSave).not.toBeCalled() - expect(spyWarning).toBeCalledWith('Error retrieving key from state.') + expect(spyCacheSave).not.toHaveBeenCalled() + expect(spyWarning).toHaveBeenCalledWith( + 'Error retrieving key from state.' + ) }) it('uploads cache based on build.gradle', async () => { createFile(join(workspace, 'build.gradle')) createStateForSuccessfulRestore() await save('gradle') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith( + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith( expect.stringMatching(/^Cache saved with the key:.*/) ) }) @@ -278,9 +282,9 @@ describe('dependency cache', () => { createStateForSuccessfulRestore() await save('gradle') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith( + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith( expect.stringMatching(/^Cache saved with the key:.*/) ) }) @@ -290,9 +294,9 @@ describe('dependency cache', () => { createStateForSuccessfulRestore() await save('gradle') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith( + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith( expect.stringMatching(/^Cache saved with the key:.*/) ) }) @@ -301,24 +305,26 @@ describe('dependency cache', () => { it('uploads cache even if no build.sbt found', async () => { createStateForMissingBuildFile() await save('sbt') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() }) it('does not upload cache if no restore run before', async () => { createFile(join(workspace, 'build.sbt')) await save('sbt') - expect(spyCacheSave).not.toBeCalled() - expect(spyWarning).toBeCalledWith('Error retrieving key from state.') + expect(spyCacheSave).not.toHaveBeenCalled() + expect(spyWarning).toHaveBeenCalledWith( + 'Error retrieving key from state.' + ) }) it('uploads cache', async () => { createFile(join(workspace, 'build.sbt')) createStateForSuccessfulRestore() await save('sbt') - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() - expect(spyInfo).toBeCalledWith( + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() + expect(spyInfo).toHaveBeenCalledWith( expect.stringMatching(/^Cache saved with the key:.*/) ) }) diff --git a/__tests__/cleanup.test.ts b/__tests__/cleanup.test.ts index 87b89ae..b43d151 100644 --- a/__tests__/cleanup.test.ts +++ b/__tests__/cleanup.test.ts @@ -27,7 +27,6 @@ import {run as cleanup} from '../src/cleanup' import * as core from '@actions/core' import * as cache from '@actions/cache' -import * as util from '../src/utils' describe('cleanup', () => { let spyWarning: jest.SpyInstance> @@ -62,8 +61,8 @@ describe('cleanup', () => { return name === 'cache' ? 'gradle' : '' }) await cleanup() - expect(spyCacheSave).toBeCalled() - expect(spyWarning).not.toBeCalled() + expect(spyCacheSave).toHaveBeenCalled() + expect(spyWarning).not.toHaveBeenCalled() }) it('does not fail even though the save process throws error', async () => { @@ -74,7 +73,7 @@ describe('cleanup', () => { return name === 'cache' ? 'gradle' : '' }) await cleanup() - expect(spyCacheSave).toBeCalled() + expect(spyCacheSave).toHaveBeenCalled() }) }) diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts new file mode 100644 index 0000000..b35791f --- /dev/null +++ b/__tests__/utils.test.ts @@ -0,0 +1,34 @@ +import * as path from 'path' +import {expect, test} from '@jest/globals' +import {toSemVer} from '../src/utils' + +test('convert version', async () => { + for (var inputAndExpectedOutput of [ + ['22', '22.0.0'], + ['22.0', '22.0.0'], + ['22.0.0', '22.0.0'], + ['22.0.0.2', '22.0.0-2'], + ['22-ea', '22.0.0-ea'], + ['22.0-ea', '22.0.0-ea'], + ['22.0.0-ea', '22.0.0-ea'] + ]) { + expect(toSemVer(inputAndExpectedOutput[0])).toBe(inputAndExpectedOutput[1]) + } +}) + +test('convert invalid version', async () => { + for (var input of ['dev', 'abc', 'a.b.c']) { + let error = new Error('unexpected') + try { + toSemVer(input) + } catch (err) { + if (!(err instanceof Error)) { + fail(`Unexpected non-Error: ${err}`) + } + error = err + } + + expect(error).not.toBeUndefined() + expect(error.message).toContain('Unable to convert') + } +}) diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index ea401f3..93c8561 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -92864,11 +92864,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.createPRComment = exports.isPREvent = exports.toSemVer = exports.calculateSHA256 = exports.downloadExtractAndCacheJDK = exports.downloadAndExtractJDK = exports.getMatchingTags = exports.getTaggedRelease = exports.getLatestRelease = exports.exec = void 0; +exports.createPRComment = exports.isPREvent = exports.toSemVer = exports.calculateSHA256 = exports.downloadExtractAndCacheJDK = exports.downloadAndExtractJDK = exports.getMatchingTags = exports.getTaggedRelease = exports.getLatestRelease = exports.getLatestPrerelease = exports.exec = void 0; const c = __importStar(__nccwpck_require__(9042)); const core = __importStar(__nccwpck_require__(2186)); const github = __importStar(__nccwpck_require__(5438)); const httpClient = __importStar(__nccwpck_require__(6255)); +const semver = __importStar(__nccwpck_require__(1383)); const tc = __importStar(__nccwpck_require__(7784)); const exec_1 = __nccwpck_require__(1514); const fs_1 = __nccwpck_require__(7147); @@ -92894,6 +92895,23 @@ function exec(commandLine, args, options) { }); } exports.exec = exec; +function getLatestPrerelease(repo) { + return __awaiter(this, void 0, void 0, function* () { + const githubToken = getGitHubToken(); + const options = githubToken.length > 0 ? { auth: githubToken } : {}; + const octokit = new GitHubDotCom(options); + const releases = (yield octokit.request('GET /repos/{owner}/{repo}/releases', { + owner: c.GRAALVM_GH_USER, + repo + })).data; + const firstPrerelease = releases.find(r => r.prerelease); + if (!firstPrerelease) { + throw new Error(`Unable to find latest prerelease in ${repo}`); + } + return firstPrerelease; + }); +} +exports.getLatestPrerelease = getLatestPrerelease; function getLatestRelease(repo) { return __awaiter(this, void 0, void 0, function* () { const githubToken = getGitHubToken(); @@ -92982,17 +93000,22 @@ function findJavaHomeInSubfolder(searchPath) { throw new Error(`Unexpected amount of directory items found: ${baseContents.length}`); } } -/** - * This helper turns GraalVM version numbers (e.g., `22.0.0.2`) into valid - * semver.org versions (e.g., `22.0.0-2`), which is needed because - * @actions/tool-cache uses `semver` to validate versions. - */ function toSemVer(version) { const parts = version.split('.'); - const major = parts[0]; - const minor = parts.length > 1 ? parts[1] : '0'; - const patch = parts.length > 2 ? parts.slice(2).join('-') : '0'; - return `${major}.${minor}.${patch}`; + if (parts.length === 4) { + /** + * Turn legacy GraalVM version numbers (e.g., `22.0.0.2`) into valid + * semver.org versions (e.g., `22.0.0-2`). + */ + return `${parts[0]}.${parts[1]}.${parts.slice(2).join('-')}`; + } + const versionParts = version.split('-', 2); + const suffix = versionParts.length === 2 ? '-' + versionParts[1] : ''; + const validVersion = semver.valid(semver.coerce(versionParts[0]) + suffix); + if (!validVersion) { + throw new Error(`Unable to convert '${version}' to semantic version. ${c.ERROR_HINT}`); + } + return validVersion; } exports.toSemVer = toSemVer; function isPREvent() { diff --git a/dist/main/index.js b/dist/main/index.js index 65ff422..de5a601 100644 --- a/dist/main/index.js +++ b/dist/main/index.js @@ -93659,7 +93659,7 @@ const assert_1 = __nccwpck_require__(9491); const uuid_1 = __nccwpck_require__(5840); function downloadGraalVMEELegacy(gdsToken, version, javaVersion) { return __awaiter(this, void 0, void 0, function* () { - const userAgent = `GraalVMGitHubAction/1.1.6 (arch:${c.GRAALVM_ARCH}; os:${c.GRAALVM_PLATFORM}; java:${javaVersion})`; + const userAgent = `GraalVMGitHubAction/1.1.7 (arch:${c.GRAALVM_ARCH}; os:${c.GRAALVM_PLATFORM}; java:${javaVersion})`; const baseArtifact = yield fetchArtifact(userAgent, 'isBase:True', version, javaVersion); return downloadArtifact(gdsToken, userAgent, baseArtifact); }); @@ -93850,6 +93850,7 @@ const tool_cache_1 = __nccwpck_require__(7784); const path_1 = __nccwpck_require__(1017); const GRAALVM_DL_BASE = 'https://download.oracle.com/graalvm'; const GRAALVM_CE_DL_BASE = `https://github.com/graalvm/${c.GRAALVM_RELEASES_REPO}/releases/download`; +const ORACLE_GRAALVM_REPO_EA_BUILDS = 'oracle-graalvm-dev-builds'; const GRAALVM_REPO_DEV_BUILDS = 'graalvm-ce-dev-builds'; const GRAALVM_JDK_TAG_PREFIX = 'jdk-'; const GRAALVM_TAG_PREFIX = 'vm-'; @@ -93860,7 +93861,8 @@ function setUpGraalVMJDK(javaVersionOrDev) { return setUpGraalVMJDKDevBuild(); } const javaVersion = javaVersionOrDev; - let toolName = determineToolName(javaVersion, false); + const toolName = determineToolName(javaVersion, false); + let downloadName = toolName; let downloadUrl; if (javaVersion.includes('.')) { if (semver.valid(javaVersion)) { @@ -93868,7 +93870,6 @@ function setUpGraalVMJDK(javaVersionOrDev) { const minorJavaVersion = semver.minor(javaVersion); const patchJavaVersion = semver.patch(javaVersion); const isGARelease = minorJavaVersion === 0 && patchJavaVersion === 0; - let downloadName = toolName; if (isGARelease) { // For GA versions of JDKs, /archive/ does not use minor and patch version (see https://www.oracle.com/java/technologies/jdk-script-friendly-urls/) downloadName = determineToolName(majorJavaVersion.toString(), false); @@ -93879,14 +93880,31 @@ function setUpGraalVMJDK(javaVersionOrDev) { throw new Error(`java-version set to '${javaVersion}'. Please make sure the java-version is set correctly. ${c.ERROR_HINT}`); } } + else if (javaVersion === '22-ea') { + downloadUrl = yield findLatestEABuildDownloadUrl(javaVersion); + } else { - downloadUrl = `${GRAALVM_DL_BASE}/${javaVersion}/latest/${toolName}${c.GRAALVM_FILE_EXTENSION}`; + downloadUrl = `${GRAALVM_DL_BASE}/${javaVersion}/latest/${downloadName}${c.GRAALVM_FILE_EXTENSION}`; } const downloader = () => __awaiter(this, void 0, void 0, function* () { return downloadGraalVMJDK(downloadUrl, javaVersion); }); return (0, utils_1.downloadExtractAndCacheJDK)(downloader, toolName, javaVersion); }); } exports.setUpGraalVMJDK = setUpGraalVMJDK; +function findLatestEABuildDownloadUrl(javaEaVersion) { + return __awaiter(this, void 0, void 0, function* () { + const latestPrerelease = yield (0, utils_1.getLatestPrerelease)(ORACLE_GRAALVM_REPO_EA_BUILDS); + const expectedFileNamePrefix = 'graalvm-jdk-'; + const expectedFileNameSuffix = `_${c.JDK_PLATFORM}-${c.JDK_ARCH}_bin${c.GRAALVM_FILE_EXTENSION}`; + for (const asset of latestPrerelease.assets) { + if (asset.name.startsWith(expectedFileNamePrefix) && + asset.name.endsWith(expectedFileNameSuffix)) { + return asset.browser_download_url; + } + } + throw new Error(`Could not find Oracle GraalVM build for ${javaEaVersion}. ${c.ERROR_HINT}`); + }); +} function setUpGraalVMJDKCE(javaVersionOrDev) { return __awaiter(this, void 0, void 0, function* () { if (javaVersionOrDev === c.VERSION_DEV) { @@ -94621,11 +94639,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.createPRComment = exports.isPREvent = exports.toSemVer = exports.calculateSHA256 = exports.downloadExtractAndCacheJDK = exports.downloadAndExtractJDK = exports.getMatchingTags = exports.getTaggedRelease = exports.getLatestRelease = exports.exec = void 0; +exports.createPRComment = exports.isPREvent = exports.toSemVer = exports.calculateSHA256 = exports.downloadExtractAndCacheJDK = exports.downloadAndExtractJDK = exports.getMatchingTags = exports.getTaggedRelease = exports.getLatestRelease = exports.getLatestPrerelease = exports.exec = void 0; const c = __importStar(__nccwpck_require__(9042)); const core = __importStar(__nccwpck_require__(2186)); const github = __importStar(__nccwpck_require__(5438)); const httpClient = __importStar(__nccwpck_require__(6255)); +const semver = __importStar(__nccwpck_require__(1383)); const tc = __importStar(__nccwpck_require__(7784)); const exec_1 = __nccwpck_require__(1514); const fs_1 = __nccwpck_require__(7147); @@ -94651,6 +94670,23 @@ function exec(commandLine, args, options) { }); } exports.exec = exec; +function getLatestPrerelease(repo) { + return __awaiter(this, void 0, void 0, function* () { + const githubToken = getGitHubToken(); + const options = githubToken.length > 0 ? { auth: githubToken } : {}; + const octokit = new GitHubDotCom(options); + const releases = (yield octokit.request('GET /repos/{owner}/{repo}/releases', { + owner: c.GRAALVM_GH_USER, + repo + })).data; + const firstPrerelease = releases.find(r => r.prerelease); + if (!firstPrerelease) { + throw new Error(`Unable to find latest prerelease in ${repo}`); + } + return firstPrerelease; + }); +} +exports.getLatestPrerelease = getLatestPrerelease; function getLatestRelease(repo) { return __awaiter(this, void 0, void 0, function* () { const githubToken = getGitHubToken(); @@ -94739,17 +94775,22 @@ function findJavaHomeInSubfolder(searchPath) { throw new Error(`Unexpected amount of directory items found: ${baseContents.length}`); } } -/** - * This helper turns GraalVM version numbers (e.g., `22.0.0.2`) into valid - * semver.org versions (e.g., `22.0.0-2`), which is needed because - * @actions/tool-cache uses `semver` to validate versions. - */ function toSemVer(version) { const parts = version.split('.'); - const major = parts[0]; - const minor = parts.length > 1 ? parts[1] : '0'; - const patch = parts.length > 2 ? parts.slice(2).join('-') : '0'; - return `${major}.${minor}.${patch}`; + if (parts.length === 4) { + /** + * Turn legacy GraalVM version numbers (e.g., `22.0.0.2`) into valid + * semver.org versions (e.g., `22.0.0-2`). + */ + return `${parts[0]}.${parts[1]}.${parts.slice(2).join('-')}`; + } + const versionParts = version.split('-', 2); + const suffix = versionParts.length === 2 ? '-' + versionParts[1] : ''; + const validVersion = semver.valid(semver.coerce(versionParts[0]) + suffix); + if (!validVersion) { + throw new Error(`Unable to convert '${version}' to semantic version. ${c.ERROR_HINT}`); + } + return validVersion; } exports.toSemVer = toSemVer; function isPREvent() { diff --git a/package-lock.json b/package-lock.json index 0bdaa58..cbc3c3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "setup-graalvm", - "version": "1.1.6", + "version": "1.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "setup-graalvm", - "version": "1.1.6", + "version": "1.1.7", "license": "UPL", "dependencies": { "@actions/cache": "^3.2.3", diff --git a/package.json b/package.json index ad9e8f8..e27dd87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "setup-graalvm", - "version": "1.1.6", + "version": "1.1.7", "private": true, "description": "GitHub Action for GraalVM", "main": "lib/main.js", diff --git a/src/constants.ts b/src/constants.ts index a021f0e..4088517 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -49,6 +49,9 @@ export type LatestReleaseResponse = export type MatchingRefsResponse = otypes.Endpoints['GET /repos/{owner}/{repo}/git/matching-refs/{ref}']['response'] +export type ReleasesResponse = + otypes.Endpoints['GET /repos/{owner}/{repo}/releases']['response'] + function determineJDKArchitecture(): string { switch (process.arch) { case 'x64': { diff --git a/src/gds.ts b/src/gds.ts index 2323e32..3c96d48 100644 --- a/src/gds.ts +++ b/src/gds.ts @@ -31,7 +31,7 @@ export async function downloadGraalVMEELegacy( version: string, javaVersion: string ): Promise { - const userAgent = `GraalVMGitHubAction/1.1.6 (arch:${c.GRAALVM_ARCH}; os:${c.GRAALVM_PLATFORM}; java:${javaVersion})` + const userAgent = `GraalVMGitHubAction/1.1.7 (arch:${c.GRAALVM_ARCH}; os:${c.GRAALVM_PLATFORM}; java:${javaVersion})` const baseArtifact = await fetchArtifact( userAgent, 'isBase:True', diff --git a/src/graalvm.ts b/src/graalvm.ts index f2be101..f8c1fd8 100644 --- a/src/graalvm.ts +++ b/src/graalvm.ts @@ -3,9 +3,11 @@ import * as semver from 'semver' import { downloadAndExtractJDK, downloadExtractAndCacheJDK, + getLatestPrerelease, getLatestRelease, getMatchingTags, - getTaggedRelease + getTaggedRelease, + toSemVer } from './utils' import {downloadGraalVMEELegacy} from './gds' import {downloadTool} from '@actions/tool-cache' @@ -13,6 +15,7 @@ import {basename} from 'path' const GRAALVM_DL_BASE = 'https://download.oracle.com/graalvm' const GRAALVM_CE_DL_BASE = `https://github.com/graalvm/${c.GRAALVM_RELEASES_REPO}/releases/download` +const ORACLE_GRAALVM_REPO_EA_BUILDS = 'oracle-graalvm-dev-builds' const GRAALVM_REPO_DEV_BUILDS = 'graalvm-ce-dev-builds' const GRAALVM_JDK_TAG_PREFIX = 'jdk-' const GRAALVM_TAG_PREFIX = 'vm-' @@ -26,7 +29,8 @@ export async function setUpGraalVMJDK( return setUpGraalVMJDKDevBuild() } const javaVersion = javaVersionOrDev - let toolName = determineToolName(javaVersion, false) + const toolName = determineToolName(javaVersion, false) + let downloadName = toolName let downloadUrl: string if (javaVersion.includes('.')) { if (semver.valid(javaVersion)) { @@ -34,7 +38,6 @@ export async function setUpGraalVMJDK( const minorJavaVersion = semver.minor(javaVersion) const patchJavaVersion = semver.patch(javaVersion) const isGARelease = minorJavaVersion === 0 && patchJavaVersion === 0 - let downloadName = toolName if (isGARelease) { // For GA versions of JDKs, /archive/ does not use minor and patch version (see https://www.oracle.com/java/technologies/jdk-script-friendly-urls/) downloadName = determineToolName(majorJavaVersion.toString(), false) @@ -45,13 +48,36 @@ export async function setUpGraalVMJDK( `java-version set to '${javaVersion}'. Please make sure the java-version is set correctly. ${c.ERROR_HINT}` ) } + } else if (javaVersion === '22-ea') { + downloadUrl = await findLatestEABuildDownloadUrl(javaVersion) } else { - downloadUrl = `${GRAALVM_DL_BASE}/${javaVersion}/latest/${toolName}${c.GRAALVM_FILE_EXTENSION}` + downloadUrl = `${GRAALVM_DL_BASE}/${javaVersion}/latest/${downloadName}${c.GRAALVM_FILE_EXTENSION}` } const downloader = async () => downloadGraalVMJDK(downloadUrl, javaVersion) return downloadExtractAndCacheJDK(downloader, toolName, javaVersion) } +async function findLatestEABuildDownloadUrl( + javaEaVersion: string +): Promise { + const latestPrerelease = await getLatestPrerelease( + ORACLE_GRAALVM_REPO_EA_BUILDS + ) + const expectedFileNamePrefix = 'graalvm-jdk-' + const expectedFileNameSuffix = `_${c.JDK_PLATFORM}-${c.JDK_ARCH}_bin${c.GRAALVM_FILE_EXTENSION}` + for (const asset of latestPrerelease.assets) { + if ( + asset.name.startsWith(expectedFileNamePrefix) && + asset.name.endsWith(expectedFileNameSuffix) + ) { + return asset.browser_download_url + } + } + throw new Error( + `Could not find Oracle GraalVM build for ${javaEaVersion}. ${c.ERROR_HINT}` + ) +} + export async function setUpGraalVMJDKCE( javaVersionOrDev: string ): Promise { diff --git a/src/utils.ts b/src/utils.ts index 0288dad..cf92b7f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import * as c from './constants' import * as core from '@actions/core' import * as github from '@actions/github' import * as httpClient from '@actions/http-client' +import * as semver from 'semver' import * as tc from '@actions/tool-cache' import {ExecOptions, exec as e} from '@actions/exec' import {readFileSync, readdirSync} from 'fs' @@ -33,6 +34,25 @@ export async function exec( } } +export async function getLatestPrerelease( + repo: string +): Promise { + const githubToken = getGitHubToken() + const options = githubToken.length > 0 ? {auth: githubToken} : {} + const octokit = new GitHubDotCom(options) + const releases: c.ReleasesResponse['data'] = ( + await octokit.request('GET /repos/{owner}/{repo}/releases', { + owner: c.GRAALVM_GH_USER, + repo + }) + ).data + const firstPrerelease = releases.find(r => r.prerelease) + if (!firstPrerelease) { + throw new Error(`Unable to find latest prerelease in ${repo}`) + } + return firstPrerelease +} + export async function getLatestRelease( repo: string ): Promise { @@ -135,17 +155,25 @@ function findJavaHomeInSubfolder(searchPath: string): string { } } -/** - * This helper turns GraalVM version numbers (e.g., `22.0.0.2`) into valid - * semver.org versions (e.g., `22.0.0-2`), which is needed because - * @actions/tool-cache uses `semver` to validate versions. - */ export function toSemVer(version: string): string { const parts = version.split('.') - const major = parts[0] - const minor = parts.length > 1 ? parts[1] : '0' - const patch = parts.length > 2 ? parts.slice(2).join('-') : '0' - return `${major}.${minor}.${patch}` + if (parts.length === 4) { + /** + * Turn legacy GraalVM version numbers (e.g., `22.0.0.2`) into valid + * semver.org versions (e.g., `22.0.0-2`). + */ + return `${parts[0]}.${parts[1]}.${parts.slice(2).join('-')}` + } + + const versionParts = version.split('-', 2) + const suffix = versionParts.length === 2 ? '-' + versionParts[1] : '' + const validVersion = semver.valid(semver.coerce(versionParts[0]) + suffix) + if (!validVersion) { + throw new Error( + `Unable to convert '${version}' to semantic version. ${c.ERROR_HINT}` + ) + } + return validVersion } export function isPREvent(): boolean {