Boot properties launher seems to have a bug so it does not properly handle file url encodings if path has spaces. Fortunately, it works fine if we just pass the path of the file directly without any encoding rather than a proper url.
271 lines
8.7 KiB
TypeScript
271 lines
8.7 KiB
TypeScript
import * as FS from 'fs';
|
|
import * as Path from 'path';
|
|
import * as ChildProcess from 'child_process';
|
|
import { basename } from 'path';
|
|
|
|
'use strict';
|
|
|
|
export interface JVM {
|
|
/**
|
|
* 8 = Java 1.8.x, 9 = Java 9.x, etc
|
|
*/
|
|
getMajorVersion() : number
|
|
|
|
/**
|
|
* Path to the Java executable
|
|
*/
|
|
getJavaExecutable() : string
|
|
|
|
/**
|
|
* Path to the corresponding 'java home' for the executable.
|
|
*/
|
|
getJavaHome() : string
|
|
|
|
/**
|
|
* Detect whether this JVM is a JDK
|
|
*/
|
|
isJdk() : boolean
|
|
|
|
/**
|
|
* Find tools.jar for this JVM.
|
|
*
|
|
* Note that if the JVM is a JRE; or a Java 9 or above JDK;
|
|
* then this will return null.
|
|
*/
|
|
getToolsJar() : string | null
|
|
|
|
/**
|
|
* Launch an executable jar with this jvm.
|
|
* This autmatically adds tools.jar to the classpath if available.
|
|
* WARNING: For adding tools jar to work properly, the jar must be packaged
|
|
* using spring-boot-maven-plugin ZIP layout.
|
|
*/
|
|
jarLaunch(jar: string, vmargs?: string[], execFileOptions?: ChildProcess.ExecFileOptions) : ChildProcess.ChildProcess
|
|
}
|
|
|
|
/**
|
|
* Find a JVM by looking in the JAVA_HOME and PATH environment variables.
|
|
*
|
|
* Optionally, a specific javaHome can be passed in. This shortcuts the
|
|
* search logic and uses that javaHome as is.
|
|
*
|
|
* The returned JVM may or may not be a JDK. Methods are provided to obtain corresponding
|
|
* toolsjar and to check whether the JVM is a JDK.
|
|
*/
|
|
export function findJvm(javaHome?: string) : Promise<JVM | null> {
|
|
let javaExe = findJavaExe(javaHome);
|
|
if (javaExe) {
|
|
return getJavaInfo(javaExe).then(javaProps => new JVMImpl(
|
|
javaProps.get("java.home"),
|
|
javaExe,
|
|
getMajorVersion(javaProps)
|
|
));
|
|
}
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
/**
|
|
* Like findJvm, but additionally, if the found JVM is not a JDK tries to
|
|
* find a companion JDK that may be installed alongside it.
|
|
*/
|
|
export function findJdk(javaHome?: string) : Promise<JVM | null> {
|
|
return findJvm(javaHome).then(jvm => {
|
|
if (!jvm.isJdk()) {
|
|
console.log("found jvm is not a JDK");
|
|
|
|
//Try to find a 'sibling' JDK.
|
|
//Mainly for windows where it is common to have side-by-side install of a jre and jdk, instead of a
|
|
//nested jre install inside of a jdk.
|
|
|
|
//E.g.
|
|
//C:\ProgramFiles\Java\jdk1.8.0_161
|
|
//C:\ProgramFiles\Java\jre1.8.0_161
|
|
|
|
let javaExe = jvm.getJavaExecutable();
|
|
console.log("javaExe = ", javaExe);
|
|
// javaExe example: C:\ProgramFiles\Java\jre1.8.0_161\bin\java.exe
|
|
let jhome = jvm.getJavaHome();
|
|
console.log("jhome = ", jhome);
|
|
let basename : string = Path.basename(jhome);
|
|
console.log("basename = ", basename);
|
|
let altBasename : string = basename.replace("jre", "jdk");
|
|
console.log("altBasename = ", altBasename);
|
|
if (altBasename!==basename) {
|
|
let altHome = Path.join(Path.dirname(jhome), altBasename);
|
|
console.log("altHome = ", altHome);
|
|
if (FS.existsSync(altHome)) {
|
|
let altExe = Path.resolve(altHome, "bin", correctBinname("java"));
|
|
console.log("altExe = ", altExe);
|
|
return new JVMImpl(altHome, altExe, jvm.getMajorVersion());
|
|
}
|
|
}
|
|
}
|
|
return jvm;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Find a 'java' exe by looking in the JAVA_HOME and PATH environment variables.
|
|
* <p>
|
|
* Optionally, a specific javaHome can be passed in. This shortcuts the
|
|
* search logic and uses that javaHome as is, not looking anywhere else.
|
|
*/
|
|
function findJavaExe(javaHome?: string) : string | null {
|
|
//Try java home first
|
|
if (!javaHome) {
|
|
javaHome = process.env["JAVA_HOME"];
|
|
}
|
|
if (javaHome) {
|
|
//Resolve symlinks
|
|
javaHome = FS.realpathSync(javaHome);
|
|
}
|
|
let binName = correctBinname("java");
|
|
if (javaHome) {
|
|
let javaExe = Path.resolve(javaHome, "bin", binName);
|
|
if (FS.existsSync(javaExe)) {
|
|
return javaExe;
|
|
}
|
|
}
|
|
|
|
for (var searchPath of process.env['PATH'].split(Path.delimiter)) {
|
|
let javaExe = Path.resolve(searchPath, binName);
|
|
if (FS.existsSync(javaExe)) {
|
|
//Resolve symlinks
|
|
return FS.realpathSync(javaExe);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
type Getter<T> = () => T;
|
|
|
|
function memoize<T>(getter : Getter<T>) : Getter<T> {
|
|
let computed : boolean = false;
|
|
let value : T | null = null;
|
|
return () => {
|
|
if (!computed) {
|
|
value = getter();
|
|
computed = true;
|
|
}
|
|
return value;
|
|
};
|
|
}
|
|
|
|
const TOOLS_JAR_PATHS : string[][] = [
|
|
["lib", "tools.jar"],
|
|
["..", "lib", "tools.jar"]
|
|
];
|
|
|
|
class JVMImpl implements JVM {
|
|
javaHome : string
|
|
javaExe : string
|
|
version : number
|
|
toolsJar: () => string | null;
|
|
constructor(javaHome : string, javaExe : string, version : number) {
|
|
this.javaHome = javaHome;
|
|
this.javaExe = javaExe;
|
|
this.version = version;
|
|
this.toolsJar = memoize(() => this.findToolsJar());
|
|
}
|
|
|
|
getJavaHome() : string {
|
|
return this.javaHome;
|
|
}
|
|
|
|
findToolsJar() : string | null {
|
|
if (this.version>=9) {
|
|
return null;
|
|
}
|
|
let javaHome = this.getJavaHome();
|
|
for (var tjp of TOOLS_JAR_PATHS) {
|
|
let toolsJar = Path.resolve(javaHome, ...tjp);
|
|
if (FS.existsSync(toolsJar)) {
|
|
return toolsJar;
|
|
}
|
|
}
|
|
//Not found.
|
|
return null;
|
|
}
|
|
|
|
getMajorVersion() {
|
|
return this.version;
|
|
}
|
|
getJavaExecutable(): string {
|
|
return this.javaExe;
|
|
}
|
|
isJdk(): boolean {
|
|
//Consider memoizing?
|
|
if (this.version<9) {
|
|
return this.getToolsJar()!=null;
|
|
} else {
|
|
return FS.existsSync(Path.resolve(this.getJavaHome(), "jmods", "jdk.management.jmod"));
|
|
}
|
|
}
|
|
getToolsJar(): string {
|
|
return this.toolsJar();
|
|
}
|
|
jarLaunch(jar: string, vmargs?: [string], execFileOptions?: ChildProcess.ExecFileOptions): ChildProcess.ChildProcess {
|
|
let args = [];
|
|
let toolsJar = this.getToolsJar();
|
|
if (toolsJar) {
|
|
args.push("-Dloader.path="+toolsJar);
|
|
}
|
|
if (vmargs) {
|
|
args.push(...vmargs);
|
|
}
|
|
args.push("-jar", jar);
|
|
return ChildProcess.execFile(this.getJavaExecutable(), args, execFileOptions);
|
|
}
|
|
}
|
|
|
|
function getJavaInfo(javaExe : string) : Promise<Map<string, string>> {
|
|
//console.log("Fetching java properties for "+javaExe);
|
|
return new Promise((resolve, reject) => {
|
|
ChildProcess.execFile(javaExe, ['-XshowSettings:properties'], {}, (error, stdout, stderr) => {
|
|
let lines = stderr.split(/\r?\n/);
|
|
let propNames = [ 'java.version', 'java.home' ];
|
|
let props = new Map<string,string>();
|
|
for (var l of lines) {
|
|
//console.log("Line: "+l);
|
|
for (var p of propNames) {
|
|
let offset = l.indexOf(p);
|
|
if (offset>=0) {
|
|
//Make sure it looks like a proper 'assignment' to the property and not an
|
|
// accidental match.
|
|
//console.log("Propname found: "+p);
|
|
let assign = " " +p + " = ";
|
|
offset = l.indexOf(assign);
|
|
if (offset>=0) {
|
|
//console.log("Assignment found: "+p);
|
|
offset = offset + assign.length;
|
|
let value = l.substring(offset);
|
|
//console.log("value = "+value);
|
|
props.set(p, value);
|
|
if (props.size >= propNames.length) {
|
|
//We found everything we care about, so we can stop now.
|
|
//console.log("result = ", props);
|
|
return resolve(props);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//Not found everything we expected.
|
|
return reject("Unexpected output from `java -XshowSettings:properties`. Didn't find all expected properties: "+propNames);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getMajorVersion(javaProperties : Map<string,string>) : number {
|
|
let versionString = javaProperties.get('java.version');
|
|
let pieces = versionString.split(".");
|
|
let major = parseInt(pieces[0]);
|
|
return major==1 ? parseInt(pieces[1]) : major;
|
|
}
|
|
|
|
function correctBinname(binname: string) {
|
|
if (process.platform === 'win32')
|
|
return binname + '.exe';
|
|
else
|
|
return binname;
|
|
} |