From db2f350d2b7dc85aeb6ac71cf5111d85cd172fd7 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 8 Dec 2021 10:50:14 -0800 Subject: [PATCH] Add microsoft distribution of the JDK. (#252) * Add microsoft distribution of the JDK. * Fix formatting to match prettier. * Rebuild js. * Fix archive suffix for Windows. * Update src/distributions/microsoft/installer.ts Co-authored-by: Brian Cristante <33549821+brcrista@users.noreply.github.com> * Update src/distributions/microsoft/installer.ts Co-authored-by: Brian Cristante <33549821+brcrista@users.noreply.github.com> * Add support for the microsoft distribution. * revert lockfile changes * npm run format * fix e2e-versions.yml * eliminate duplication in version numbers * Fix test Co-authored-by: Brendan Burns Co-authored-by: Brian Cristante <33549821+brcrista@users.noreply.github.com> --- .github/workflows/e2e-versions.yml | 18 ++- README.md | 1 + .../distributors/microsoft-installer.test.ts | 88 ++++++++++++ dist/setup/index.js | 135 +++++++++++++++++- src/distributions/distribution-factory.ts | 6 +- src/distributions/microsoft/installer.ts | 115 +++++++++++++++ src/distributions/microsoft/models.ts | 12 ++ 7 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 __tests__/distributors/microsoft-installer.test.ts create mode 100644 src/distributions/microsoft/installer.ts create mode 100644 src/distributions/microsoft/models.ts diff --git a/.github/workflows/e2e-versions.yml b/.github/workflows/e2e-versions.yml index 0eabb14c..97855308 100644 --- a/.github/workflows/e2e-versions.yml +++ b/.github/workflows/e2e-versions.yml @@ -20,8 +20,11 @@ jobs: fail-fast: false matrix: os: [macos-latest, windows-latest, ubuntu-latest] - distribution: ['temurin', 'adopt', 'adopt-openj9', 'zulu', 'liberica'] # internally 'adopt-hotspot' is the same as 'adopt' + distribution: ['temurin', 'adopt', 'adopt-openj9', 'zulu', 'liberica', 'microsoft' ] # internally 'adopt-hotspot' is the same as 'adopt' version: ['8', '11', '16'] + exclude: + - distribution: microsoft + version: 8 steps: - name: Checkout uses: actions/checkout@v2 @@ -174,16 +177,17 @@ jobs: run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}" shell: bash - setup-java-custom-architecture: - name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-x86) - ${{ matrix.os }} + # Only Liberica and Zulu provide x86 + setup-java-x86: + name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-${{ matrix.architecture }}) - ${{ matrix.os }} needs: setup-java-major-minor-versions runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - # Only Zulu and Liberica provides x86 arch for now and only for windows / ubuntu + # x86 is not supported on macOS os: [windows-latest, ubuntu-latest] - distribution: ['zulu', 'liberica'] + distribution: ['liberica', 'zulu'] version: ['11'] steps: - name: Checkout @@ -194,7 +198,9 @@ jobs: with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.version }} - architecture: x86 + architecture: 'x86' - name: Verify Java run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}" shell: bash + + # Only Microsoft provides AArch64. However, GitHub-hosted runners do not support this architecture. \ No newline at end of file diff --git a/README.md b/README.md index 0ee64fd0..f3cea5d2 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Currently, the following distributions are supported: | `adopt` or `adopt-hotspot` | Adopt OpenJDK Hotspot | [Link](https://adoptopenjdk.net/) | [Link](https://adoptopenjdk.net/about.html) | | `adopt-openj9` | Adopt OpenJDK OpenJ9 | [Link](https://adoptopenjdk.net/) | [Link](https://adoptopenjdk.net/about.html) | | `liberica` | Liberica JDK | [Link](https://bell-sw.com/) | [Link](https://bell-sw.com/liberica_eula/) | +| `microsoft` | Microsoft Build of OpenJDK | [Link](https://www.microsoft.com/openjdk) | [Link](https://docs.microsoft.com/java/openjdk/faq) **NOTE:** The different distributors can provide discrepant list of available versions / supported configurations. Please refer to the official documentation to see the list of supported versions. diff --git a/__tests__/distributors/microsoft-installer.test.ts b/__tests__/distributors/microsoft-installer.test.ts new file mode 100644 index 00000000..eb77f0e2 --- /dev/null +++ b/__tests__/distributors/microsoft-installer.test.ts @@ -0,0 +1,88 @@ +import { MicrosoftDistributions } from '../../src/distributions/microsoft/installer'; + +describe('findPackageForDownload', () => { + let distribution: MicrosoftDistributions; + + beforeEach(() => { + distribution = new MicrosoftDistributions({ + version: '', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); + }); + + it.each([ + [ + '17.x', + '17.0.1', + 'https://aka.ms/download-jdk/microsoft-jdk-17.0.1.12.1-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}' + ], + [ + '16.0.x', + '16.0.2', + 'https://aka.ms/download-jdk/microsoft-jdk-16.0.2.7.1-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}' + ], + [ + '11.0.13', + '11.0.13', + 'https://aka.ms/download-jdk/microsoft-jdk-11.0.13.8.1-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}' + ] + ])('version is %s -> %s', async (input, expectedVersion, expectedUrl) => { + const result = await distribution['findPackageForDownload'](input); + expect(result.version).toBe(expectedVersion); + let os: string; + let archive: string; + switch (process.platform) { + case 'darwin': + os = 'macos'; + archive = 'tar.gz'; + break; + case 'win32': + os = 'windows'; + archive = 'zip'; + break; + default: + os = process.platform.toString(); + archive = 'tar.gz'; + break; + } + const url = expectedUrl.replace('{{OS_TYPE}}', os).replace('{{ARCHIVE_TYPE}}', archive); + expect(result.url).toBe(url); + }); + + it('should throw an error', async () => { + await expect(distribution['findPackageForDownload']('8')).rejects.toThrow( + /Could not find satisfied version for SemVer */ + ); + }); +}); + +describe('getPlatformOption', () => { + const distributions = new MicrosoftDistributions({ + architecture: 'x64', + version: '11', + packageType: 'jdk', + checkLatest: false + }); + + it.each([ + ['linux', 'tar.gz', 'linux'], + ['darwin', 'tar.gz', 'macos'], + ['win32', 'zip', 'windows'] + ])('os version %s -> %s', (input, expectedArchive, expectedOs) => { + const actual = distributions['getPlatformOption'](input as NodeJS.Platform); + + expect(actual.archive).toEqual(expectedArchive); + expect(actual.os).toEqual(expectedOs); + }); + + it.each(['aix', 'android', 'freebsd', 'openbsd', 'netbsd', 'solaris', 'cygwin'])( + 'not support os version %s', + input => { + expect(() => distributions['getPlatformOption'](input as NodeJS.Platform)).toThrow( + /Platform '\w+' is not supported\. Supported platforms: .+/ + ); + } + ); +}); diff --git a/dist/setup/index.js b/dist/setup/index.js index 19fd5018..855880b7 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -13819,7 +13819,136 @@ exports.XMLCBWriter = XMLCBWriter; /* 193 */, /* 194 */, /* 195 */, -/* 196 */, +/* 196 */ +/***/ (function(__unusedmodule, exports, __webpack_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()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MicrosoftDistributions = void 0; +const base_installer_1 = __webpack_require__(83); +const semver_1 = __importDefault(__webpack_require__(876)); +const util_1 = __webpack_require__(322); +const core = __importStar(__webpack_require__(470)); +const tc = __importStar(__webpack_require__(139)); +const fs_1 = __importDefault(__webpack_require__(747)); +const path_1 = __importDefault(__webpack_require__(622)); +class MicrosoftDistributions extends base_installer_1.JavaBase { + constructor(installerOptions) { + super('Microsoft', installerOptions); + } + downloadTool(javaRelease) { + return __awaiter(this, void 0, void 0, function* () { + core.info(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); + const javaArchivePath = yield tc.downloadTool(javaRelease.url); + core.info(`Extracting Java archive...`); + const extension = util_1.getDownloadArchiveExtension(); + const extractedJavaPath = yield util_1.extractJdkFile(javaArchivePath, extension); + const archiveName = fs_1.default.readdirSync(extractedJavaPath)[0]; + const archivePath = path_1.default.join(extractedJavaPath, archiveName); + const javaPath = yield tc.cacheDir(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture); + return { version: javaRelease.version, path: javaPath }; + }); + } + findPackageForDownload(range) { + return __awaiter(this, void 0, void 0, function* () { + if (this.architecture !== 'x64' && this.architecture !== 'aarch64') { + throw new Error(`Unsupported architecture: ${this.architecture}`); + } + const availableVersionsRaw = yield this.getAvailableVersions(); + const opts = this.getPlatformOption(); + const availableVersions = availableVersionsRaw.map(item => ({ + url: `https://aka.ms/download-jdk/microsoft-jdk-${item.version.join('.')}-${opts.os}-${this.architecture}.${opts.archive}`, + version: this.convertVersionToSemver(item) + })); + const satisfiedVersion = availableVersions + .filter(item => util_1.isVersionSatisfies(range, item.version)) + .sort((a, b) => -semver_1.default.compareBuild(a.version, b.version))[0]; + if (!satisfiedVersion) { + const availableOptions = availableVersions.map(item => item.version).join(', '); + const availableOptionsMessage = availableOptions + ? `\nAvailable versions: ${availableOptions}` + : ''; + throw new Error(`Could not find satisfied version for SemVer ${range}. ${availableOptionsMessage}`); + } + return satisfiedVersion; + }); + } + getAvailableVersions() { + return __awaiter(this, void 0, void 0, function* () { + // TODO get these dynamically! + // We will need Microsoft to add an endpoint where we can query for versions. + const jdkVersions = [ + { + version: [17, 0, 1, 12, 1] + }, + { + version: [16, 0, 2, 7, 1] + } + ]; + // M1 is only supported for Java 16 & 17 + if (process.platform !== 'darwin' || this.architecture !== 'aarch64') { + jdkVersions.push({ + version: [11, 0, 13, 8, 1] + }); + } + return jdkVersions; + }); + } + getPlatformOption(platform = process.platform /* for testing */) { + switch (platform) { + case 'darwin': + return { archive: 'tar.gz', os: 'macos' }; + case 'win32': + return { archive: 'zip', os: 'windows' }; + case 'linux': + return { archive: 'tar.gz', os: 'linux' }; + default: + throw new Error(`Platform '${platform}' is not supported. Supported platforms: 'darwin', 'linux', 'win32'`); + } + } + convertVersionToSemver(version) { + const major = version.version[0]; + const minor = version.version[1]; + const patch = version.version[2]; + return `${major}.${minor}.${patch}`; + } +} +exports.MicrosoftDistributions = MicrosoftDistributions; + + +/***/ }), /* 197 */ /***/ (function(__unusedmodule, exports, __webpack_require__) { @@ -56004,6 +56133,7 @@ const installer_2 = __webpack_require__(834); const installer_3 = __webpack_require__(584); const installer_4 = __webpack_require__(439); const installer_5 = __webpack_require__(507); +const installer_6 = __webpack_require__(196); var JavaDistribution; (function (JavaDistribution) { JavaDistribution["Adopt"] = "adopt"; @@ -56013,6 +56143,7 @@ var JavaDistribution; JavaDistribution["Zulu"] = "zulu"; JavaDistribution["Liberica"] = "liberica"; JavaDistribution["JdkFile"] = "jdkfile"; + JavaDistribution["Microsoft"] = "microsoft"; })(JavaDistribution || (JavaDistribution = {})); function getJavaDistribution(distributionName, installerOptions, jdkFile) { switch (distributionName) { @@ -56029,6 +56160,8 @@ function getJavaDistribution(distributionName, installerOptions, jdkFile) { return new installer_2.ZuluDistribution(installerOptions); case JavaDistribution.Liberica: return new installer_5.LibericaDistributions(installerOptions); + case JavaDistribution.Microsoft: + return new installer_6.MicrosoftDistributions(installerOptions); default: return null; } diff --git a/src/distributions/distribution-factory.ts b/src/distributions/distribution-factory.ts index 2ec2421f..623bbae1 100644 --- a/src/distributions/distribution-factory.ts +++ b/src/distributions/distribution-factory.ts @@ -5,6 +5,7 @@ import { ZuluDistribution } from './zulu/installer'; import { AdoptDistribution, AdoptImplementation } from './adopt/installer'; import { TemurinDistribution, TemurinImplementation } from './temurin/installer'; import { LibericaDistributions } from './liberica/installer'; +import { MicrosoftDistributions } from './microsoft/installer'; enum JavaDistribution { Adopt = 'adopt', @@ -13,7 +14,8 @@ enum JavaDistribution { Temurin = 'temurin', Zulu = 'zulu', Liberica = 'liberica', - JdkFile = 'jdkfile' + JdkFile = 'jdkfile', + Microsoft = 'microsoft' } export function getJavaDistribution( @@ -35,6 +37,8 @@ export function getJavaDistribution( return new ZuluDistribution(installerOptions); case JavaDistribution.Liberica: return new LibericaDistributions(installerOptions); + case JavaDistribution.Microsoft: + return new MicrosoftDistributions(installerOptions); default: return null; } diff --git a/src/distributions/microsoft/installer.ts b/src/distributions/microsoft/installer.ts new file mode 100644 index 00000000..79fffe5e --- /dev/null +++ b/src/distributions/microsoft/installer.ts @@ -0,0 +1,115 @@ +import { JavaBase } from '../base-installer'; +import { JavaDownloadRelease, JavaInstallerOptions, JavaInstallerResults } from '../base-models'; +import semver from 'semver'; +import { extractJdkFile, getDownloadArchiveExtension, isVersionSatisfies } from '../../util'; +import * as core from '@actions/core'; +import { MicrosoftVersion, PlatformOptions } from './models'; +import * as tc from '@actions/tool-cache'; +import fs from 'fs'; +import path from 'path'; + +export class MicrosoftDistributions extends JavaBase { + constructor(installerOptions: JavaInstallerOptions) { + super('Microsoft', installerOptions); + } + + protected async downloadTool(javaRelease: JavaDownloadRelease): Promise { + core.info( + `Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...` + ); + const javaArchivePath = await tc.downloadTool(javaRelease.url); + + core.info(`Extracting Java archive...`); + const extension = getDownloadArchiveExtension(); + const extractedJavaPath = await extractJdkFile(javaArchivePath, extension); + + const archiveName = fs.readdirSync(extractedJavaPath)[0]; + const archivePath = path.join(extractedJavaPath, archiveName); + + const javaPath = await tc.cacheDir( + archivePath, + this.toolcacheFolderName, + this.getToolcacheVersionName(javaRelease.version), + this.architecture + ); + + return { version: javaRelease.version, path: javaPath }; + } + + protected async findPackageForDownload(range: string): Promise { + if (this.architecture !== 'x64' && this.architecture !== 'aarch64') { + throw new Error(`Unsupported architecture: ${this.architecture}`); + } + const availableVersionsRaw = await this.getAvailableVersions(); + + const opts = this.getPlatformOption(); + const availableVersions = availableVersionsRaw.map(item => ({ + url: `https://aka.ms/download-jdk/microsoft-jdk-${item.version.join('.')}-${opts.os}-${ + this.architecture + }.${opts.archive}`, + version: this.convertVersionToSemver(item) + })); + + const satisfiedVersion = availableVersions + .filter(item => isVersionSatisfies(range, item.version)) + .sort((a, b) => -semver.compareBuild(a.version, b.version))[0]; + + if (!satisfiedVersion) { + const availableOptions = availableVersions.map(item => item.version).join(', '); + const availableOptionsMessage = availableOptions + ? `\nAvailable versions: ${availableOptions}` + : ''; + throw new Error( + `Could not find satisfied version for SemVer ${range}. ${availableOptionsMessage}` + ); + } + + return satisfiedVersion; + } + + private async getAvailableVersions(): Promise { + // TODO get these dynamically! + // We will need Microsoft to add an endpoint where we can query for versions. + const jdkVersions = [ + { + version: [17, 0, 1, 12, 1] + }, + { + version: [16, 0, 2, 7, 1] + } + ]; + + // M1 is only supported for Java 16 & 17 + if (process.platform !== 'darwin' || this.architecture !== 'aarch64') { + jdkVersions.push({ + version: [11, 0, 13, 8, 1] + }); + } + + return jdkVersions; + } + + private getPlatformOption( + platform: NodeJS.Platform = process.platform /* for testing */ + ): PlatformOptions { + switch (platform) { + case 'darwin': + return { archive: 'tar.gz', os: 'macos' }; + case 'win32': + return { archive: 'zip', os: 'windows' }; + case 'linux': + return { archive: 'tar.gz', os: 'linux' }; + default: + throw new Error( + `Platform '${platform}' is not supported. Supported platforms: 'darwin', 'linux', 'win32'` + ); + } + } + + private convertVersionToSemver(version: MicrosoftVersion): string { + const major = version.version[0]; + const minor = version.version[1]; + const patch = version.version[2]; + return `${major}.${minor}.${patch}`; + } +} diff --git a/src/distributions/microsoft/models.ts b/src/distributions/microsoft/models.ts new file mode 100644 index 00000000..361c7dba --- /dev/null +++ b/src/distributions/microsoft/models.ts @@ -0,0 +1,12 @@ +type OsVersions = 'linux' | 'macos' | 'windows'; +type ArchiveType = 'tar.gz' | 'zip'; + +export interface PlatformOptions { + archive: ArchiveType; + os: OsVersions; +} + +export interface MicrosoftVersion { + downloadUrl?: string; + version: Array; +}