From b878223aa527dc255785e1e8e8b3da206baf5a3b Mon Sep 17 00:00:00 2001 From: Nikolas Grottendieck Date: Sat, 25 Jun 2022 14:01:05 +0200 Subject: [PATCH] [JavaToolInstallerV0] add Maven Toolchains declaration (#16492) Add Maven Toolchains declaration generation based on the JDK input parameters --- .../resources.resjson/en-US/resources.resjson | 2 + Tasks/JavaToolInstallerV0/Tests/L0.ts | 11 ++ .../Tests/L0MavenToolchains.ts | 106 +++++++++++++++ .../JavaToolInstallerV0/javatoolinstaller.ts | 5 + Tasks/JavaToolInstallerV0/package-lock.json | 55 ++++++++ Tasks/JavaToolInstallerV0/package.json | 3 +- Tasks/JavaToolInstallerV0/task.json | 8 ++ Tasks/JavaToolInstallerV0/task.loc.json | 8 ++ Tasks/JavaToolInstallerV0/toolchains.ts | 121 ++++++++++++++++++ 9 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 Tasks/JavaToolInstallerV0/Tests/L0MavenToolchains.ts create mode 100644 Tasks/JavaToolInstallerV0/toolchains.ts diff --git a/Tasks/JavaToolInstallerV0/Strings/resources.resjson/en-US/resources.resjson b/Tasks/JavaToolInstallerV0/Strings/resources.resjson/en-US/resources.resjson index f03a34183ed4..c89b0f4ae0fd 100644 --- a/Tasks/JavaToolInstallerV0/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/JavaToolInstallerV0/Strings/resources.resjson/en-US/resources.resjson @@ -26,6 +26,8 @@ "loc.input.help.cleanDestinationDirectory": "Select this option to clean the destination directory before JDK is extracted into it.", "loc.input.label.createExtractDirectory": "Create directory for extracting", "loc.input.help.createExtractDirectory": "By default, task is creating a directory similar to this JAVA_HOME_8_X64_OpenJDK_zip for extracting JDK. This option allows to disable creation of this folder, in this case, JDK will be located in the root of jdkDestinationDirectory", + "loc.input.label.createMavenToolchains": "Create Maven Toolchains declaration", + "loc.input.help.createMavenToolchains": "Create a Maven Toolchains declaration in the official default location describing the JDK that was installed with this task. Existing Maven Toolchains are modified accordingly.", "loc.messages.DownloadFromAzureBlobStorage": "Downloading artifacts from Azure blob storage, Container Name: %s", "loc.messages.SuccessFullyFetchedItemList": "Successfully fetched list of items", "loc.messages.StorageAccountDoesNotExist": "Failed to get azure storage account with name %s.", diff --git a/Tasks/JavaToolInstallerV0/Tests/L0.ts b/Tasks/JavaToolInstallerV0/Tests/L0.ts index b392114a571d..dacff7c5f886 100644 --- a/Tasks/JavaToolInstallerV0/Tests/L0.ts +++ b/Tasks/JavaToolInstallerV0/Tests/L0.ts @@ -51,4 +51,15 @@ describe('JavaToolInstaller L0 Suite', function () { assert(testRunner.failed, 'task should have failed'); }); + + it('should run successfully when creating a Maven Toolchains Declaration', function () { + this.timeout(20000); + + const testPath: string = path.join(__dirname, 'L0MavenToolchains.js'); + const testRunner: ttm.MockTestRunner = new ttm.MockTestRunner(testPath); + + testRunner.run(); + + assert(testRunner.succeeded, 'task should have succeeded.'); + }); }); diff --git a/Tasks/JavaToolInstallerV0/Tests/L0MavenToolchains.ts b/Tasks/JavaToolInstallerV0/Tests/L0MavenToolchains.ts new file mode 100644 index 000000000000..71cff08b203d --- /dev/null +++ b/Tasks/JavaToolInstallerV0/Tests/L0MavenToolchains.ts @@ -0,0 +1,106 @@ +import mockanswer = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import msRestAzure = require('azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-common'); +import path = require('path'); +import os = require('os'); +import mockTask = require('azure-pipelines-task-lib/mock-task'); + +const taskPath = path.join(__dirname, '..', 'javatoolinstaller.js'); +const tr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +const destDir = path.join(os.homedir(), '.m2'); +const destFile = path.join(destDir, 'toolchains.xml'); + +tr.setInput("versionSpec", "8.1"); +tr.setInput("jdkSource", "Azure Storage") +tr.setInput("artifactProvider", "azureStorage"); +tr.setInput("azureResourceManagerEndpoint", "ARM1"); +tr.setInput("azureStorageAccountName", "storage1"); +tr.setInput("azureContainerName", "container1"); +tr.setInput("azureCommonVirtualPath", ""); +tr.setInput("fileType", ".tar.gz"); +tr.setInput("destinationFolder", "javaJDK"); +tr.setInput("cleanDestinationFolder", "false"); +tr.setInput("createMavenToolchains", "true"); + +// provide answers for task mock +const a: mockanswer.TaskLibAnswers = { + exist: {[destDir]: true, [destFile]: true}, + find: {[destDir]: [destDir], [destFile]: [destFile]}, + rmRF: { }, + stats: {[destDir]: {'isDirectory':'true'}, [destFile]: {'isFile':'true'}}, +}; +tr.setAnswers(a); + +tr.registerMock("azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-storage", { + StorageManagementClient: function (A, B) { + return { + storageAccounts: { + get: function (A) { + return { + properties: { + primaryEndpoints: { + blob: "primaryBlobUrl" + } + }, + id: "StorageAccountUrl" + } + }, + listkeys: function (A, B, C) { + return ["accesskey1", "accessKey2"]; + } + } + } + }, + StorageAccounts: { + getResourceGroupNameFromUri: function (A) { + return "storageAccountResouceGroupName"; + } + } +}); + +tr.registerMock("azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-common", { + ApplicationTokenCredentials: function(A,B,C,D,E,F,G) { + return {}; + } +}); + +tr.registerMock('./AzureStorageArtifacts/AzureStorageArtifactDownloader',{ + AzureStorageArtifactDownloader: function(A,B,C) { + return { + downloadArtifacts: function(A,B) { + return "pathFromDownloader"; + } + } + } +}) + +const mtl = require("azure-pipelines-tool-lib/tool") +const mtlClone = Object.assign({}, mtl); + +mtlClone.prependPath = function(variable1: string, variable2: string) { + return {}; +}; + +tr.registerMock("azure-pipelines-tool-lib/tool", mtlClone); + +const mfs = require('fs') +const mfsClone = Object.assign({}, mfs); + +mfsClone.lstatSync = function(variable: string) { + return { + isDirectory: function() { + return true; + } + }; +}; + +mfsClone.existsSync = function (variable: string) { + if (variable === "DestinationDirectory\\econdlevelJDK2" || variable === "DestinationDirectory/econdlevelJDK2") { + return false; + } else return true; +} + +tr.registerMock('fs', mfsClone); + +tr.run(); diff --git a/Tasks/JavaToolInstallerV0/javatoolinstaller.ts b/Tasks/JavaToolInstallerV0/javatoolinstaller.ts index 90563ffa12eb..9a9345260f0d 100644 --- a/Tasks/JavaToolInstallerV0/javatoolinstaller.ts +++ b/Tasks/JavaToolInstallerV0/javatoolinstaller.ts @@ -8,6 +8,7 @@ import * as telemetry from 'azure-pipelines-tasks-utility-common/telemetry'; import { AzureStorageArtifactDownloader } from './AzureStorageArtifacts/AzureStorageArtifactDownloader'; import { JavaFilesExtractor, BIN_FOLDER } from './FileExtractor/JavaFilesExtractor'; import taskutils = require('./taskutils'); +import {configureToolchains} from "./toolchains"; const VOLUMES_FOLDER: string = '/Volumes'; const JDK_FOLDER: string = '/Library/Java/JavaVirtualMachines'; @@ -32,6 +33,7 @@ async function getJava(versionSpec: string, jdkArchitectureOption: string): Prom const fromAzure: boolean = ('AzureStorage' == taskLib.getInput('jdkSourceOption', true)); const extractLocation: string = taskLib.getPathInput('jdkDestinationDirectory', true); const cleanDestinationDirectory: boolean = taskLib.getBoolInput('cleanDestinationDirectory', false); + const createMavenToolchains = taskLib.getBoolInput('createMavenToolchains', false); let compressedFileExtension: string; let jdkDirectory: string; const extendedJavaHome: string = `JAVA_HOME_${versionSpec}_${jdkArchitectureOption}`.toUpperCase(); @@ -76,6 +78,9 @@ async function getJava(versionSpec: string, jdkArchitectureOption: string): Prom console.log(taskLib.loc('SetExtendedJavaHome', extendedJavaHome, jdkDirectory)); taskLib.setVariable(extendedJavaHome, jdkDirectory); toolLib.prependPath(path.join(jdkDirectory, BIN_FOLDER)); + if (createMavenToolchains) { + await configureToolchains(version, 'unknown', jdkDirectory); + } } /** diff --git a/Tasks/JavaToolInstallerV0/package-lock.json b/Tasks/JavaToolInstallerV0/package-lock.json index 0f2c3ceecac8..951b3db25f0f 100644 --- a/Tasks/JavaToolInstallerV0/package-lock.json +++ b/Tasks/JavaToolInstallerV0/package-lock.json @@ -4,6 +4,38 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@oozcitak/dom": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.8.tgz", + "integrity": "sha512-MoOnLBNsF+ok0HjpAvxYxR4piUhRDCEWK0ot3upwOOHYudJd30j6M+LNcE8RKpwfnclAX9T66nXXzkytd29XSw==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "requires": { + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==" + }, "@types/concat-stream": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", @@ -3561,6 +3593,29 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, + "xmlbuilder2": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-2.4.1.tgz", + "integrity": "sha512-vliUplZsk5vJnhxXN/mRcij/AE24NObTUm/Zo4vkLusgayO6s3Et5zLEA14XZnY1c3hX5o1ToR0m0BJOPy0UvQ==", + "requires": { + "@oozcitak/dom": "1.15.8", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "@types/node": "*", + "js-yaml": "3.14.0" + }, + "dependencies": { + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/Tasks/JavaToolInstallerV0/package.json b/Tasks/JavaToolInstallerV0/package.json index 09aab1a5ee41..d9b174785c5c 100644 --- a/Tasks/JavaToolInstallerV0/package.json +++ b/Tasks/JavaToolInstallerV0/package.json @@ -30,7 +30,8 @@ "azure-pipelines-task-lib": "^3.1.2", "azure-pipelines-tasks-azure-arm-rest-v2": "2.0.4", "azure-pipelines-tasks-utility-common": "^3.0.3", - "azure-pipelines-tool-lib": "^1.0.2" + "azure-pipelines-tool-lib": "^1.0.2", + "xmlbuilder2": "^2.4.1" }, "devDependencies": { "typescript": "4.0.2" diff --git a/Tasks/JavaToolInstallerV0/task.json b/Tasks/JavaToolInstallerV0/task.json index 92018ecfbde8..f5778d08f7e2 100644 --- a/Tasks/JavaToolInstallerV0/task.json +++ b/Tasks/JavaToolInstallerV0/task.json @@ -134,6 +134,14 @@ "defaultValue": true, "visibleRule": "jdkSourceOption != PreInstalled", "helpMarkDown": "By default, task is creating a directory similar to this JAVA_HOME_8_X64_OpenJDK_zip for extracting JDK. This option allows to disable creation of this folder, in this case, JDK will be located in the root of jdkDestinationDirectory" + }, + { + "name": "createMavenToolchains", + "type": "boolean", + "label": "Create Maven Toolchains declaration", + "required": false, + "defaultValue": false, + "helpMarkDown": "Create a Maven Toolchains declaration in the official default location describing the JDK that was installed with this task. Existing Maven Toolchains are modified accordingly." } ], "dataSourceBindings": [ diff --git a/Tasks/JavaToolInstallerV0/task.loc.json b/Tasks/JavaToolInstallerV0/task.loc.json index 685ff4fa034d..a9d2a6d24b89 100644 --- a/Tasks/JavaToolInstallerV0/task.loc.json +++ b/Tasks/JavaToolInstallerV0/task.loc.json @@ -134,6 +134,14 @@ "defaultValue": true, "visibleRule": "jdkSourceOption != PreInstalled", "helpMarkDown": "ms-resource:loc.input.help.createExtractDirectory" + }, + { + "name": "createMavenToolchains", + "type": "boolean", + "label": "ms-resource:loc.input.label.createMavenToolchains", + "required": false, + "defaultValue": false, + "helpMarkDown": "ms-resource:loc.input.help.createMavenToolchains" } ], "dataSourceBindings": [ diff --git a/Tasks/JavaToolInstallerV0/toolchains.ts b/Tasks/JavaToolInstallerV0/toolchains.ts new file mode 100644 index 000000000000..6cb7e5340c1c --- /dev/null +++ b/Tasks/JavaToolInstallerV0/toolchains.ts @@ -0,0 +1,121 @@ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import taskLib = require('azure-pipelines-task-lib/task'); +import toolLib = require('azure-pipelines-tool-lib/tool'); +import { create as xmlCreate } from 'xmlbuilder2'; + +const M2_DIR = '.m2'; +const MVN_TOOLCHAINS_FILE: string = 'toolchains.xml'; + +export async function configureToolchains(version: string, vendor: string, jdkHome: string) { + const id = `${vendor}_${version}`; + const settingsDirectory = path.join(os.homedir(), M2_DIR); + + await createToolchainsSettings( + version, + vendor, + id, + jdkHome, + settingsDirectory, + ); +} + +async function createToolchainsSettings( + version: string, + vendor: string, + id: string, + jdkHome: string, + settingsDirectory: string, +) { + toolLib.debug(`Creating ${MVN_TOOLCHAINS_FILE} for JDK version ${version} from ${vendor}`); + await taskLib.mkdirP(settingsDirectory); + const originalToolchains = await readExisting(settingsDirectory); + const updatedToolchains = generate(originalToolchains, version, vendor, id, jdkHome); + await write(settingsDirectory, updatedToolchains, true); +} + +function generate( + original: string, + version: string, + vendor: string, + id: string, + jdkHome: string +) { + let xmlObj; + if (original && original.length > 0) { + xmlObj = xmlCreate(original) + .root() + .ele({ + 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 readExisting(directory: string) { + const location = path.join(directory, MVN_TOOLCHAINS_FILE); + if (fs.existsSync(location)) { + return fs.readFileSync(location, { + encoding: 'utf-8', + flag: 'r' + }); + } + return ''; +} + +async function write(directory: string, settings: string, overwriteSettings: boolean) { + const location = path.join(directory, MVN_TOOLCHAINS_FILE); + const settingsExists = fs.existsSync(location); + if (settingsExists && overwriteSettings) { + toolLib.debug(`Overwriting existing file ${location}`); + } else if (!settingsExists) { + toolLib.debug(`Writing to ${location}`); + } else { + toolLib.debug( + `Skipping generation ${location} because file already exists and overwriting is not required` + ); + return; + } + + return fs.writeFileSync(location, settings, { + encoding: 'utf-8', + flag: 'w' + }); +} \ No newline at end of file