diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bdc1379..11600d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -407,3 +407,27 @@ jobs: # popd > /dev/null - name: Remove components run: gu remove espresso llvm-toolchain nodejs python ruby wasm + + test-java-version-file: + name: java-version-file on ubuntu-latest + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write # for `native-image-pr-reports` option + steps: + - uses: actions/checkout@v4 + - run: echo '17' > .java-version + - name: Run setup-graalvm action + uses: ./ + with: + java-version-file: .java-version + distribution: 'graalvm' + native-image-job-reports: 'true' + native-image-pr-reports: 'true' + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Build HelloWorld executable with GraalVM Native Image on Windows + run: | + echo 'public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }' > HelloWorld.java + javac HelloWorld.java + native-image HelloWorld + ./helloworld diff --git a/README.md b/README.md index eef7ff2..22de72d 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,8 @@ can be replaced with: | Name | Default | Description | |-----------------|:--------:|-------------| -| `java-version`
*(required)* | n/a | Java version | +| `java-version` | n/a | Java version | +| `java-version-file`| n/a | The path to the `.java-version` file. See more details in [about `.java-version` file](#Java-version-file) | | `distribution` | `'graalvm'` | GraalVM distribution | | `java-package` | `'jdk'` | The package type (`'jdk'` or `'jdk+fx'`). Currently applies to Liberica only. | | `github-token` | `'${{ github.token }}'` | Token for communication with the GitHub API. Please set this to `${{ secrets.GITHUB_TOKEN }}` (see [templates](#templates)) to allow the action to authenticate with the GitHub API, which helps reduce rate-limiting issues. | @@ -203,6 +204,20 @@ can be replaced with: **) Make sure that Native Image is used only once per build job. Otherwise, the report is only generated for the last Native Image build.* +## Advanced Usage + +### Java-version file + +If the `java-version-file` input is specified, the action will try to extract the version from the file and install it. +Action is able to recognize all variants of the version description according to [jenv](https://github.com/jenv/jenv). +Valid entry options: +``` +major versions: 8, 11, 16, 17 +more specific versions: 1.8.0.2, 17.0, 11.0, 11.0.4, 8.0.232, 8.0.282+8 +early access (EA) versions: 15-ea, 15.0.0-ea, 15.0.0-ea.2, 15.0.0+2-ea +versions with specified distribution: openjdk64-11.0.2 +``` +If the file contains multiple versions, only the first one will be recognized. ## Contributing diff --git a/action.yml b/action.yml index dece5ee..19115cd 100644 --- a/action.yml +++ b/action.yml @@ -6,8 +6,9 @@ branding: color: 'blue' inputs: java-version: - required: true description: 'Java version. See examples of supported syntax in the README file.' + java-version-file: + description: 'The path to the `.java-version` file. See examples of supported syntax in README file' java-package: description: 'The package type (jdk or jdk+fx). Currently applies to Liberica only.' required: false diff --git a/src/constants.ts b/src/constants.ts index 11964e8..b14191d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,6 +3,7 @@ import * as otypes from '@octokit/types' export const INPUT_VERSION = 'version' export const INPUT_GDS_TOKEN = 'gds-token' export const INPUT_JAVA_VERSION = 'java-version' +export const INPUT_JAVA_VERSION_FILE = 'java-version-file' export const INPUT_JAVA_PACKAGE = 'java-package' export const INPUT_DISTRIBUTION = 'distribution' export const INPUT_COMPONENTS = 'components' @@ -12,6 +13,8 @@ export const INPUT_CACHE = 'cache' export const INPUT_CHECK_FOR_UPDATES = 'check-for-updates' export const INPUT_NI_MUSL = 'native-image-musl' +export const DISTRIBUTIONS_ONLY_MAJOR_VERSION = ['corretto'] + export const IS_LINUX = process.platform === 'linux' export const IS_MACOS = process.platform === 'darwin' export const IS_WINDOWS = process.platform === 'win32' diff --git a/src/main.ts b/src/main.ts index 75b373e..0a60b4f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import * as c from './constants' import * as core from '@actions/core' import * as graalvm from './graalvm' +import {getVersionFromFileContent} from './utils' import * as semver from 'semver' import {isFeatureAvailable as isCacheAvailable} from '@actions/cache' import {basename, join} from 'path' @@ -14,10 +15,12 @@ import {setUpNativeImageMusl} from './features/musl' import {setUpWindowsEnvironment} from './msvc' import {setUpNativeImageBuildReports} from './features/reports' import {exec} from '@actions/exec' +import fs from 'fs' async function run(): Promise { try { - const javaVersion = core.getInput(c.INPUT_JAVA_VERSION, {required: true}) + const javaVersionInput = core.getInput(c.INPUT_JAVA_VERSION) + const javaVersionFile = core.getInput(c.INPUT_JAVA_VERSION_FILE) const javaPackage = core.getInput(c.INPUT_JAVA_PACKAGE) const distribution = core.getInput(c.INPUT_DISTRIBUTION) const graalVMVersion = core.getInput(c.INPUT_VERSION) @@ -35,6 +38,32 @@ async function run(): Promise { const isGraalVMforJDK17OrLater = distribution.length > 0 || graalVMVersion.length == 0 + if (!javaVersionInput && !javaVersionFile) { + throw new Error('java-version or java-version-file input expected'); + } + + var javaVersion = javaVersionInput + if (!javaVersion) { + core.debug( + 'java-version input is empty, looking for java-version-file input' + ); + const content = fs.readFileSync(javaVersionFile).toString().trim(); + + javaVersion = getVersionFromFileContent( + content, + javaPackage, + javaVersionFile + ) + + core.debug(`Parsed version from file '${javaVersion}'`); + + if (!javaVersion) { + throw new Error( + `No supported version was found in file ${javaVersionFile}` + ); + } + } + if (c.IS_WINDOWS) { setUpWindowsEnvironment( javaVersion, diff --git a/src/utils.ts b/src/utils.ts index 1bd5335..74a5b71 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,7 +8,7 @@ import {ExecOptions, exec as e} from '@actions/exec' import {readFileSync, readdirSync} from 'fs' import {Octokit} from '@octokit/core' import {createHash} from 'crypto' -import {join} from 'path' +import {join, path} from 'path' // Set up Octokit for github.com only and in the same way as @actions/github (see https://git.io/Jy9YP) const baseUrl = 'https://api.github.com' @@ -201,3 +201,58 @@ export async function createPRComment(content: string): Promise { ) } } + +// copied from https://github.com/actions/setup-java/blob/a1c6c9c8677803c9f4bd31e0f15ac0844258f955/src/util.ts#L116 +export function getVersionFromFileContent( + content: string, + distributionName: string, + versionFile: string +): string | null { + let javaVersionRegExp: RegExp; + + function getFileName(versionFile: string) { + return path.basename(versionFile); + } + + const versionFileName = getFileName(versionFile); + if (versionFileName == '.tool-versions') { + javaVersionRegExp = + /^(java\s+)(?:\S*-)?v?(?(\d+)(\.\d+)?(\.\d+)?(\+\d+)?(-ea(\.\d+)?)?)$/m; + } else { + javaVersionRegExp = /(?(?<=(^|\s|-))(\d+\S*))(\s|$)/; + } + + const fileContent = content.match(javaVersionRegExp)?.groups?.version + ? (content.match(javaVersionRegExp)?.groups?.version as string) + : ''; + if (!fileContent) { + return null; + } + + core.debug(`Version from file '${fileContent}'`); + + const tentativeVersion = avoidOldNotation(fileContent); + const rawVersion = tentativeVersion.split('-')[0]; + + let version = semver.validRange(rawVersion) + ? tentativeVersion + : semver.coerce(tentativeVersion); + + core.debug(`Range version from file is '${version}'`); + + if (!version) { + return null; + } + + if (c.DISTRIBUTIONS_ONLY_MAJOR_VERSION.includes(distributionName)) { + const coerceVersion = semver.coerce(version) ?? version; + version = semver.major(coerceVersion).toString(); + } + + return version.toString(); +} + +// By convention, action expects version 8 in the format `8.*` instead of `1.8` +function avoidOldNotation(content: string): string { + return content.startsWith('1.') ? content.substring(2) : content; +}