mirror of
				https://github.com/actions/setup-java.git
				synced 2025-10-31 14:30:53 +00:00 
			
		
		
		
	Implement support for custom vendors in setup-java
This commit is contained in:
		
					parent
					
						
							
								e73e96a93b
							
						
					
				
			
			
				commit
				
					
						b2da088220
					
				
			
		
					 21 changed files with 34331 additions and 21391 deletions
				
			
		
							
								
								
									
										23
									
								
								action.yml
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								action.yml
									
										
									
									
									
								
							|  | @ -4,22 +4,21 @@ description: 'Set up a specific version of the Java JDK and add the | |||
| author: 'GitHub' | ||||
| inputs: | ||||
|   java-version: | ||||
|     description: 'The Java version to make available on the path. Takes a whole | ||||
|        or semver Java version, or 1.x syntax (e.g. 1.8 => Java 8.x). | ||||
|        Early access versions can be specified in the form of e.g. 14-ea, | ||||
|        14.0.0-ea, or 14.0.0-ea.28' | ||||
|     required: true | ||||
|     description: 'The Java version to set up. Takes a whole or semver Java version. See examples of supported syntax in README file' | ||||
|     required: false | ||||
|   distribution: | ||||
|     description: 'Java distribution. See the list of supported distributions in README file' | ||||
|     required: false | ||||
|   java-package: | ||||
|     description: 'The package type (jre, jdk, jdk+fx)' | ||||
|     description: 'The package type (jdk, jre)' | ||||
|     required: false | ||||
|     default: 'jdk' | ||||
|   architecture: | ||||
|     description: 'The architecture (x86, x64) of the package.' | ||||
|     description: 'The architecture of the package' | ||||
|     required: false | ||||
|     default: 'x64' | ||||
|   jdkFile: | ||||
|     description: 'Path to where the compressed JDK is located. The path could | ||||
|        be in your source repository or a local path on the agent.' | ||||
|     description: 'Path to where the compressed JDK is located' | ||||
|     required: false | ||||
|   server-id: | ||||
|     description: 'ID of the distributionManagement repository in the pom.xml | ||||
|  | @ -47,10 +46,12 @@ inputs: | |||
|        $GPG_PASSPHRASE.' | ||||
|     required: false | ||||
| outputs: | ||||
|   path: | ||||
|     description: 'Path to where the java environment has been installed (same as $JAVA_HOME)' | ||||
|   distribution: | ||||
|     description: 'Distribution of Java that has been installed' | ||||
|   version: | ||||
|     description: 'Actual version of the java environment that has been installed' | ||||
|   path: | ||||
|     description: 'Path to where the java environment has been installed (same as $JAVA_HOME)' | ||||
| runs: | ||||
|   using: 'node12' | ||||
|   main: 'dist/setup/index.js' | ||||
|  |  | |||
							
								
								
									
										3647
									
								
								dist/cleanup/index.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3647
									
								
								dist/cleanup/index.js
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										46646
									
								
								dist/setup/index.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										46646
									
								
								dist/setup/index.js
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										4242
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										4242
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										36
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										36
									
								
								package.json
									
										
									
									
									
								
							|  | @ -1,13 +1,13 @@ | |||
| { | ||||
|   "name": "setup-java", | ||||
|   "version": "1.0.0", | ||||
|   "version": "2.0.0", | ||||
|   "private": true, | ||||
|   "description": "setup java action", | ||||
|   "main": "dist/setup/index.js", | ||||
|   "scripts": { | ||||
|     "build": "ncc build -o dist/setup src/setup-java.ts && ncc build -o dist/cleanup src/cleanup-java.ts", | ||||
|     "format": "prettier --write **/*.ts", | ||||
|     "format-check": "prettier --check **/*.ts", | ||||
|     "format": "prettier --write \"{,!(node_modules)/**/}*.ts\"", | ||||
|     "format-check": "prettier --check \"{,!(node_modules)/**/}*.ts\"", | ||||
|     "prerelease": "npm run-script build", | ||||
|     "release": "git add -f dist/setup/index.js dist/cleanup/index.js", | ||||
|     "test": "jest" | ||||
|  | @ -18,30 +18,30 @@ | |||
|   }, | ||||
|   "keywords": [ | ||||
|     "actions", | ||||
|     "node", | ||||
|     "java", | ||||
|     "setup" | ||||
|   ], | ||||
|   "author": "GitHub", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "@actions/core": "^1.0.0", | ||||
|     "@actions/exec": "^1.0.0", | ||||
|     "@actions/http-client": "^1.0.8", | ||||
|     "@actions/io": "^1.0.0", | ||||
|     "@actions/tool-cache": "^1.3.1", | ||||
|     "semver": "^6.1.1", | ||||
|     "xmlbuilder2": "^2.1.2" | ||||
|     "@actions/core": "^1.2.6", | ||||
|     "@actions/exec": "^1.0.4", | ||||
|     "@actions/http-client": "^1.0.9", | ||||
|     "@actions/io": "^1.0.2", | ||||
|     "@actions/tool-cache": "^1.6.1", | ||||
|     "semver": "^7.3.4", | ||||
|     "xmlbuilder2": "^2.4.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/jest": "^24.0.13", | ||||
|     "@types/node": "^12.0.4", | ||||
|     "@types/semver": "^6.0.0", | ||||
|     "@types/jest": "^26.0.20", | ||||
|     "@types/node": "^12.19.13", | ||||
|     "@types/semver": "^7.3.4", | ||||
|     "@zeit/ncc": "^0.20.5", | ||||
|     "jest": "^24.8.0", | ||||
|     "jest-circus": "^24.7.1", | ||||
|     "jest": "^26.6.3", | ||||
|     "jest-circus": "^26.6.3", | ||||
|     "prettier": "^1.19.1", | ||||
|     "ts-jest": "^24.0.2", | ||||
|     "typescript": "^3.5.1" | ||||
|     "ts-jest": "^26.5.3", | ||||
|     "typescript": "^4.2.3" | ||||
|   }, | ||||
|   "husky": { | ||||
|     "skipCI": true, | ||||
|  |  | |||
							
								
								
									
										66
									
								
								src/auth.ts
									
										
									
									
									
								
							
							
						
						
									
										66
									
								
								src/auth.ts
									
										
									
									
									
								
							|  | @ -1,26 +1,52 @@ | |||
| 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 {create as xmlCreate} from 'xmlbuilder2'; | ||||
| 
 | ||||
| import * as fs from 'fs'; | ||||
| import * as os from 'os'; | ||||
| 
 | ||||
| import { create as xmlCreate } from 'xmlbuilder2'; | ||||
| import * as constants from './constants'; | ||||
| import * as gpg from './gpg'; | ||||
| 
 | ||||
| export const M2_DIR = '.m2'; | ||||
| export const SETTINGS_FILE = 'settings.xml'; | ||||
| 
 | ||||
| export async function configAuthentication( | ||||
| 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 gpgPrivateKey = | ||||
|     core.getInput(constants.INPUT_GPG_PRIVATE_KEY) || constants.INPUT_DEFAULT_GPG_PRIVATE_KEY; | ||||
|   const gpgPassphrase = | ||||
|     core.getInput(constants.INPUT_GPG_PASSPHRASE) || | ||||
|     (gpgPrivateKey ? constants.INPUT_DEFAULT_GPG_PASSPHRASE : undefined); | ||||
| 
 | ||||
|   if (gpgPrivateKey) { | ||||
|     core.setSecret(gpgPrivateKey); | ||||
|   } | ||||
| 
 | ||||
|   await createAuthenticationSettings(id, username, password, gpgPassphrase); | ||||
| 
 | ||||
|   if (gpgPrivateKey) { | ||||
|     core.info('Importing private gpg key'); | ||||
|     const keyFingerprint = (await gpg.importKey(gpgPrivateKey)) || ''; | ||||
|     core.saveState(constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT, keyFingerprint); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export async function createAuthenticationSettings( | ||||
|   id: string, | ||||
|   username: string, | ||||
|   password: string, | ||||
|   gpgPassphrase: string | undefined = undefined | ||||
| ) { | ||||
|   console.log( | ||||
|     `creating ${SETTINGS_FILE} with server-id: ${id};`, | ||||
|     'environment variables:', | ||||
|     `username=\$${username},`, | ||||
|     `password=\$${password},`, | ||||
|     `and gpg-passphrase=${gpgPassphrase ? '$' + gpgPassphrase : null}` | ||||
|   core.info( | ||||
|     `Creating ${SETTINGS_FILE} with server-id: ${id};
 | ||||
|      environment variables: | ||||
|      username=\$${username}, | ||||
|      password=\$${password}, | ||||
|      and gpg-passphrase=${gpgPassphrase ? '$' + gpgPassphrase : null}` | ||||
|   ); | ||||
|   // when an alternate m2 location is specified use only that location (no .m2 directory)
 | ||||
|   // otherwise use the home/.m2/ path
 | ||||
|  | @ -29,11 +55,7 @@ export async function configAuthentication( | |||
|     core.getInput(constants.INPUT_SETTINGS_PATH) ? '' : M2_DIR | ||||
|   ); | ||||
|   await io.mkdirP(settingsDirectory); | ||||
|   core.debug(`created directory ${settingsDirectory}`); | ||||
|   await write( | ||||
|     settingsDirectory, | ||||
|     generate(id, username, password, gpgPassphrase) | ||||
|   ); | ||||
|   await write(settingsDirectory, generate(id, username, password, gpgPassphrase)); | ||||
| } | ||||
| 
 | ||||
| // only exported for testing purposes
 | ||||
|  | @ -41,9 +63,9 @@ export function generate( | |||
|   id: string, | ||||
|   username: string, | ||||
|   password: string, | ||||
|   gpgPassphrase: string | undefined = undefined | ||||
|   gpgPassphrase?: string | undefined | ||||
| ) { | ||||
|   const xmlObj: {[key: string]: any} = { | ||||
|   const xmlObj: { [key: string]: any } = { | ||||
|     settings: { | ||||
|       '@xmlns': 'http://maven.apache.org/SETTINGS/1.0.0', | ||||
|       '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', | ||||
|  | @ -69,15 +91,19 @@ export function generate( | |||
|     xmlObj.settings.servers.server.push(gpgServer); | ||||
|   } | ||||
| 
 | ||||
|   return xmlCreate(xmlObj).end({headless: true, prettyPrint: true, width: 80}); | ||||
|   return xmlCreate(xmlObj).end({ | ||||
|     headless: true, | ||||
|     prettyPrint: true, | ||||
|     width: 80 | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| async function write(directory: string, settings: string) { | ||||
|   const location = path.join(directory, SETTINGS_FILE); | ||||
|   if (fs.existsSync(location)) { | ||||
|     console.warn(`overwriting existing file ${location}`); | ||||
|     core.warning(`Overwriting existing file ${location}`); | ||||
|   } else { | ||||
|     console.log(`writing ${location}`); | ||||
|     core.info(`Writing ${location}`); | ||||
|   } | ||||
| 
 | ||||
|   return fs.writeFileSync(location, settings, { | ||||
|  |  | |||
|  | @ -3,15 +3,13 @@ import * as gpg from './gpg'; | |||
| import * as constants from './constants'; | ||||
| 
 | ||||
| async function run() { | ||||
|   if (core.getInput(constants.INPUT_GPG_PRIVATE_KEY, {required: false})) { | ||||
|     core.info('removing private key from keychain'); | ||||
|   if (core.getInput(constants.INPUT_GPG_PRIVATE_KEY, { required: false })) { | ||||
|     core.info('Removing private key from keychain'); | ||||
|     try { | ||||
|       const keyFingerprint = core.getState( | ||||
|         constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT | ||||
|       ); | ||||
|       const keyFingerprint = core.getState(constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT); | ||||
|       await gpg.deleteKey(keyFingerprint); | ||||
|     } catch (error) { | ||||
|       core.setFailed('failed to remove private key'); | ||||
|       core.setFailed('Failed to remove private key'); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,8 @@ | |||
| export const INPUT_VERSION = 'version'; | ||||
| export const macOSJavaContentDir = 'Contents/Home'; | ||||
| export const INPUT_JAVA_VERSION = 'java-version'; | ||||
| export const INPUT_ARCHITECTURE = 'architecture'; | ||||
| export const INPUT_JAVA_PACKAGE = 'java-package'; | ||||
| export const INPUT_DISTRIBUTION = 'distribution'; | ||||
| export const INPUT_JDK_FILE = 'jdkFile'; | ||||
| export const INPUT_SERVER_ID = 'server-id'; | ||||
| export const INPUT_SERVER_USERNAME = 'server-username'; | ||||
|  |  | |||
							
								
								
									
										146
									
								
								src/distributions/adoptium/installer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/distributions/adoptium/installer.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,146 @@ | |||
| import * as core from '@actions/core'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| 
 | ||||
| import fs from 'fs'; | ||||
| import path from 'path'; | ||||
| import semver from 'semver'; | ||||
| 
 | ||||
| import { JavaBase } from '../base-installer'; | ||||
| import { IAdoptiumAvailableVersions } from './models'; | ||||
| import { JavaInstallerOptions, JavaDownloadRelease, JavaInstallerResults } from '../base-models'; | ||||
| import { macOSJavaContentDir } from '../../constants'; | ||||
| import { extractJdkFile, getDownloadArchiveExtension } from '../../util'; | ||||
| 
 | ||||
| export class AdoptiumDistribution extends JavaBase { | ||||
|   constructor(installerOptions: JavaInstallerOptions) { | ||||
|     super('Adoptium', installerOptions); | ||||
|   } | ||||
| 
 | ||||
|   protected async findPackageForDownload(version: semver.Range): Promise<JavaDownloadRelease> { | ||||
|     const availableVersionsRaw = await this.getAvailableVersions(); | ||||
|     const availableVersionsWithBinaries = availableVersionsRaw | ||||
|       .filter(item => item.binaries.length > 0) | ||||
|       .map(item => { | ||||
|         return { | ||||
|           version: item.version_data.semver, | ||||
|           url: item.binaries[0].package.link | ||||
|         } as JavaDownloadRelease; | ||||
|       }); | ||||
| 
 | ||||
|     const satisfiedVersions = availableVersionsWithBinaries | ||||
|       .filter(item => semver.satisfies(item.version, version)) | ||||
|       .sort((a, b) => { | ||||
|         return -semver.compareBuild(a.version, b.version); | ||||
|       }); | ||||
| 
 | ||||
|     const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null; | ||||
|     if (!resolvedFullVersion) { | ||||
|       const availableOptions = availableVersionsWithBinaries.map(item => item.version).join(', '); | ||||
|       const availableOptionsMessage = availableOptions | ||||
|         ? `\nAvailable versions: ${availableOptions}` | ||||
|         : ''; | ||||
|       throw new Error( | ||||
|         `Could not find satisfied version for SemVer '${version.raw}'. ${availableOptionsMessage}` | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return resolvedFullVersion; | ||||
|   } | ||||
| 
 | ||||
|   protected async downloadTool(javaRelease: JavaDownloadRelease): Promise<JavaInstallerResults> { | ||||
|     let javaPath: string; | ||||
|     let extractedJavaPath: string; | ||||
| 
 | ||||
|     core.info( | ||||
|       `Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...` | ||||
|     ); | ||||
|     const javaArchivePath = await tc.downloadTool(javaRelease.url); | ||||
| 
 | ||||
|     core.info(`Extracting Java archive...`); | ||||
|     let extension = getDownloadArchiveExtension(); | ||||
| 
 | ||||
|     extractedJavaPath = await extractJdkFile(javaArchivePath, extension); | ||||
| 
 | ||||
|     const archiveName = fs.readdirSync(extractedJavaPath)[0]; | ||||
|     const archivePath = path.join(extractedJavaPath, archiveName); | ||||
|     const version = this.getToolcacheVersionName(javaRelease.version); | ||||
| 
 | ||||
|     javaPath = await tc.cacheDir(archivePath, this.toolcacheFolderName, version, this.architecture); | ||||
| 
 | ||||
|     if (process.platform === 'darwin') { | ||||
|       javaPath = path.join(javaPath, macOSJavaContentDir); | ||||
|     } | ||||
| 
 | ||||
|     return { version: javaRelease.version, path: javaPath }; | ||||
|   } | ||||
| 
 | ||||
|   private async getAvailableVersions(): Promise<IAdoptiumAvailableVersions[]> { | ||||
|     const platform = this.getPlatformOption(); | ||||
|     const arch = this.architecture; | ||||
|     const imageType = this.packageType; | ||||
|     const versionRange = '[1.0,100.0]'; // retrieve all available versions
 | ||||
|     const encodedVersionRange = encodeURI(versionRange); | ||||
|     const releaseType = this.stable ? 'ga' : 'ea'; | ||||
| 
 | ||||
|     console.time('adopt-retrieve-available-versions'); | ||||
| 
 | ||||
|     const baseRequestArguments = [ | ||||
|       `project=jdk`, | ||||
|       'vendor=adoptopenjdk', | ||||
|       `heap_size=normal`, | ||||
|       `jvm_impl=hotspot`, | ||||
|       'sort_method=DEFAULT', | ||||
|       'sort_order=DESC', | ||||
|       `os=${platform}`, | ||||
|       `architecture=${arch}`, | ||||
|       `image_type=${imageType}`, | ||||
|       `release_type=${releaseType}` | ||||
|     ].join('&'); | ||||
| 
 | ||||
|     // need to iterate through all pages to retrieve the list of all versions
 | ||||
|     // Adopt API doesn't provide way to retrieve the count of pages to iterate so infinity loop
 | ||||
|     let page_index = 0; | ||||
|     const availableVersions: IAdoptiumAvailableVersions[] = []; | ||||
|     while (true) { | ||||
|       const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`; | ||||
|       const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${encodedVersionRange}?${requestArguments}`; | ||||
|       if (core.isDebug() && page_index === 0) { | ||||
|         // url is identical except page_index so print it once for debug
 | ||||
|         core.debug(`Gathering available versions from '${availableVersionsUrl}'`); | ||||
|       } | ||||
| 
 | ||||
|       const paginationPage = ( | ||||
|         await this.http.getJson<IAdoptiumAvailableVersions[]>(availableVersionsUrl) | ||||
|       ).result; | ||||
|       if (paginationPage === null || paginationPage.length === 0) { | ||||
|         // break infinity loop because we have reached end of pagination
 | ||||
|         break; | ||||
|       } | ||||
| 
 | ||||
|       availableVersions.push(...paginationPage); | ||||
|       page_index++; | ||||
|     } | ||||
| 
 | ||||
|     if (core.isDebug()) { | ||||
|       core.startGroup('Print information about available versions'); | ||||
|       console.timeEnd('adopt-retrieve-available-versions'); | ||||
|       console.log(`Available versions: [${availableVersions.length}]`); | ||||
|       console.log(availableVersions.map(item => item.version_data.semver).join(', ')); | ||||
|       core.endGroup(); | ||||
|     } | ||||
| 
 | ||||
|     return availableVersions; | ||||
|   } | ||||
| 
 | ||||
|   private getPlatformOption(): string { | ||||
|     // Adopt has own platform names so need to map them
 | ||||
|     switch (process.platform) { | ||||
|       case 'darwin': | ||||
|         return 'mac'; | ||||
|       case 'win32': | ||||
|         return 'windows'; | ||||
|       default: | ||||
|         return process.platform; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/distributions/adoptium/models.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/distributions/adoptium/models.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| export interface IAdoptiumAvailableVersions { | ||||
|   binaries: [ | ||||
|     { | ||||
|       architecture: string; | ||||
|       heap_size: string; | ||||
|       image_type: string; | ||||
|       jvm_impl: string; | ||||
|       os: string; | ||||
|       package: { | ||||
|         checksum: string; | ||||
|         checksum_link: string; | ||||
|         download_count: number; | ||||
|         link: string; | ||||
|         metadata_link: string; | ||||
|         name: string; | ||||
|         size: string; | ||||
|       }; | ||||
|       project: string; | ||||
|       scm_ref: string; | ||||
|       updated_at: string; | ||||
|     } | ||||
|   ]; | ||||
|   id: string; | ||||
|   release_link: string; | ||||
|   release_name: string; | ||||
|   release_type: string; | ||||
|   vendor: string; | ||||
|   version_data: { | ||||
|     build: number; | ||||
|     major: number; | ||||
|     minor: number; | ||||
|     openjdk_version: string; | ||||
|     security: string; | ||||
|     semver: string; | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										115
									
								
								src/distributions/base-installer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/distributions/base-installer.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | |||
| import * as tc from '@actions/tool-cache'; | ||||
| import * as core from '@actions/core'; | ||||
| import semver from 'semver'; | ||||
| import path from 'path'; | ||||
| import * as httpm from '@actions/http-client'; | ||||
| import { getVersionFromToolcachePath } from '../util'; | ||||
| import { JavaDownloadRelease, JavaInstallerOptions, JavaInstallerResults } from './base-models'; | ||||
| 
 | ||||
| export abstract class JavaBase { | ||||
|   protected http: httpm.HttpClient; | ||||
|   protected version: semver.Range; | ||||
|   protected architecture: string; | ||||
|   protected packageType: string; | ||||
|   protected stable: boolean; | ||||
| 
 | ||||
|   constructor(protected distribution: string, installerOptions: JavaInstallerOptions) { | ||||
|     this.http = new httpm.HttpClient('setup-java', undefined, { | ||||
|       allowRetries: true, | ||||
|       maxRetries: 3 | ||||
|     }); | ||||
| 
 | ||||
|     ({ version: this.version, stable: this.stable } = this.normalizeVersion( | ||||
|       installerOptions.version | ||||
|     )); | ||||
|     this.architecture = installerOptions.arch; | ||||
|     this.packageType = installerOptions.packageType; | ||||
|   } | ||||
| 
 | ||||
|   protected abstract downloadTool(javaRelease: JavaDownloadRelease): Promise<JavaInstallerResults>; | ||||
|   protected abstract findPackageForDownload(range: semver.Range): Promise<JavaDownloadRelease>; | ||||
| 
 | ||||
|   public async setupJava(): Promise<JavaInstallerResults> { | ||||
|     let foundJava = this.findInToolcache(); | ||||
|     if (foundJava) { | ||||
|       core.info(`Resolved Java ${foundJava.version} from tool-cache`); | ||||
|     } else { | ||||
|       core.info(`Java ${this.version.raw} is not found in tool-cache. Trying to download...`); | ||||
|       const javaRelease = await this.findPackageForDownload(this.version); | ||||
|       foundJava = await this.downloadTool(javaRelease); | ||||
|       core.info(`Java ${foundJava.version} was downloaded`); | ||||
|     } | ||||
| 
 | ||||
|     core.info(`Setting Java ${foundJava.version} as default`); | ||||
|     this.setJavaDefault(foundJava.version, foundJava.path); | ||||
| 
 | ||||
|     return foundJava; | ||||
|   } | ||||
| 
 | ||||
|   protected get toolcacheFolderName(): string { | ||||
|     return `Java_${this.distribution}_${this.packageType}`; | ||||
|   } | ||||
| 
 | ||||
|   protected getToolcacheVersionName(resolvedVersion: string): string { | ||||
|     let version = resolvedVersion; | ||||
|     if (!this.stable) { | ||||
|       const cleanVersion = semver.clean(version); | ||||
|       return `${cleanVersion}-ea`; | ||||
|     } | ||||
|     return version; | ||||
|   } | ||||
| 
 | ||||
|   protected findInToolcache(): JavaInstallerResults | null { | ||||
|     // we can't use tc.find directly because firstly, we need to filter versions by stability
 | ||||
|     // if *-ea is provided, take only ea versions from toolcache, otherwise - only stable versions
 | ||||
|     const availableVersions = tc | ||||
|       .findAllVersions(this.toolcacheFolderName, this.architecture) | ||||
|       .filter(item => item.endsWith('-ea') === !this.stable); | ||||
| 
 | ||||
|     const satisfiedVersions = availableVersions | ||||
|       .filter(item => semver.satisfies(item.replace(/-ea$/, ''), this.version)) | ||||
|       .sort(semver.rcompare); | ||||
|     if (!satisfiedVersions || satisfiedVersions.length === 0) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const javaPath = tc.find(this.toolcacheFolderName, satisfiedVersions[0], this.architecture); | ||||
|     if (!javaPath) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       version: getVersionFromToolcachePath(javaPath), | ||||
|       path: javaPath | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   protected setJavaDefault(version: string, toolPath: string) { | ||||
|     core.exportVariable('JAVA_HOME', toolPath); | ||||
|     core.addPath(path.join(toolPath, 'bin')); | ||||
|     core.setOutput('distribution', this.distribution); | ||||
|     core.setOutput('path', toolPath); | ||||
|     core.setOutput('version', version); | ||||
|   } | ||||
| 
 | ||||
|   // this function validates and parse java version to its normal semver notation
 | ||||
|   protected normalizeVersion(version: string) { | ||||
|     let stable = true; | ||||
| 
 | ||||
|     if (version.endsWith('-ea')) { | ||||
|       version = version.replace('-ea', ''); | ||||
|       stable = false; | ||||
|     } | ||||
| 
 | ||||
|     if (!semver.validRange(version)) { | ||||
|       throw new Error( | ||||
|         `The string '${version}' is not valid SemVer notation for Java version. Please check README file for code snippets and more detailed information` | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       version: new semver.Range(version), | ||||
|       stable | ||||
|     }; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/distributions/base-models.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/distributions/base-models.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| export interface JavaInstallerOptions { | ||||
|   version: string; | ||||
|   arch: string; | ||||
|   packageType: string; | ||||
| } | ||||
| 
 | ||||
| export interface JavaInstallerResults { | ||||
|   version: string; | ||||
|   path: string; | ||||
| } | ||||
| 
 | ||||
| export interface JavaDownloadRelease { | ||||
|   version: string; | ||||
|   url: string; | ||||
| } | ||||
							
								
								
									
										28
									
								
								src/distributions/distribution-factory.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/distributions/distribution-factory.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| import { AdoptiumDistribution } from './adoptium/installer'; | ||||
| import { JavaBase } from './base-installer'; | ||||
| import { JavaInstallerOptions } from './base-models'; | ||||
| import { LocalDistribution } from './local/installer'; | ||||
| import { ZuluDistribution } from './zulu/installer'; | ||||
| 
 | ||||
| enum JavaDistribution { | ||||
|   Adoptium = 'adoptium', | ||||
|   Zulu = 'zulu', | ||||
|   JdkFile = 'jdkfile' | ||||
| } | ||||
| 
 | ||||
| export function getJavaDistribution( | ||||
|   distributionName: string, | ||||
|   installerOptions: JavaInstallerOptions, | ||||
|   jdkFile?: string | ||||
| ): JavaBase | null { | ||||
|   switch (distributionName) { | ||||
|     case JavaDistribution.JdkFile: | ||||
|       return new LocalDistribution(installerOptions, jdkFile); | ||||
|     case JavaDistribution.Adoptium: | ||||
|       return new AdoptiumDistribution(installerOptions); | ||||
|     case JavaDistribution.Zulu: | ||||
|       return new ZuluDistribution(installerOptions); | ||||
|     default: | ||||
|       return null; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										77
									
								
								src/distributions/local/installer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/distributions/local/installer.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,77 @@ | |||
| import * as tc from '@actions/tool-cache'; | ||||
| import * as core from '@actions/core'; | ||||
| 
 | ||||
| import fs from 'fs'; | ||||
| import path from 'path'; | ||||
| import semver from 'semver'; | ||||
| 
 | ||||
| import { JavaBase } from '../base-installer'; | ||||
| import { JavaInstallerOptions, JavaDownloadRelease, JavaInstallerResults } from '../base-models'; | ||||
| import { extractJdkFile } from '../../util'; | ||||
| import { macOSJavaContentDir } from '../../constants'; | ||||
| 
 | ||||
| export class LocalDistribution extends JavaBase { | ||||
|   constructor(installerOptions: JavaInstallerOptions, private jdkFile?: string) { | ||||
|     super('jdkfile', installerOptions); | ||||
|   } | ||||
| 
 | ||||
|   public async setupJava(): Promise<JavaInstallerResults> { | ||||
|     let foundJava = this.findInToolcache(); | ||||
| 
 | ||||
|     if (foundJava) { | ||||
|       core.info(`Resolved Java ${foundJava.version} from tool-cache`); | ||||
|     } else { | ||||
|       core.info( | ||||
|         `Java ${this.version.raw} is not found in tool-cache. Trying to unpack JDK file...` | ||||
|       ); | ||||
|       if (!this.jdkFile) { | ||||
|         throw new Error("'jdkFile' is not specified"); | ||||
|       } | ||||
|       const jdkFilePath = path.resolve(this.jdkFile); | ||||
|       const stats = fs.statSync(jdkFilePath); | ||||
| 
 | ||||
|       if (!stats.isFile()) { | ||||
|         throw new Error(`JDK file is not found in path '${jdkFilePath}'`); | ||||
|       } | ||||
| 
 | ||||
|       core.info(`Extracting Java from '${jdkFilePath}'`); | ||||
| 
 | ||||
|       const extractedJavaPath = await extractJdkFile(jdkFilePath); | ||||
|       const archiveName = fs.readdirSync(extractedJavaPath)[0]; | ||||
|       const archivePath = path.join(extractedJavaPath, archiveName); | ||||
|       const javaVersion = this.version.raw; | ||||
| 
 | ||||
|       let javaPath = await tc.cacheDir( | ||||
|         archivePath, | ||||
|         this.toolcacheFolderName, | ||||
|         this.getToolcacheVersionName(javaVersion), | ||||
|         this.architecture | ||||
|       ); | ||||
| 
 | ||||
|       if ( | ||||
|         process.platform === 'darwin' && | ||||
|         fs.existsSync(path.join(javaPath, macOSJavaContentDir)) | ||||
|       ) { | ||||
|         javaPath = path.join(javaPath, macOSJavaContentDir); | ||||
|       } | ||||
| 
 | ||||
|       foundJava = { | ||||
|         version: javaVersion, | ||||
|         path: javaPath | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
|     core.info(`Setting Java ${foundJava.version} as default`); | ||||
| 
 | ||||
|     this.setJavaDefault(foundJava.version, foundJava.path); | ||||
|     return foundJava; | ||||
|   } | ||||
| 
 | ||||
|   protected async findPackageForDownload(version: semver.Range): Promise<JavaDownloadRelease> { | ||||
|     throw new Error('Should not be implemented'); | ||||
|   } | ||||
| 
 | ||||
|   protected async downloadTool(javaRelease: JavaDownloadRelease): Promise<JavaInstallerResults> { | ||||
|     throw new Error('Should not be implemented'); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										163
									
								
								src/distributions/zulu/installer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/distributions/zulu/installer.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,163 @@ | |||
| import * as core from '@actions/core'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| 
 | ||||
| import path from 'path'; | ||||
| import fs from 'fs'; | ||||
| import semver from 'semver'; | ||||
| 
 | ||||
| import { JavaBase } from '../base-installer'; | ||||
| import { IZuluVersions } from './models'; | ||||
| import { extractJdkFile, getDownloadArchiveExtension } from '../../util'; | ||||
| import { JavaDownloadRelease, JavaInstallerOptions, JavaInstallerResults } from '../base-models'; | ||||
| 
 | ||||
| export class ZuluDistribution extends JavaBase { | ||||
|   constructor(installerOptions: JavaInstallerOptions) { | ||||
|     super('Zulu', installerOptions); | ||||
|   } | ||||
| 
 | ||||
|   protected async findPackageForDownload(version: semver.Range): Promise<JavaDownloadRelease> { | ||||
|     const availableVersionsRaw = await this.getAvailableVersions(); | ||||
|     const availableVersions = availableVersionsRaw.map(item => { | ||||
|       return { | ||||
|         version: this.convertVersionToSemver(item.jdk_version), | ||||
|         url: item.url, | ||||
|         zuluVersion: this.convertVersionToSemver(item.zulu_version) | ||||
|       }; | ||||
|     }); | ||||
| 
 | ||||
|     const satisfiedVersions = availableVersions | ||||
|       .filter(item => semver.satisfies(item.version, version)) | ||||
|       .sort((a, b) => { | ||||
|         // Azul provides two versions: jdk_version and azul_version
 | ||||
|         // we should sort by both fields by descending
 | ||||
|         return ( | ||||
|           -semver.compareBuild(a.version, b.version) || | ||||
|           -semver.compareBuild(a.zuluVersion, b.zuluVersion) | ||||
|         ); | ||||
|       }) | ||||
|       .map(item => { | ||||
|         return { | ||||
|           version: item.version, | ||||
|           url: item.url | ||||
|         } as JavaDownloadRelease; | ||||
|       }); | ||||
| 
 | ||||
|     const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null; | ||||
|     if (!resolvedFullVersion) { | ||||
|       const availableOptions = availableVersions.map(item => item.version).join(', '); | ||||
|       const availableOptionsMessage = availableOptions | ||||
|         ? `\nAvailable versions: ${availableOptions}` | ||||
|         : ''; | ||||
|       throw new Error( | ||||
|         `Could not find satisfied version for semver ${version.raw}. ${availableOptionsMessage}` | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return resolvedFullVersion; | ||||
|   } | ||||
| 
 | ||||
|   protected async downloadTool(javaRelease: JavaDownloadRelease): Promise<JavaInstallerResults> { | ||||
|     let extractedJavaPath: string; | ||||
| 
 | ||||
|     core.info( | ||||
|       `Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...` | ||||
|     ); | ||||
|     const javaArchivePath = await tc.downloadTool(javaRelease.url); | ||||
| 
 | ||||
|     core.info(`Extracting Java archive...`); | ||||
|     let extension = getDownloadArchiveExtension(); | ||||
| 
 | ||||
|     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 }; | ||||
|   } | ||||
| 
 | ||||
|   private async getAvailableVersions(): Promise<IZuluVersions[]> { | ||||
|     const { arch, hw_bitness, abi } = this.getArchitectureOptions(); | ||||
|     const [bundleType, features] = this.packageType.split('+'); | ||||
|     const platform = this.getPlatformOption(); | ||||
|     const extension = getDownloadArchiveExtension(); | ||||
|     const javafx = features?.includes('fx') ?? false; | ||||
|     const releaseStatus = this.stable ? 'ga' : 'ea'; | ||||
| 
 | ||||
|     console.time('azul-retrieve-available-versions'); | ||||
|     const requestArguments = [ | ||||
|       `os=${platform}`, | ||||
|       `ext=${extension}`, | ||||
|       `bundle_type=${bundleType}`, | ||||
|       `javafx=${javafx}`, | ||||
|       `arch=${arch}`, | ||||
|       `hw_bitness=${hw_bitness}`, | ||||
|       `release_status=${releaseStatus}`, | ||||
|       abi ? `abi=${abi}` : null, | ||||
|       features ? `features=${features}` : null | ||||
|     ] | ||||
|       .filter(Boolean) | ||||
|       .join('&'); | ||||
| 
 | ||||
|     const availableVersionsUrl = `https://api.azul.com/zulu/download/community/v1.0/bundles/?${requestArguments}`; | ||||
|     if (core.isDebug()) { | ||||
|       core.debug(`Gathering available versions from '${availableVersionsUrl}'`); | ||||
|     } | ||||
| 
 | ||||
|     const availableVersions = | ||||
|       (await this.http.getJson<Array<IZuluVersions>>(availableVersionsUrl)).result ?? []; | ||||
| 
 | ||||
|     if (core.isDebug()) { | ||||
|       core.startGroup('Print information about available versions'); | ||||
|       console.timeEnd('azul-retrieve-available-versions'); | ||||
|       console.log(`Available versions: [${availableVersions.length}]`); | ||||
|       console.log(availableVersions.map(item => item.jdk_version.join('.')).join(', ')); | ||||
|       core.endGroup(); | ||||
|     } | ||||
| 
 | ||||
|     return availableVersions; | ||||
|   } | ||||
| 
 | ||||
|   private getArchitectureOptions(): { | ||||
|     arch: string; | ||||
|     hw_bitness: string; | ||||
|     abi: string; | ||||
|   } { | ||||
|     if (this.architecture == 'x64') { | ||||
|       return { arch: 'x86', hw_bitness: '64', abi: '' }; | ||||
|     } else if (this.architecture == 'x86') { | ||||
|       return { arch: 'x86', hw_bitness: '32', abi: '' }; | ||||
|     } else { | ||||
|       return { arch: this.architecture, hw_bitness: '', abi: '' }; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private getPlatformOption(): string { | ||||
|     // Azul has own platform names so need to map them
 | ||||
|     switch (process.platform) { | ||||
|       case 'darwin': | ||||
|         return 'macos'; | ||||
|       case 'win32': | ||||
|         return 'windows'; | ||||
|       default: | ||||
|         return process.platform; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Azul API returns jdk_version as array of digits like [11, 0, 2, 1]
 | ||||
|   private convertVersionToSemver(version_array: number[]) { | ||||
|     const mainVersion = version_array.slice(0, 3).join('.'); | ||||
|     if (version_array.length > 3) { | ||||
|       // intentionally ignore more than 4 numbers because it is invalid semver
 | ||||
|       return `${mainVersion}+${version_array[3]}`; | ||||
|     } | ||||
| 
 | ||||
|     return mainVersion; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/distributions/zulu/models.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/distributions/zulu/models.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| export interface IZuluVersions { | ||||
|   id: number; | ||||
|   name: string; | ||||
|   url: string; | ||||
|   jdk_version: Array<number>; | ||||
|   zulu_version: Array<number>; | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/gpg.ts
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								src/gpg.ts
									
										
									
									
									
								
							|  | @ -3,7 +3,7 @@ import * as path from 'path'; | |||
| import * as io from '@actions/io'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import * as util from './util'; | ||||
| import {ExecOptions} from '@actions/exec/lib/interfaces'; | ||||
| import { ExecOptions } from '@actions/exec/lib/interfaces'; | ||||
| 
 | ||||
| export const PRIVATE_KEY_FILE = path.join(util.getTempDir(), 'private-key.asc'); | ||||
| 
 | ||||
|  | @ -28,13 +28,7 @@ export async function importKey(privateKey: string) { | |||
| 
 | ||||
|   await exec.exec( | ||||
|     'gpg', | ||||
|     [ | ||||
|       '--batch', | ||||
|       '--import-options', | ||||
|       'import-show', | ||||
|       '--import', | ||||
|       PRIVATE_KEY_FILE | ||||
|     ], | ||||
|     ['--batch', '--import-options', 'import-show', '--import', PRIVATE_KEY_FILE], | ||||
|     options | ||||
|   ); | ||||
| 
 | ||||
|  | @ -45,14 +39,8 @@ export async function importKey(privateKey: string) { | |||
| } | ||||
| 
 | ||||
| export async function deleteKey(keyFingerprint: string) { | ||||
|   await exec.exec( | ||||
|     'gpg', | ||||
|     ['--batch', '--yes', '--delete-secret-keys', keyFingerprint], | ||||
|     {silent: true} | ||||
|   ); | ||||
|   await exec.exec( | ||||
|     'gpg', | ||||
|     ['--batch', '--yes', '--delete-keys', keyFingerprint], | ||||
|     {silent: true} | ||||
|   ); | ||||
|   await exec.exec('gpg', ['--batch', '--yes', '--delete-secret-keys', keyFingerprint], { | ||||
|     silent: true | ||||
|   }); | ||||
|   await exec.exec('gpg', ['--batch', '--yes', '--delete-keys', keyFingerprint], { silent: true }); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										298
									
								
								src/installer.ts
									
										
									
									
									
								
							
							
						
						
									
										298
									
								
								src/installer.ts
									
										
									
									
									
								
							|  | @ -1,298 +0,0 @@ | |||
| import * as core from '@actions/core'; | ||||
| import * as io from '@actions/io'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import * as httpm from '@actions/http-client'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| import * as fs from 'fs'; | ||||
| import * as path from 'path'; | ||||
| import * as semver from 'semver'; | ||||
| import * as util from './util'; | ||||
| 
 | ||||
| const tempDirectory = util.getTempDir(); | ||||
| const IS_WINDOWS = util.isWindows(); | ||||
| 
 | ||||
| export async function getJava( | ||||
|   version: string, | ||||
|   arch: string, | ||||
|   jdkFile: string, | ||||
|   javaPackage: string | ||||
| ): Promise<void> { | ||||
|   let toolPath = tc.find(javaPackage, version); | ||||
| 
 | ||||
|   if (toolPath) { | ||||
|     core.debug(`Tool found in cache ${toolPath}`); | ||||
|   } else { | ||||
|     let compressedFileExtension = ''; | ||||
|     if (!jdkFile) { | ||||
|       core.debug('Downloading JDK from Azul'); | ||||
|       const http = new httpm.HttpClient('setup-java', undefined, { | ||||
|         allowRetries: true, | ||||
|         maxRetries: 3 | ||||
|       }); | ||||
|       const url = 'https://static.azul.com/zulu/bin/'; | ||||
|       const response = await http.get(url); | ||||
|       const statusCode = response.message.statusCode || 0; | ||||
|       if (statusCode < 200 || statusCode > 299) { | ||||
|         let body = ''; | ||||
|         try { | ||||
|           body = await response.readBody(); | ||||
|         } catch (err) { | ||||
|           core.debug(`Unable to read body: ${err.message}`); | ||||
|         } | ||||
|         const message = `Unexpected HTTP status code '${response.message.statusCode}' when retrieving versions from '${url}'. ${body}`.trim(); | ||||
|         throw new Error(message); | ||||
|       } | ||||
| 
 | ||||
|       const contents = await response.readBody(); | ||||
|       const refs = contents.match(/<a href.*\">/gi) || []; | ||||
|       const downloadInfo = getDownloadInfo(refs, version, arch, javaPackage); | ||||
|       jdkFile = await tc.downloadTool(downloadInfo.url); | ||||
|       version = downloadInfo.version; | ||||
|       compressedFileExtension = IS_WINDOWS ? '.zip' : '.tar.gz'; | ||||
|     } else { | ||||
|       core.debug('Retrieving Jdk from local path'); | ||||
|     } | ||||
|     compressedFileExtension = compressedFileExtension || getFileEnding(jdkFile); | ||||
|     let tempDir: string = path.join( | ||||
|       tempDirectory, | ||||
|       'temp_' + Math.floor(Math.random() * 2000000000) | ||||
|     ); | ||||
|     const jdkDir = await unzipJavaDownload( | ||||
|       jdkFile, | ||||
|       compressedFileExtension, | ||||
|       tempDir | ||||
|     ); | ||||
|     core.debug(`jdk extracted to ${jdkDir}`); | ||||
|     toolPath = await tc.cacheDir( | ||||
|       jdkDir, | ||||
|       javaPackage, | ||||
|       getCacheVersionString(version), | ||||
|       arch | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   let extendedJavaHome = 'JAVA_HOME_' + version + '_' + arch; | ||||
|   core.exportVariable(extendedJavaHome, toolPath); //TODO: remove for v2
 | ||||
|   // For portability reasons environment variables should only consist of
 | ||||
|   // uppercase letters, digits, and the underscore. Therefore we convert
 | ||||
|   // the extendedJavaHome variable to upper case and replace '.' symbols and
 | ||||
|   // any other non-alphanumeric characters with an underscore.
 | ||||
|   extendedJavaHome = extendedJavaHome.toUpperCase().replace(/[^0-9A-Z_]/g, '_'); | ||||
|   core.exportVariable('JAVA_HOME', toolPath); | ||||
|   core.exportVariable(extendedJavaHome, toolPath); | ||||
|   core.addPath(path.join(toolPath, 'bin')); | ||||
|   core.setOutput('path', toolPath); | ||||
|   core.setOutput('version', version); | ||||
| } | ||||
| 
 | ||||
| function getCacheVersionString(version: string) { | ||||
|   const versionArray = version.split('.'); | ||||
|   const major = versionArray[0]; | ||||
|   const minor = versionArray.length > 1 ? versionArray[1] : '0'; | ||||
|   const patch = versionArray.length > 2 ? versionArray[2] : '0'; | ||||
|   return `${major}.${minor}.${patch}`; | ||||
| } | ||||
| 
 | ||||
| function getFileEnding(file: string): string { | ||||
|   let fileEnding = ''; | ||||
| 
 | ||||
|   if (file.endsWith('.tar')) { | ||||
|     fileEnding = '.tar'; | ||||
|   } else if (file.endsWith('.tar.gz')) { | ||||
|     fileEnding = '.tar.gz'; | ||||
|   } else if (file.endsWith('.zip')) { | ||||
|     fileEnding = '.zip'; | ||||
|   } else if (file.endsWith('.7z')) { | ||||
|     fileEnding = '.7z'; | ||||
|   } else { | ||||
|     throw new Error(`${file} has an unsupported file extension`); | ||||
|   } | ||||
| 
 | ||||
|   return fileEnding; | ||||
| } | ||||
| 
 | ||||
| async function extractFiles( | ||||
|   file: string, | ||||
|   fileEnding: string, | ||||
|   destinationFolder: string | ||||
| ): Promise<void> { | ||||
|   const stats = fs.statSync(file); | ||||
|   if (!stats) { | ||||
|     throw new Error(`Failed to extract ${file} - it doesn't exist`); | ||||
|   } else if (stats.isDirectory()) { | ||||
|     throw new Error(`Failed to extract ${file} - it is a directory`); | ||||
|   } | ||||
| 
 | ||||
|   if ('.tar' === fileEnding || '.tar.gz' === fileEnding) { | ||||
|     await tc.extractTar(file, destinationFolder); | ||||
|   } else if ('.zip' === fileEnding) { | ||||
|     await tc.extractZip(file, destinationFolder); | ||||
|   } else { | ||||
|     // fall through and use sevenZip
 | ||||
|     await tc.extract7z(file, destinationFolder); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // This method recursively finds all .pack files under fsPath and unpacks them with the unpack200 tool
 | ||||
| async function unpackJars(fsPath: string, javaBinPath: string) { | ||||
|   if (fs.existsSync(fsPath)) { | ||||
|     if (fs.lstatSync(fsPath).isDirectory()) { | ||||
|       for (const file in fs.readdirSync(fsPath)) { | ||||
|         const curPath = path.join(fsPath, file); | ||||
|         await unpackJars(curPath, javaBinPath); | ||||
|       } | ||||
|     } else if (path.extname(fsPath).toLowerCase() === '.pack') { | ||||
|       // Unpack the pack file synchonously
 | ||||
|       const p = path.parse(fsPath); | ||||
|       const toolName = IS_WINDOWS ? 'unpack200.exe' : 'unpack200'; | ||||
|       const args = IS_WINDOWS ? '-r -v -l ""' : ''; | ||||
|       const name = path.join(p.dir, p.name); | ||||
|       await exec.exec(`"${path.join(javaBinPath, toolName)}"`, [ | ||||
|         `${args} "${name}.pack" "${name}.jar"` | ||||
|       ]); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| async function unzipJavaDownload( | ||||
|   repoRoot: string, | ||||
|   fileEnding: string, | ||||
|   destinationFolder: string, | ||||
|   extension?: string | ||||
| ): Promise<string> { | ||||
|   // Create the destination folder if it doesn't exist
 | ||||
|   await io.mkdirP(destinationFolder); | ||||
| 
 | ||||
|   const jdkFile = path.normalize(repoRoot); | ||||
|   const stats = fs.statSync(jdkFile); | ||||
|   if (stats.isFile()) { | ||||
|     await extractFiles(jdkFile, fileEnding, destinationFolder); | ||||
|     const jdkDirectory = path.join( | ||||
|       destinationFolder, | ||||
|       fs.readdirSync(destinationFolder)[0] | ||||
|     ); | ||||
|     await unpackJars(jdkDirectory, path.join(jdkDirectory, 'bin')); | ||||
|     return jdkDirectory; | ||||
|   } else { | ||||
|     throw new Error(`Jdk argument ${jdkFile} is not a file`); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function getDownloadInfo( | ||||
|   refs: string[], | ||||
|   version: string, | ||||
|   arch: string, | ||||
|   javaPackage: string | ||||
| ): {version: string; url: string} { | ||||
|   version = normalizeVersion(version); | ||||
| 
 | ||||
|   const archExtension = arch === 'x86' ? 'i686' : 'x64'; | ||||
| 
 | ||||
|   let extension = ''; | ||||
|   if (IS_WINDOWS) { | ||||
|     extension = `-win_${archExtension}.zip`; | ||||
|   } else { | ||||
|     if (process.platform === 'darwin') { | ||||
|       extension = `-macosx_${archExtension}.tar.gz`; | ||||
|     } else { | ||||
|       extension = `-linux_${archExtension}.tar.gz`; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   core.debug(`Searching for files with extension: ${extension}`); | ||||
| 
 | ||||
|   let pkgRegexp = new RegExp(''); | ||||
|   let pkgTypeLength = 0; | ||||
|   if (javaPackage === 'jdk') { | ||||
|     pkgRegexp = /jdk.*-/gi; | ||||
|     pkgTypeLength = 'jdk'.length; | ||||
|   } else if (javaPackage == 'jre') { | ||||
|     pkgRegexp = /jre.*-/gi; | ||||
|     pkgTypeLength = 'jre'.length; | ||||
|   } else if (javaPackage == 'jdk+fx') { | ||||
|     pkgRegexp = /fx-jdk.*-/gi; | ||||
|     pkgTypeLength = 'fx-jdk'.length; | ||||
|   } else { | ||||
|     throw new Error( | ||||
|       `package argument ${javaPackage} is not in [jdk | jre | jdk+fx]` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   // Maps version to url
 | ||||
|   let versionMap = new Map(); | ||||
| 
 | ||||
|   // Filter by platform
 | ||||
|   refs.forEach(ref => { | ||||
|     if (!ref.endsWith(extension + '">')) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // If we haven't returned, means we're looking at the correct platform
 | ||||
|     let versions = ref.match(pkgRegexp) || []; | ||||
|     if (versions.length > 1) { | ||||
|       throw new Error( | ||||
|         `Invalid ref received from https://static.azul.com/zulu/bin/: ${ref}` | ||||
|       ); | ||||
|     } | ||||
|     if (versions.length == 0) { | ||||
|       return; | ||||
|     } | ||||
|     const refVersion = versions[0].slice(pkgTypeLength, versions[0].length - 1); | ||||
| 
 | ||||
|     if (semver.satisfies(refVersion, version)) { | ||||
|       versionMap.set( | ||||
|         refVersion, | ||||
|         'https://static.azul.com/zulu/bin/' + | ||||
|           ref.slice('<a href="'.length, ref.length - '">'.length) | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   // Choose the most recent satisfying version
 | ||||
|   let curVersion = '0.0.0'; | ||||
|   let curUrl = ''; | ||||
|   for (const entry of versionMap.entries()) { | ||||
|     const entryVersion = entry[0]; | ||||
|     const entryUrl = entry[1]; | ||||
|     if (semver.gt(entryVersion, curVersion)) { | ||||
|       curUrl = entryUrl; | ||||
|       curVersion = entryVersion; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (curUrl == '') { | ||||
|     throw new Error( | ||||
|       `No valid download found for version ${version} and package ${javaPackage}. Check https://static.azul.com/zulu/bin/ for a list of valid versions or download your own jdk file and add the jdkFile argument` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return {version: curVersion, url: curUrl}; | ||||
| } | ||||
| 
 | ||||
| function normalizeVersion(version: string): string { | ||||
|   if (version.slice(0, 2) === '1.') { | ||||
|     // Trim leading 1. for versions like 1.8
 | ||||
|     version = version.slice(2); | ||||
|     if (!version) { | ||||
|       throw new Error('1. is not a valid version'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (version.endsWith('-ea')) { | ||||
|     // convert e.g. 14-ea to 14.0.0-ea
 | ||||
|     if (version.indexOf('.') == -1) { | ||||
|       version = version.slice(0, version.length - 3) + '.0.0-ea'; | ||||
|     } | ||||
|     // match anything in -ea.X (semver won't do .x matching on pre-release versions)
 | ||||
|     if (version[0] >= '0' && version[0] <= '9') { | ||||
|       version = '>=' + version; | ||||
|     } | ||||
|   } else if (version.split('.').length < 3) { | ||||
|     // For non-ea versions, add trailing .x if it is missing
 | ||||
|     if (version[version.length - 1] != 'x') { | ||||
|       version = version + '.x'; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return version; | ||||
| } | ||||
|  | @ -1,60 +1,51 @@ | |||
| import * as core from '@actions/core'; | ||||
| import * as installer from './installer'; | ||||
| import * as auth from './auth'; | ||||
| import * as gpg from './gpg'; | ||||
| 
 | ||||
| import * as constants from './constants'; | ||||
| import * as path from 'path'; | ||||
| import { getJavaDistribution } from './distributions/distribution-factory'; | ||||
| import { JavaInstallerOptions } from './distributions/base-models'; | ||||
| 
 | ||||
| async function run() { | ||||
|   try { | ||||
|     let version = core.getInput(constants.INPUT_VERSION); | ||||
|     if (!version) { | ||||
|       version = core.getInput(constants.INPUT_JAVA_VERSION, {required: true}); | ||||
|     const version = core.getInput(constants.INPUT_JAVA_VERSION); | ||||
|     const arch = core.getInput(constants.INPUT_ARCHITECTURE); | ||||
|     const distributionName = core.getInput(constants.INPUT_DISTRIBUTION); | ||||
|     const packageType = core.getInput(constants.INPUT_JAVA_PACKAGE); | ||||
|     const jdkFile = core.getInput(constants.INPUT_JDK_FILE); | ||||
| 
 | ||||
|     if (version || distributionName) { | ||||
|       if (!version || !distributionName) { | ||||
|         throw new Error( | ||||
|           `Both ‘${constants.INPUT_JAVA_VERSION}’ and ‘${constants.INPUT_DISTRIBUTION}’ inputs are required if one of them is specified` | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       const installerOptions: JavaInstallerOptions = { | ||||
|         arch, | ||||
|         packageType, | ||||
|         version | ||||
|       }; | ||||
| 
 | ||||
|       const distribution = getJavaDistribution(distributionName, installerOptions, jdkFile); | ||||
|       if (!distribution) { | ||||
|         throw new Error(`No supported distribution was found for input ${distributionName}`); | ||||
|       } | ||||
| 
 | ||||
|       const result = await distribution.setupJava(); | ||||
| 
 | ||||
|       core.info(''); | ||||
|       core.info('Java configuration:'); | ||||
|       core.info(`  Distribution: ${distributionName}`); | ||||
|       core.info(`  Version: ${result.version}`); | ||||
|       core.info(`  Path: ${result.path}`); | ||||
|       core.info(''); | ||||
|     } | ||||
| 
 | ||||
|     const arch = core.getInput(constants.INPUT_ARCHITECTURE, {required: true}); | ||||
|     if (!['x86', 'x64'].includes(arch)) { | ||||
|       throw new Error(`architecture "${arch}" is not in [x86 | x64]`); | ||||
|     } | ||||
| 
 | ||||
|     const javaPackage = core.getInput(constants.INPUT_JAVA_PACKAGE, { | ||||
|       required: true | ||||
|     }); | ||||
|     const jdkFile = core.getInput(constants.INPUT_JDK_FILE, {required: false}); | ||||
| 
 | ||||
|     await installer.getJava(version, arch, jdkFile, javaPackage); | ||||
| 
 | ||||
|     const matchersPath = path.join(__dirname, '..', '..', '.github'); | ||||
|     core.info(`##[add-matcher]${path.join(matchersPath, 'java.json')}`); | ||||
| 
 | ||||
|     const id = core.getInput(constants.INPUT_SERVER_ID, {required: false}); | ||||
|     const username = core.getInput(constants.INPUT_SERVER_USERNAME, { | ||||
|       required: false | ||||
|     }); | ||||
|     const password = core.getInput(constants.INPUT_SERVER_PASSWORD, { | ||||
|       required: false | ||||
|     }); | ||||
|     const gpgPrivateKey = | ||||
|       core.getInput(constants.INPUT_GPG_PRIVATE_KEY, {required: false}) || | ||||
|       constants.INPUT_DEFAULT_GPG_PRIVATE_KEY; | ||||
|     const gpgPassphrase = | ||||
|       core.getInput(constants.INPUT_GPG_PASSPHRASE, {required: false}) || | ||||
|       (gpgPrivateKey ? constants.INPUT_DEFAULT_GPG_PASSPHRASE : undefined); | ||||
| 
 | ||||
|     if (gpgPrivateKey) { | ||||
|       core.setSecret(gpgPrivateKey); | ||||
|     } | ||||
| 
 | ||||
|     await auth.configAuthentication(id, username, password, gpgPassphrase); | ||||
| 
 | ||||
|     if (gpgPrivateKey) { | ||||
|       core.info('importing private key'); | ||||
|       const keyFingerprint = (await gpg.importKey(gpgPrivateKey)) || ''; | ||||
|       core.saveState( | ||||
|         constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT, | ||||
|         keyFingerprint | ||||
|       ); | ||||
|     } | ||||
|     await auth.configureAuthentication(); | ||||
|   } catch (error) { | ||||
|     core.setFailed(error.message); | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										55
									
								
								src/util.ts
									
										
									
									
									
								
							
							
						
						
									
										55
									
								
								src/util.ts
									
										
									
									
									
								
							|  | @ -1,26 +1,41 @@ | |||
| import * as path from 'path'; | ||||
| import os from 'os'; | ||||
| import path from 'path'; | ||||
| 
 | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| 
 | ||||
| export function getTempDir() { | ||||
|   let tempDirectory = process.env.RUNNER_TEMP; | ||||
|   if (tempDirectory === undefined) { | ||||
|     let baseLocation; | ||||
|     if (isWindows()) { | ||||
|       // On windows use the USERPROFILE env variable
 | ||||
|       baseLocation = process.env['USERPROFILE'] | ||||
|         ? process.env['USERPROFILE'] | ||||
|         : 'C:\\'; | ||||
|     } else { | ||||
|       if (process.platform === 'darwin') { | ||||
|         baseLocation = '/Users'; | ||||
|       } else { | ||||
|         baseLocation = '/home'; | ||||
|       } | ||||
|     } | ||||
|     tempDirectory = path.join(baseLocation, 'actions', 'temp'); | ||||
|   } | ||||
|   let tempDirectory = process.env['RUNNER_TEMP'] || os.tmpdir(); | ||||
| 
 | ||||
|   return tempDirectory; | ||||
| } | ||||
| 
 | ||||
| export function isWindows() { | ||||
|   return process.platform === 'win32'; | ||||
| export function getVersionFromToolcachePath(toolPath: string) { | ||||
|   if (toolPath) { | ||||
|     return path.basename(path.dirname(toolPath)); | ||||
|   } | ||||
| 
 | ||||
|   return toolPath; | ||||
| } | ||||
| 
 | ||||
| export async function extractJdkFile(toolPath: string, extension?: string) { | ||||
|   if (!extension) { | ||||
|     extension = toolPath.endsWith('.tar.gz') ? 'tar.gz' : path.extname(toolPath); | ||||
|     if (extension.startsWith('.')) { | ||||
|       extension = extension.substring(1); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   switch (extension) { | ||||
|     case 'tar.gz': | ||||
|     case 'tar': | ||||
|       return await tc.extractTar(toolPath); | ||||
|     case 'zip': | ||||
|       return await tc.extractZip(toolPath); | ||||
|     default: | ||||
|       return await tc.extract7z(toolPath); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function getDownloadArchiveExtension() { | ||||
|   return process.platform === 'win32' ? 'zip' : 'tar.gz'; | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| extends: default | ||||
| 
 | ||||
| rules: | ||||
|   # 80 chars should be enough, but don't fail if a line is longer | ||||
|   # 100 chars should be enough, but don't fail if a line is longer | ||||
|   line-length: | ||||
|     max: 80 | ||||
|     max: 100 | ||||
|     level: warning | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue