Improved logics for finding JVM / JDK in vscode-boot-java
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import {activate, findJvmFile, ActivatorOptions} from './launch-util';
|
||||
import {activate, ActivatorOptions} from './launch-util';
|
||||
import {JVM} from './jvm-util';
|
||||
|
||||
export {activate, findJvmFile, ActivatorOptions};
|
||||
export {activate, JVM, ActivatorOptions};
|
||||
|
||||
186
vscode-extensions/commons-vscode/src/jvm-util.ts
Normal file
186
vscode-extensions/commons-vscode/src/jvm-util.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import * as FS from 'fs';
|
||||
import * as Path from 'path';
|
||||
import * as ChildProcess from 'child_process';
|
||||
|
||||
'use strict';
|
||||
|
||||
export interface JVM {
|
||||
/**
|
||||
* 8 = Java 1.8.x, 9 = Java 9.x, etc
|
||||
*/
|
||||
getMajorVersion() : number
|
||||
|
||||
/**
|
||||
* Path to the Java executable
|
||||
*/
|
||||
getJavaExecutable() : 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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 determineJavaVersion(javaExe).then(version => new JavaExecutable(javaExe, version));
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
if (!javaHome) {
|
||||
javaHome = process.env["JAVA_HOME"];
|
||||
}
|
||||
if (javaHome) {
|
||||
//Resolve symlinks
|
||||
javaHome = FS.realpathSync(javaHome);
|
||||
}
|
||||
let binName = correctBinname("java");
|
||||
if (javaHome) {
|
||||
return Path.resolve(javaHome, "bin", binName);
|
||||
} else {
|
||||
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 JavaExecutable implements JVM {
|
||||
javaExe : string
|
||||
version : number
|
||||
toolsJar: () => string | null;
|
||||
constructor(javaExe : string, version : number) {
|
||||
this.javaExe = javaExe;
|
||||
this.version = version;
|
||||
this.toolsJar = memoize(() => this.findToolsJar());
|
||||
}
|
||||
|
||||
getJavaHome() : string {
|
||||
return Path.resolve(this.javaExe, "..", "..");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
function determineJavaVersion(javaExecutablePath : string) : Promise<number> {
|
||||
//Examples of the 'java -version' command output:
|
||||
//
|
||||
// For Java 9:
|
||||
/*
|
||||
java version "9.0.4"
|
||||
Java(TM) SE Runtime Environment (build 9.0.4+11)
|
||||
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)
|
||||
*/
|
||||
// For Java 8:
|
||||
/*
|
||||
java version "1.8.0_161"
|
||||
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
|
||||
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
|
||||
*/
|
||||
return new Promise((resolve, reject) => {
|
||||
ChildProcess.execFile(javaExecutablePath, ['-version'], {}, (error, stdout, stderr) => {
|
||||
let versionStart = stderr.indexOf('"');
|
||||
if (versionStart>=0) {
|
||||
versionStart = versionStart + 1;
|
||||
let versionEnd = stderr.indexOf('"', versionStart);
|
||||
if (versionEnd>=0) {
|
||||
let versionString = stderr.substring(versionStart, versionEnd);
|
||||
let pieces = versionString.split(".");
|
||||
let major = parseInt(pieces[0]);
|
||||
major = major==1 ? parseInt(pieces[1]) : major
|
||||
return resolve(major);
|
||||
}
|
||||
}
|
||||
//Unexpected output...
|
||||
return resolve(0);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function correctBinname(binname: string) {
|
||||
if (process.platform === 'win32')
|
||||
return binname + '.exe';
|
||||
else
|
||||
return binname;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import {WorkspaceEdit, Position} from 'vscode-languageserver-types';
|
||||
import {HighlightService, HighlightParams} from './highlight-service';
|
||||
import { log } from 'util';
|
||||
import { tmpdir } from 'os';
|
||||
import { JVM, findJvm } from './jvm-util';
|
||||
|
||||
let p2c = P2C.createConverter();
|
||||
|
||||
@@ -34,7 +35,7 @@ export interface ActivatorOptions {
|
||||
launcher: (context: VSCode.ExtensionContext) => string;
|
||||
jvmHeap?: string;
|
||||
workspaceOptions?: VSCode.WorkspaceConfiguration;
|
||||
classpath?: (context: VSCode.ExtensionContext, javaVersion: number) => string[];
|
||||
classpath?: (context: VSCode.ExtensionContext, jvm: JVM) => string[];
|
||||
}
|
||||
|
||||
type JavaOptions = {
|
||||
@@ -72,22 +73,21 @@ export function activate(options: ActivatorOptions, context: VSCode.ExtensionCon
|
||||
}
|
||||
}
|
||||
|
||||
let javaExecutablePath = findJvmFile('bin', correctBinname('java'));
|
||||
|
||||
if (javaExecutablePath == null) {
|
||||
VSCode.window.showErrorMessage("Couldn't locate java in $JAVA_HOME or $PATH");
|
||||
return;
|
||||
}
|
||||
log("Found java exe: " + javaExecutablePath);
|
||||
|
||||
|
||||
return javaVersion(javaExecutablePath).then(version => {
|
||||
if (!version) {
|
||||
return findJvm().then(jvm => {
|
||||
if (!jvm) {
|
||||
VSCode.window.showErrorMessage("Couldn't locate java in $JAVA_HOME or $PATH");
|
||||
return;
|
||||
}
|
||||
let javaExecutablePath = jvm.getJavaExecutable();
|
||||
log("Found java exe: " + javaExecutablePath);
|
||||
|
||||
let version = jvm.getMajorVersion();
|
||||
if (version<8) {
|
||||
VSCode.window.showErrorMessage('Java-based Language Server requires Java 8 or higher (using ' + javaExecutablePath + ')');
|
||||
return;
|
||||
}
|
||||
log("isJavaEightOrHigher => true");
|
||||
|
||||
|
||||
function createServer(): Promise<StreamInfo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
PortFinder.getPort((err, port) => {
|
||||
@@ -112,7 +112,7 @@ export function activate(options: ActivatorOptions, context: VSCode.ExtensionCon
|
||||
'-Dsts.log.file=' + logfile
|
||||
];
|
||||
if (options.classpath) {
|
||||
const classpath = options.classpath(context, version);
|
||||
const classpath = options.classpath(context, jvm);
|
||||
if (classpath) {
|
||||
args.push('-cp');
|
||||
args.push(classpath.join(Path.delimiter));
|
||||
@@ -143,7 +143,6 @@ export function activate(options: ActivatorOptions, context: VSCode.ExtensionCon
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return setupLanguageClient(context, createServer, options);
|
||||
});
|
||||
}
|
||||
@@ -210,44 +209,6 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser
|
||||
});
|
||||
}
|
||||
|
||||
function javaVersion(javaExecutablePath: string): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
ChildProcess.execFile(javaExecutablePath, ['-version'], {}, (error, stdout, stderr) => {
|
||||
if (stderr.indexOf('1.8') >= 0) {
|
||||
resolve(8);
|
||||
} else if (stderr.indexOf('java version "9') >= 0) {
|
||||
resolve(9);
|
||||
} else {
|
||||
resolve(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function findJvmFile(folderPath: string, file: string): string {
|
||||
// First search each JAVA_HOME bin folder
|
||||
if (process.env['JAVA_HOME']) {
|
||||
let workspaces = process.env['JAVA_HOME'].split(Path.delimiter);
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
let filePath = Path.join(workspaces[i], folderPath, file);
|
||||
if (FS.existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then search PATH parts
|
||||
if (process.env['PATH']) {
|
||||
let pathparts = process.env['PATH'].split(Path.delimiter);
|
||||
for (let i = 0; i < pathparts.length; i++) {
|
||||
let filePath = Path.join(pathparts[i], file);
|
||||
if (FS.existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function correctBinname(binname: string) {
|
||||
if (process.platform === 'win32')
|
||||
return binname + '.exe';
|
||||
|
||||
0
vscode-extensions/commons-vscode/test.txt
Normal file
0
vscode-extensions/commons-vscode/test.txt
Normal file
@@ -1,31 +1,17 @@
|
||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "Launch Extension",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
"name": "Launch Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceRoot}/out/test"],
|
||||
"preLaunchTask": "npm"
|
||||
}
|
||||
]
|
||||
}
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": true,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
|
||||
"preLaunchTask": "npm: watch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,30 +1,20 @@
|
||||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
// A task runner that calls a custom npm script that compiles the extension.
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "0.1.0",
|
||||
|
||||
// we want to run npm
|
||||
"command": "npm",
|
||||
|
||||
// the command is a shell script
|
||||
"isShellCommand": true,
|
||||
|
||||
// show the output window only if unrecognized errors occur.
|
||||
"showOutput": "silent",
|
||||
|
||||
// we run the custom script "compile" as defined in package.json
|
||||
"args": ["run", "compile"], //, "--loglevel", "silent"],
|
||||
|
||||
// The tsc compiler is started in watching mode
|
||||
"isWatching": true,
|
||||
|
||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||
"problemMatcher": "$tsc-watch"
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
'use strict';
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
|
||||
import * as net from 'net';
|
||||
|
||||
@@ -14,28 +12,25 @@ import { workspace, TextDocument } from 'vscode';
|
||||
import { Trace } from 'vscode-jsonrpc';
|
||||
|
||||
import * as commons from 'commons-vscode';
|
||||
import { connect } from 'tls';
|
||||
|
||||
export function activate(context: VSCode.ExtensionContext) {
|
||||
|
||||
let options: commons.ActivatorOptions = {
|
||||
DEBUG: false,
|
||||
CONNECT_TO_LS: false,
|
||||
extensionId: 'boot-java',
|
||||
launcher: (context: VSCode.ExtensionContext) => 'org.springframework.boot.loader.JarLauncher',
|
||||
classpath: (context: VSCode.ExtensionContext, javaVersion: number) => {
|
||||
classpath: (context: VSCode.ExtensionContext, jvm: commons.JVM) => {
|
||||
const classpath = [
|
||||
Path.resolve(context.extensionPath, 'jars/language-server.jar')
|
||||
];
|
||||
|
||||
if (javaVersion < 9) {
|
||||
const toolsJar = commons.findJvmFile('lib', 'tools.jar');
|
||||
if (toolsJar) {
|
||||
classpath.push(toolsJar);
|
||||
} else {
|
||||
VSCode.window.showWarningMessage('JAVA_HOME environment variable points either to JRE or JDK missing "lib/tools.jar" hence Boot Hints are unavailable');
|
||||
}
|
||||
if (!jvm.isJdk()) {
|
||||
VSCode.window.showWarningMessage('JAVA_HOME or PATH environment variable seems to point to a JRE. A JDK is required, hence Boot Hints are unavailable.');
|
||||
}
|
||||
const toolsJar = jvm.getToolsJar();
|
||||
if (toolsJar) {
|
||||
classpath.unshift(toolsJar);
|
||||
}
|
||||
|
||||
return classpath;
|
||||
},
|
||||
clientOptions: {
|
||||
|
||||
@@ -8,9 +8,9 @@ workdir=`pwd`
|
||||
npm install ../commons-vscode/commons-vscode-*.tgz
|
||||
|
||||
# Use maven to build fat jar of the language server
|
||||
cd ../../headless-services/boot-java-language-server
|
||||
./build.sh
|
||||
#cd ../../headless-services/boot-java-language-server
|
||||
#./build.sh
|
||||
|
||||
mkdir -p ${workdir}/jars
|
||||
cp target/*.jar ${workdir}/jars/language-server.jar
|
||||
# mkdir -p ${workdir}/jars
|
||||
# cp target/*.jar ${workdir}/jars/language-server.jar
|
||||
|
||||
|
||||
Reference in New Issue
Block a user