From eb1418aa817168ee868a8dfc07f081c8674ba3de Mon Sep 17 00:00:00 2001 From: Nikolas Grottendieck Date: Sun, 16 Jan 2022 17:33:29 +0100 Subject: [PATCH] Add Maven Toolchains Declaration (#276) * Add (optional) Maven Toolchains Declaration after JDK is installed * Extract common/shared Maven constants Resolves #276 --- README.md | 12 +- __tests__/auth.test.ts | 7 +- __tests__/toolchains.test.ts | 292 +++++++++++++++++++++++++++++++++++ action.yml | 6 + dist/cleanup/index.js | 7 +- dist/setup/index.js | 185 +++++++++++++++++++++- docs/advanced-usage.md | 98 ++++++++++++ src/auth.ts | 9 +- src/constants.ts | 6 + src/setup-java.ts | 14 +- src/toolchains.ts | 158 +++++++++++++++++++ 11 files changed, 774 insertions(+), 20 deletions(-) create mode 100644 __tests__/toolchains.test.ts create mode 100644 src/toolchains.ts diff --git a/README.md b/README.md index 904521d..7f77410 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The `setup-java` action provides the following functionality for GitHub Actions - Caching dependencies managed by Apache Maven - Caching dependencies managed by Gradle - Caching dependencies managed by sbt +- [Maven Toolchains declaration](https://maven.apache.org/guides/mini/guide-using-toolchains.html) for specified JDK versions This action allows you to work with Java and Scala projects. @@ -37,7 +38,7 @@ This action allows you to work with Java and Scala projects. - `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predifined package managers. It can be one of "maven", "gradle" or "sbt". #### Maven options - The action has a bunch of inputs to generate maven's [settings.xml](https://maven.apache.org/settings.html) on the fly and pass the values to Apache Maven GPG Plugin. See [advanced usage](docs/advanced-usage.md) for more. + The action has a bunch of inputs to generate maven's [settings.xml](https://maven.apache.org/settings.html) on the fly and pass the values to Apache Maven GPG Plugin as well as Apache Maven Toolchains. See [advanced usage](docs/advanced-usage.md) for more. - `overwrite-settings`: By default action overwrites the settings.xml. In order to skip generation of file if it exists set this to `false`. @@ -53,6 +54,10 @@ This action allows you to work with Java and Scala projects. - `gpg-passphrase`: description: '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-vendor`: Name of Maven Toolchain Vendor if the default name of `${distribution}` is not wanted. + ### Basic Configuration #### Eclipse Temurin @@ -203,7 +208,11 @@ All versions are added to the PATH. The last version will be used and available 15 ``` +### Using Maven Toolchains +In the example above multiple JDKs are installed for the same job. The result after the last JDK is installed is a Maven Toolchains declaration containing references to all three JDKs. The values for `id`, `version`, and `vendor` of the individual Toolchain entries are the given input values for `distribution` and `java-version` (`vendor` being the combination of `${distribution}_${java-version}`) by default. + ### 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) @@ -219,6 +228,7 @@ All versions are added to the PATH. The last version will be used and available - [Publishing using Apache Maven](docs/advanced-usage.md#Publishing-using-Apache-Maven) - [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) ## License diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index 94ad7e1..c5d2ffc 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -5,9 +5,10 @@ import * as core from '@actions/core'; import os from 'os'; import * as auth from '../src/auth'; +import { M2_DIR, MVN_SETTINGS_FILE } from '../src/constants'; -const m2Dir = path.join(__dirname, auth.M2_DIR); -const settingsFile = path.join(m2Dir, auth.SETTINGS_FILE); +const m2Dir = path.join(__dirname, M2_DIR); +const settingsFile = path.join(m2Dir, MVN_SETTINGS_FILE); describe('auth tests', () => { let spyOSHomedir: jest.SpyInstance; @@ -38,7 +39,7 @@ describe('auth tests', () => { const password = 'TOLKIEN'; const altHome = path.join(__dirname, 'runner', 'settings'); - const altSettingsFile = path.join(altHome, auth.SETTINGS_FILE); + const altSettingsFile = path.join(altHome, MVN_SETTINGS_FILE); await io.rmRF(altHome); // ensure it doesn't already exist await auth.createAuthenticationSettings(id, username, password, altHome, true); diff --git a/__tests__/toolchains.test.ts b/__tests__/toolchains.test.ts new file mode 100644 index 0000000..ff6fdab --- /dev/null +++ b/__tests__/toolchains.test.ts @@ -0,0 +1,292 @@ +import * as fs from 'fs'; +import os from 'os'; +import * as path from 'path'; +import * as core from '@actions/core'; +import * as io from '@actions/io'; +import * as toolchains from '../src/toolchains'; +import { M2_DIR, MVN_TOOLCHAINS_FILE } from '../src/constants'; + +const m2Dir = path.join(__dirname, M2_DIR); +const toolchainsFile = path.join(m2Dir, MVN_TOOLCHAINS_FILE); + +describe('toolchains tests', () => { + let spyOSHomedir: jest.SpyInstance; + let spyInfo: jest.SpyInstance; + + beforeEach(async () => { + await io.rmRF(m2Dir); + spyOSHomedir = jest.spyOn(os, 'homedir'); + spyOSHomedir.mockReturnValue(__dirname); + spyInfo = jest.spyOn(core, 'info'); + spyInfo.mockImplementation(() => null); + }, 300000); + + afterAll(async () => { + try { + await io.rmRF(m2Dir); + } catch { + console.log('Failed to remove test directories'); + } + jest.resetAllMocks(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + }, 100000); + + it('creates toolchains.xml in alternate locations', async () => { + const jdkInfo = { + version: '17', + vendor: 'Eclipse Temurin', + id: 'temurin_17', + jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64' + }; + + const altHome = path.join(__dirname, 'runner', 'toolchains'); + const altToolchainsFile = path.join(altHome, MVN_TOOLCHAINS_FILE); + await io.rmRF(altHome); // ensure it doesn't already exist + + await toolchains.createToolchainsSettings({ + jdkInfo, + settingsDirectory: altHome, + overwriteSettings: true + }); + + expect(fs.existsSync(m2Dir)).toBe(false); + expect(fs.existsSync(toolchainsFile)).toBe(false); + + expect(fs.existsSync(altHome)).toBe(true); + expect(fs.existsSync(altToolchainsFile)).toBe(true); + expect(fs.readFileSync(altToolchainsFile, 'utf-8')).toEqual( + toolchains.generateToolchainDefinition( + '', + jdkInfo.version, + jdkInfo.vendor, + jdkInfo.id, + jdkInfo.jdkHome + ) + ); + + await io.rmRF(altHome); + }, 100000); + + it('creates toolchains.xml with minimal configuration', async () => { + const jdkInfo = { + version: '17', + vendor: 'Eclipse Temurin', + id: 'temurin_17', + jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64' + }; + + const result = ` + + + jdk + + 17 + Eclipse Temurin + temurin_17 + + + /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64 + + +`; + + await toolchains.createToolchainsSettings({ + jdkInfo, + settingsDirectory: m2Dir, + overwriteSettings: true + }); + + expect(fs.existsSync(m2Dir)).toBe(true); + expect(fs.existsSync(toolchainsFile)).toBe(true); + expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual( + toolchains.generateToolchainDefinition( + '', + jdkInfo.version, + jdkInfo.vendor, + jdkInfo.id, + jdkInfo.jdkHome + ) + ); + expect( + toolchains.generateToolchainDefinition( + '', + jdkInfo.version, + jdkInfo.vendor, + jdkInfo.id, + jdkInfo.jdkHome + ) + ).toEqual(result); + }, 100000); + + it('reuses existing toolchains.xml files', async () => { + const jdkInfo = { + version: '17', + vendor: 'Eclipse Temurin', + id: 'temurin_17', + jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64' + }; + + const originalFile = ` + + jdk + + 1.6 + Sun + sun_1.6 + + + /opt/jdk/sun/1.6 + + + `; + const result = ` + + + jdk + + 1.6 + Sun + sun_1.6 + + + /opt/jdk/sun/1.6 + + + + jdk + + 17 + Eclipse Temurin + temurin_17 + + + /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64 + + +`; + + fs.mkdirSync(m2Dir, { recursive: true }); + fs.writeFileSync(toolchainsFile, originalFile); + expect(fs.existsSync(m2Dir)).toBe(true); + expect(fs.existsSync(toolchainsFile)).toBe(true); + + await toolchains.createToolchainsSettings({ + jdkInfo, + settingsDirectory: m2Dir, + overwriteSettings: true + }); + + expect(fs.existsSync(m2Dir)).toBe(true); + expect(fs.existsSync(toolchainsFile)).toBe(true); + expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual( + toolchains.generateToolchainDefinition( + originalFile, + jdkInfo.version, + jdkInfo.vendor, + jdkInfo.id, + jdkInfo.jdkHome + ) + ); + expect( + toolchains.generateToolchainDefinition( + originalFile, + jdkInfo.version, + jdkInfo.vendor, + jdkInfo.id, + jdkInfo.jdkHome + ) + ).toEqual(result); + }, 100000); + + it('does not overwrite existing toolchains.xml files', async () => { + const jdkInfo = { + version: '17', + vendor: 'Eclipse Temurin', + id: 'temurin_17', + jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64' + }; + + const originalFile = ` + + jdk + + 1.6 + Sun + sun_1.6 + + + /opt/jdk/sun/1.6 + + + `; + + fs.mkdirSync(m2Dir, { recursive: true }); + fs.writeFileSync(toolchainsFile, originalFile); + expect(fs.existsSync(m2Dir)).toBe(true); + expect(fs.existsSync(toolchainsFile)).toBe(true); + + await toolchains.createToolchainsSettings({ + jdkInfo, + settingsDirectory: m2Dir, + overwriteSettings: false + }); + + expect(fs.existsSync(m2Dir)).toBe(true); + expect(fs.existsSync(toolchainsFile)).toBe(true); + expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(originalFile); + }, 100000); + + it('generates valid toolchains.xml with minimal configuration', () => { + const jdkInfo = { + version: 'JAVA_VERSION', + vendor: 'JAVA_VENDOR', + id: 'VENDOR_VERSION', + jdkHome: 'JAVA_HOME' + }; + + const expectedToolchains = ` + + + jdk + + ${jdkInfo.version} + ${jdkInfo.vendor} + ${jdkInfo.id} + + + ${jdkInfo.jdkHome} + + +`; + + expect( + toolchains.generateToolchainDefinition( + '', + jdkInfo.version, + jdkInfo.vendor, + jdkInfo.id, + jdkInfo.jdkHome + ) + ).toEqual(expectedToolchains); + }, 100000); + + it('creates toolchains.xml with correct id when none is supplied', async () => { + const version = '17'; + const distributionName = 'temurin'; + const id = 'temurin_17'; + const jdkHome = '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64'; + + await toolchains.configureToolchains(version, distributionName, jdkHome, undefined); + + expect(fs.existsSync(m2Dir)).toBe(true); + expect(fs.existsSync(toolchainsFile)).toBe(true); + expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual( + toolchains.generateToolchainDefinition('', version, distributionName, id, jdkHome) + ); + }, 100000); +}); diff --git a/action.yml b/action.yml index 499eae3..7bd7180 100644 --- a/action.yml +++ b/action.yml @@ -62,6 +62,12 @@ inputs: token: description: Used to pull java versions from setup-java. Since there is a default value, token is typically not supplied by the user. default: ${{ 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' + 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' + required: false outputs: distribution: description: 'Distribution of Java that has been installed' diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index e4fcbd5..df9ec30 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -68406,7 +68406,7 @@ else { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; +exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; exports.INPUT_JAVA_VERSION = 'java-version'; exports.INPUT_ARCHITECTURE = 'architecture'; @@ -68426,6 +68426,11 @@ exports.INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE'; exports.INPUT_CACHE = 'cache'; exports.INPUT_JOB_STATUS = 'job-status'; exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; +exports.M2_DIR = '.m2'; +exports.MVN_SETTINGS_FILE = 'settings.xml'; +exports.MVN_TOOLCHAINS_FILE = 'toolchains.xml'; +exports.INPUT_MVN_TOOLCHAIN_ID = 'mvn-toolchain-id'; +exports.INPUT_MVN_TOOLCHAIN_VENDOR = 'mvn-toolchain-vendor'; /***/ }), diff --git a/dist/setup/index.js b/dist/setup/index.js index e19da55..0bdc9dc 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -103216,7 +103216,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.generate = exports.createAuthenticationSettings = exports.configureAuthentication = exports.SETTINGS_FILE = exports.M2_DIR = void 0; +exports.generate = exports.createAuthenticationSettings = exports.configureAuthentication = void 0; const path = __importStar(__nccwpck_require__(1017)); const core = __importStar(__nccwpck_require__(2186)); const io = __importStar(__nccwpck_require__(7436)); @@ -103226,14 +103226,12 @@ const xmlbuilder2_1 = __nccwpck_require__(151); const constants = __importStar(__nccwpck_require__(9042)); const gpg = __importStar(__nccwpck_require__(3759)); const util_1 = __nccwpck_require__(2629); -exports.M2_DIR = '.m2'; -exports.SETTINGS_FILE = 'settings.xml'; function configureAuthentication() { return __awaiter(this, void 0, void 0, function* () { 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 settingsDirectory = core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), exports.M2_DIR); + const settingsDirectory = core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), constants.M2_DIR); const overwriteSettings = util_1.getBooleanInput(constants.INPUT_OVERWRITE_SETTINGS, true); const gpgPrivateKey = core.getInput(constants.INPUT_GPG_PRIVATE_KEY) || constants.INPUT_DEFAULT_GPG_PRIVATE_KEY; const gpgPassphrase = core.getInput(constants.INPUT_GPG_PASSPHRASE) || @@ -103252,7 +103250,7 @@ function configureAuthentication() { exports.configureAuthentication = configureAuthentication; function createAuthenticationSettings(id, username, password, settingsDirectory, overwriteSettings, gpgPassphrase = undefined) { return __awaiter(this, void 0, void 0, function* () { - core.info(`Creating ${exports.SETTINGS_FILE} with server-id: ${id}`); + 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) // otherwise use the home/.m2/ path yield io.mkdirP(settingsDirectory); @@ -103294,7 +103292,7 @@ function generate(id, username, password, gpgPassphrase) { exports.generate = generate; function write(directory, settings, overwriteSettings) { return __awaiter(this, void 0, void 0, function* () { - const location = path.join(directory, exports.SETTINGS_FILE); + const location = path.join(directory, constants.MVN_SETTINGS_FILE); const settingsExists = fs.existsSync(location); if (settingsExists && overwriteSettings) { core.info(`Overwriting existing file ${location}`); @@ -103510,7 +103508,7 @@ function isProbablyGradleDaemonProblem(packageManager, error) { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; +exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; exports.INPUT_JAVA_VERSION = 'java-version'; exports.INPUT_ARCHITECTURE = 'architecture'; @@ -103530,6 +103528,11 @@ exports.INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE'; exports.INPUT_CACHE = 'cache'; exports.INPUT_JOB_STATUS = 'job-status'; exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; +exports.M2_DIR = '.m2'; +exports.MVN_SETTINGS_FILE = 'settings.xml'; +exports.MVN_TOOLCHAINS_FILE = 'toolchains.xml'; +exports.INPUT_MVN_TOOLCHAIN_ID = 'mvn-toolchain-id'; +exports.INPUT_MVN_TOOLCHAIN_VENDOR = 'mvn-toolchain-vendor'; /***/ }), @@ -104952,6 +104955,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); const auth = __importStar(__nccwpck_require__(3497)); const util_1 = __nccwpck_require__(2629); +const toolchains = __importStar(__nccwpck_require__(9322)); const constants = __importStar(__nccwpck_require__(9042)); const cache_1 = __nccwpck_require__(4810); const path = __importStar(__nccwpck_require__(1017)); @@ -104966,8 +104970,12 @@ function run() { const jdkFile = core.getInput(constants.INPUT_JDK_FILE); const cache = core.getInput(constants.INPUT_CACHE); const checkLatest = util_1.getBooleanInput(constants.INPUT_CHECK_LATEST, false); + let toolchainIds = core.getMultilineInput(constants.INPUT_MVN_TOOLCHAIN_ID); + if (versions.length !== toolchainIds.length) { + toolchainIds = []; + } core.startGroup('Installed distributions'); - for (const version of versions) { + for (const [index, version] of versions.entries()) { const installerOptions = { architecture, packageType, @@ -104979,6 +104987,7 @@ function run() { throw new Error(`No supported distribution was found for input ${distributionName}`); } const result = yield distribution.setupJava(); + yield toolchains.configureToolchains(version, distributionName, result.path, toolchainIds[index]); core.info(''); core.info('Java configuration:'); core.info(` Distribution: ${distributionName}`); @@ -105002,6 +105011,166 @@ function run() { run(); +/***/ }), + +/***/ 9322: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.generateToolchainDefinition = exports.createToolchainsSettings = exports.configureToolchains = void 0; +const fs = __importStar(__nccwpck_require__(7147)); +const os = __importStar(__nccwpck_require__(2037)); +const path = __importStar(__nccwpck_require__(1017)); +const core = __importStar(__nccwpck_require__(2186)); +const io = __importStar(__nccwpck_require__(7436)); +const constants = __importStar(__nccwpck_require__(9042)); +const util_1 = __nccwpck_require__(2629); +const xmlbuilder2_1 = __nccwpck_require__(151); +function configureToolchains(version, distributionName, jdkHome, toolchainId) { + return __awaiter(this, void 0, void 0, function* () { + const vendor = core.getInput(constants.INPUT_MVN_TOOLCHAIN_VENDOR) || distributionName; + const id = toolchainId || `${vendor}_${version}`; + const settingsDirectory = core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), constants.M2_DIR); + const overwriteSettings = util_1.getBooleanInput(constants.INPUT_OVERWRITE_SETTINGS, true); + yield createToolchainsSettings({ + jdkInfo: { + version, + vendor, + id, + jdkHome + }, + settingsDirectory, + overwriteSettings + }); + }); +} +exports.configureToolchains = configureToolchains; +function createToolchainsSettings({ jdkInfo, settingsDirectory, overwriteSettings }) { + return __awaiter(this, void 0, void 0, function* () { + core.info(`Creating ${constants.MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}`); + // when an alternate m2 location is specified use only that location (no .m2 directory) + // otherwise use the home/.m2/ path + yield io.mkdirP(settingsDirectory); + const originalToolchains = yield readExistingToolchainsFile(settingsDirectory); + const updatedToolchains = generateToolchainDefinition(originalToolchains, jdkInfo.version, jdkInfo.vendor, jdkInfo.id, jdkInfo.jdkHome); + yield writeToolchainsFileToDisk(settingsDirectory, updatedToolchains, overwriteSettings); + }); +} +exports.createToolchainsSettings = createToolchainsSettings; +// only exported for testing purposes +function generateToolchainDefinition(original, version, vendor, id, jdkHome) { + let xmlObj; + if (original === null || original === void 0 ? void 0 : original.length) { + xmlObj = xmlbuilder2_1.create(original) + .root() + .ele({ + toolchain: { + type: 'jdk', + provides: { + version: `${version}`, + vendor: `${vendor}`, + id: `${id}` + }, + configuration: { + jdkHome: `${jdkHome}` + } + } + }); + } + else + xmlObj = xmlbuilder2_1.create({ + toolchains: { + '@xmlns': 'https://maven.apache.org/TOOLCHAINS/1.1.0', + '@xmlns:xsi': 'https://www.w3.org/2001/XMLSchema-instance', + '@xsi:schemaLocation': 'https://maven.apache.org/TOOLCHAINS/1.1.0 https://maven.apache.org/xsd/toolchains-1.1.0.xsd', + toolchain: [ + { + type: 'jdk', + provides: { + version: `${version}`, + vendor: `${vendor}`, + id: `${id}` + }, + configuration: { + jdkHome: `${jdkHome}` + } + } + ] + } + }); + return xmlObj.end({ + format: 'xml', + wellFormed: false, + headless: false, + prettyPrint: true, + width: 80 + }); +} +exports.generateToolchainDefinition = generateToolchainDefinition; +function readExistingToolchainsFile(directory) { + return __awaiter(this, void 0, void 0, function* () { + const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE); + if (fs.existsSync(location)) { + return fs.readFileSync(location, { + encoding: 'utf-8', + flag: 'r' + }); + } + return ''; + }); +} +function writeToolchainsFileToDisk(directory, settings, overwriteSettings) { + return __awaiter(this, void 0, void 0, function* () { + const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE); + const settingsExists = fs.existsSync(location); + if (settingsExists && overwriteSettings) { + core.info(`Overwriting existing file ${location}`); + } + else if (!settingsExists) { + core.info(`Writing to ${location}`); + } + else { + core.info(`Skipping generation of ${location} because file already exists and overwriting is not enabled`); + return; + } + return fs.writeFileSync(location, settings, { + encoding: 'utf-8', + flag: 'w' + }); + }); +} + + /***/ }), /***/ 2629: diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index befe52d..93ebd85 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -14,6 +14,7 @@ - [Publishing using Apache Maven](#Publishing-using-Apache-Maven) - [Publishing using Gradle](#Publishing-using-Gradle) - [Hosted Tool Cache](#Hosted-Tool-Cache) +- [Modifying Maven Toolchains](#Modifying-Maven-Toolchains) See [action.yml](../action.yml) for more details on task inputs. @@ -350,3 +351,100 @@ GitHub Hosted Runners have a tool cache that comes with some Java versions pre-i Currently, LTS versions of Adopt OpenJDK (`adopt`) are cached on the GitHub Hosted Runners. The tools cache gets updated on a weekly basis. For information regarding locally cached versions of Java on GitHub hosted runners, check out [GitHub Actions Virtual Environments](https://github.com/actions/virtual-environments). + +## Modifying Maven Toolchains +The `setup-java` action generates a basic [Maven Toolchains declaration](https://maven.apache.org/guides/mini/guide-using-toolchains.html) for specified Java versions by either creating a minimal toolchains file or extending an existing declaration with the additional JDKs. + +### Installing Multiple JDKs With Toolchains +Subsequent calls to `setup-java` with distinct distribution and version parameters will continue to extend the toolchains declaration and make all specified Java versions available. + +```yaml +steps: +- uses: actions/setup-java@v3 + with: + distribution: '' + java-version: | + 8 + 11 + +- uses: actions/setup-java@v3 + with: + distribution: '' + java-version: 15 +``` + +The result is a Toolchain with entries for JDKs 8, 11 and 15. You can even combine this with custom JDKs of arbitrary versions: + +```yaml +- run: | + download_url="https://example.com/java/jdk/6u45-b06/jdk-6u45-linux-x64.tar.gz" + wget -O $RUNNER_TEMP/java_package.tar.gz $download_url +- uses: actions/setup-java@v3 + with: + distribution: 'jdkfile' + jdkFile: ${{ runner.temp }}/java_package.tar.gz + java-version: '1.6' + architecture: x64 +``` + +This will generate a Toolchains entry with the following values: `version: 1.6`, `vendor: jkdfile`, `id: Oracle_1.6`. + +### Modifying The Toolchain Vendor For JDKs +Each JDK provider will receive a default `vendor` using the `distribution` input value but this can be overridden with the `mvn-toolchain-vendor` parameter as follows. + +```yaml +- run: | + download_url="https://example.com/java/jdk/6u45-b06/jdk-6u45-linux-x64.tar.gz" + wget -O $RUNNER_TEMP/java_package.tar.gz $download_url +- uses: actions/setup-java@v3 + with: + distribution: 'jdkfile' + jdkFile: ${{ runner.temp }}/java_package.tar.gz + java-version: '1.6' + architecture: x64 + mvn-toolchain-vendor: 'Oracle' +``` + +This will generate a Toolchains entry with the following values: `version: 1.6`, `vendor: Oracle`, `id: Oracle_1.6`. + +In case you install multiple versions of Java at once with multi-line `java-version` input setting the `mvn-toolchain-vendor` still only accepts one value and will use this value for installed JDKs as expected when installing multiple versions of the same `distribution`. + +```yaml +steps: +- uses: actions/setup-java@v3 + with: + distribution: '' + java-version: | + 8 + 11 + mvn-toolchain-vendor: Eclipse Temurin +``` + +### Modifying The Toolchain ID For JDKs +Each JDK provider will receive a default `id` based on the combination of `distribution` and `java-version` in the format of `distribution_java-version` (e.g. `temurin_11`) but this can be overridden with the `mvn-toolchain-id` parameter as follows. + +```yaml +steps: +- uses: actions/checkout@v3 +- uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + mvn-toolchain-id: 'some_other_id' +- run: java -cp java HelloWorldApp +``` + +In case you install multiple versions of Java at once you can use the same syntax as used in `java-versions`. Please note that you have to declare an ID for all Java versions that will be installed or the `mvn-toolchain-id` instruction will be skipped wholesale due to mapping ambiguities. + +```yaml +steps: +- uses: actions/setup-java@v3 + with: + distribution: '' + java-version: | + 8 + 11 + mvn-toolchain-id: | + something_else + something_other +``` diff --git a/src/auth.ts b/src/auth.ts index a52306b..f697ddc 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -10,15 +10,12 @@ import * as constants from './constants'; import * as gpg from './gpg'; import { getBooleanInput } from './util'; -export const M2_DIR = '.m2'; -export const SETTINGS_FILE = 'settings.xml'; - 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 settingsDirectory = - core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), M2_DIR); + core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), constants.M2_DIR); const overwriteSettings = getBooleanInput(constants.INPUT_OVERWRITE_SETTINGS, true); const gpgPrivateKey = core.getInput(constants.INPUT_GPG_PRIVATE_KEY) || constants.INPUT_DEFAULT_GPG_PRIVATE_KEY; @@ -54,7 +51,7 @@ export async function createAuthenticationSettings( overwriteSettings: boolean, gpgPassphrase: string | undefined = undefined ) { - core.info(`Creating ${SETTINGS_FILE} with server-id: ${id}`); + 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) // otherwise use the home/.m2/ path await io.mkdirP(settingsDirectory); @@ -106,7 +103,7 @@ export function generate( } async function write(directory: string, settings: string, overwriteSettings: boolean) { - const location = path.join(directory, SETTINGS_FILE); + const location = path.join(directory, constants.MVN_SETTINGS_FILE); const settingsExists = fs.existsSync(location); if (settingsExists && overwriteSettings) { core.info(`Overwriting existing file ${location}`); diff --git a/src/constants.ts b/src/constants.ts index 94d7667..ad8709e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -20,3 +20,9 @@ export const INPUT_CACHE = 'cache'; export const INPUT_JOB_STATUS = 'job-status'; export const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; + +export const M2_DIR = '.m2'; +export const MVN_SETTINGS_FILE = 'settings.xml'; +export const MVN_TOOLCHAINS_FILE = 'toolchains.xml'; +export const INPUT_MVN_TOOLCHAIN_ID = 'mvn-toolchain-id'; +export const INPUT_MVN_TOOLCHAIN_VENDOR = 'mvn-toolchain-vendor'; diff --git a/src/setup-java.ts b/src/setup-java.ts index 621883a..048c5dd 100644 --- a/src/setup-java.ts +++ b/src/setup-java.ts @@ -1,6 +1,7 @@ import * as core from '@actions/core'; import * as auth from './auth'; import { getBooleanInput, isCacheFeatureAvailable } from './util'; +import * as toolchains from './toolchains'; import * as constants from './constants'; import { restore } from './cache'; import * as path from 'path'; @@ -16,9 +17,14 @@ async function run() { const jdkFile = core.getInput(constants.INPUT_JDK_FILE); const cache = core.getInput(constants.INPUT_CACHE); const checkLatest = getBooleanInput(constants.INPUT_CHECK_LATEST, false); + let toolchainIds = core.getMultilineInput(constants.INPUT_MVN_TOOLCHAIN_ID); + + if (versions.length !== toolchainIds.length) { + toolchainIds = []; + } core.startGroup('Installed distributions'); - for (const version of versions) { + for (const [index, version] of versions.entries()) { const installerOptions: JavaInstallerOptions = { architecture, packageType, @@ -32,6 +38,12 @@ async function run() { } const result = await distribution.setupJava(); + await toolchains.configureToolchains( + version, + distributionName, + result.path, + toolchainIds[index] + ); core.info(''); core.info('Java configuration:'); diff --git a/src/toolchains.ts b/src/toolchains.ts new file mode 100644 index 0000000..8c7d72c --- /dev/null +++ b/src/toolchains.ts @@ -0,0 +1,158 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as core from '@actions/core'; +import * as io from '@actions/io'; +import * as constants from './constants'; + +import { getBooleanInput } from './util'; +import { create as xmlCreate } from 'xmlbuilder2'; + +interface JdkInfo { + version: string; + vendor: string; + id: string; + jdkHome: string; +} + +export async function configureToolchains( + version: string, + distributionName: string, + jdkHome: string, + toolchainId?: string +) { + const vendor = core.getInput(constants.INPUT_MVN_TOOLCHAIN_VENDOR) || distributionName; + const id = toolchainId || `${vendor}_${version}`; + const settingsDirectory = + core.getInput(constants.INPUT_SETTINGS_PATH) || path.join(os.homedir(), constants.M2_DIR); + const overwriteSettings = getBooleanInput(constants.INPUT_OVERWRITE_SETTINGS, true); + + await createToolchainsSettings({ + jdkInfo: { + version, + vendor, + id, + jdkHome + }, + settingsDirectory, + overwriteSettings + }); +} + +export async function createToolchainsSettings({ + jdkInfo, + settingsDirectory, + overwriteSettings +}: { + jdkInfo: JdkInfo; + settingsDirectory: string; + overwriteSettings: boolean; +}) { + core.info( + `Creating ${constants.MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}` + ); + // when an alternate m2 location is specified use only that location (no .m2 directory) + // otherwise use the home/.m2/ path + await io.mkdirP(settingsDirectory); + const originalToolchains = await readExistingToolchainsFile(settingsDirectory); + const updatedToolchains = generateToolchainDefinition( + originalToolchains, + jdkInfo.version, + jdkInfo.vendor, + jdkInfo.id, + jdkInfo.jdkHome + ); + await writeToolchainsFileToDisk(settingsDirectory, updatedToolchains, overwriteSettings); +} + +// only exported for testing purposes +export function generateToolchainDefinition( + original: string, + version: string, + vendor: string, + id: string, + jdkHome: string +) { + let xmlObj; + if (original?.length) { + xmlObj = xmlCreate(original) + .root() + .ele({ + toolchain: { + type: 'jdk', + provides: { + version: `${version}`, + vendor: `${vendor}`, + id: `${id}` + }, + configuration: { + jdkHome: `${jdkHome}` + } + } + }); + } else + xmlObj = xmlCreate({ + toolchains: { + '@xmlns': 'https://maven.apache.org/TOOLCHAINS/1.1.0', + '@xmlns:xsi': 'https://www.w3.org/2001/XMLSchema-instance', + '@xsi:schemaLocation': + 'https://maven.apache.org/TOOLCHAINS/1.1.0 https://maven.apache.org/xsd/toolchains-1.1.0.xsd', + toolchain: [ + { + type: 'jdk', + provides: { + version: `${version}`, + vendor: `${vendor}`, + id: `${id}` + }, + configuration: { + jdkHome: `${jdkHome}` + } + } + ] + } + }); + + return xmlObj.end({ + format: 'xml', + wellFormed: false, + headless: false, + prettyPrint: true, + width: 80 + }); +} + +async function readExistingToolchainsFile(directory: string) { + const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE); + if (fs.existsSync(location)) { + return fs.readFileSync(location, { + encoding: 'utf-8', + flag: 'r' + }); + } + return ''; +} + +async function writeToolchainsFileToDisk( + directory: string, + settings: string, + overwriteSettings: boolean +) { + const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE); + const settingsExists = fs.existsSync(location); + if (settingsExists && overwriteSettings) { + core.info(`Overwriting existing file ${location}`); + } else if (!settingsExists) { + core.info(`Writing to ${location}`); + } else { + core.info( + `Skipping generation of ${location} because file already exists and overwriting is not enabled` + ); + return; + } + + return fs.writeFileSync(location, settings, { + encoding: 'utf-8', + flag: 'w' + }); +}