From 8e6e7ac008dd300ef18635ac140dad6f44a9bb0f Mon Sep 17 00:00:00 2001 From: Jiaming <71547730+Jiaaming@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:56:50 +0800 Subject: [PATCH 1/2] refactor - Merge Language Server to Gradle Server (#1525) --- .vscode/launch.json | 35 --- extension/src/Extension.ts | 39 ++-- .../src/languageServer/languageServer.ts | 56 +---- extension/src/server/GradleServer.ts | 29 ++- gradle-language-server/build.gradle | 38 ++-- .../gradle/GradleLanguageServer.java | 21 +- .../gradle/transport/NamedPipeStream.java | 210 ++++++++++++++++++ gradle-server/build.gradle | 9 +- .../github/badsyntax/gradle/GradleServer.java | 10 + 9 files changed, 308 insertions(+), 139 deletions(-) create mode 100644 gradle-language-server/src/main/java/com/microsoft/gradle/transport/NamedPipeStream.java diff --git a/.vscode/launch.json b/.vscode/launch.json index 97080c446..f24dfdf37 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -77,41 +77,6 @@ "order": 3 } }, - { - "name": "Debug Language Server: Launch Extension", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}/extension" - ], - "outFiles": [ - "${workspaceFolder}/extension/dist/**/*.js" - ], - "preLaunchTask": "Gradle: Build", - "env": { - "VSCODE_DEBUG_LANGUAGE_SERVER": "true", - "VSCODE_GRADLE_PORT": "6006" - }, - "presentation": { - "group": "debug", - "order": 4 - } - }, - { - "type": "java", - "name": "Debug Language Server: Launch Language Server", - "request": "launch", - "mainClass": "com.microsoft.gradle.GradleLanguageServer", - "projectName": "gradle-language-server", - "env": { - "VSCODE_GRADLE_PORT": "6006" - }, - "presentation": { - "group": "debug", - "order": 5 - } - }, { "type": "java", "name": "Attach to Gradle Plugin", diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 69c483b84..540921d3d 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -22,7 +22,7 @@ import { FileWatcher } from "./util/FileWatcher"; import { DependencyTreeItem } from "./views/gradleTasks/DependencyTreeItem"; import { GRADLE_DEPENDENCY_REVEAL } from "./views/gradleTasks/DependencyUtils"; import { GradleDependencyProvider } from "./dependencies/GradleDependencyProvider"; -import { isLanguageServerStarted, startLanguageServer } from "./languageServer/languageServer"; +import { isLanguageServerStarted, startLanguageClientAndWaitForConnection } from "./languageServer/languageServer"; import { DefaultProjectsTreeDataProvider } from "./views/defaultProject/DefaultProjectsTreeDataProvider"; import { CompletionKinds, @@ -42,7 +42,7 @@ import { BspProxy } from "./bs/BspProxy"; export class Extension { private readonly bspProxy: BspProxy; - private readonly client: GradleClient; + private readonly taskServerClient: GradleClient; private readonly server: GradleServer; private readonly pinnedTasksStore: PinnedTasksStore; private readonly recentTasksStore: RecentTasksStore; @@ -71,7 +71,6 @@ export class Extension { private readonly onDidTerminalOpen: vscode.Event = this._onDidTerminalOpen.event; private recentTerminal: vscode.Terminal | undefined; private readonly buildServerController: BuildServerController; - public constructor(private readonly context: vscode.ExtensionContext) { const loggingChannel = vscode.window.createOutputChannel("Gradle for Java"); logger.setLoggingChannel(loggingChannel); @@ -92,15 +91,15 @@ export class Extension { const statusBarItem = vscode.window.createStatusBarItem(); this.bspProxy = new BspProxy(this.context, bspLogger); this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.bspProxy); - this.client = new GradleClient(this.server, statusBarItem, clientLogger); + this.taskServerClient = new GradleClient(this.server, statusBarItem, clientLogger); this.pinnedTasksStore = new PinnedTasksStore(context); this.recentTasksStore = new RecentTasksStore(); this.taskTerminalsStore = new TaskTerminalsStore(); this.rootProjectsStore = new RootProjectsStore(); - this.gradleBuildContentProvider = new GradleBuildContentProvider(this.client); + this.gradleBuildContentProvider = new GradleBuildContentProvider(this.taskServerClient); this.gradleTaskProvider = new GradleTaskProvider( this.rootProjectsStore, - this.client, + this.taskServerClient, this.gradleBuildContentProvider ); this.gradleDependencyProvider = new GradleDependencyProvider(this.gradleBuildContentProvider); @@ -114,7 +113,7 @@ export class Extension { this.gradleTaskProvider, this.gradleDependencyProvider, this.icons, - this.client + this.taskServerClient ); this.gradleTasksTreeView = vscode.window.createTreeView(GRADLE_TASKS_VIEW, { treeDataProvider: this.gradleTasksTreeDataProvider, @@ -130,7 +129,7 @@ export class Extension { this.taskTerminalsStore, this.rootProjectsStore, this.gradleTaskProvider, - this.client, + this.taskServerClient, this.icons ); this.recentTasksTreeView = vscode.window.createTreeView(RECENT_TASKS_VIEW, { @@ -140,7 +139,7 @@ export class Extension { this.defaultProjectsTreeDataProvider = new DefaultProjectsTreeDataProvider( this.gradleTaskProvider, this.rootProjectsStore, - this.client, + this.taskServerClient, this.icons ); this.defaultProjectsTreeView = vscode.window.createTreeView(GRADLE_DEFAULT_PROJECTS_VIEW, { @@ -151,7 +150,12 @@ export class Extension { this.gradleTaskManager = new GradleTaskManager(context); this.buildFileWatcher = new FileWatcher("**/*.{gradle,gradle.kts}"); this.gradleWrapperWatcher = new FileWatcher("**/gradle/wrapper/gradle-wrapper.properties"); - this.api = new Api(this.client, this.gradleTasksTreeDataProvider, this.gradleTaskProvider, this.icons); + this.api = new Api( + this.taskServerClient, + this.gradleTasksTreeDataProvider, + this.gradleTaskProvider, + this.icons + ); this.commands = new Commands( this.context, @@ -161,7 +165,7 @@ export class Extension { this.gradleTasksTreeDataProvider, this.recentTasksTreeDataProvider, this.gradleDaemonsTreeDataProvider, - this.client, + this.taskServerClient, this.rootProjectsStore, this.taskTerminalsStore, this.recentTasksStore, @@ -205,16 +209,21 @@ export class Extension { ) ); - this.client.onDidConnect(() => this.refresh()); + this.taskServerClient.onDidConnect(() => this.refresh()); + void startLanguageClientAndWaitForConnection( + this.context, + this.gradleBuildContentProvider, + this.rootProjectsStore, + this.server.getLanguageServerPipePath() + ); void this.activate(); - void startLanguageServer(this.context, this.gradleBuildContentProvider, this.rootProjectsStore); void vscode.commands.executeCommand("setContext", "allowParallelRun", getAllowParallelRun()); void vscode.commands.executeCommand("setContext", Context.ACTIVATION_CONTEXT_KEY, true); } private storeSubscriptions(): void { this.context.subscriptions.push( - this.client, + this.taskServerClient, this.pinnedTasksStore, this.recentTasksStore, this.taskTerminalsStore, @@ -327,7 +336,7 @@ export class Extension { } private async restartServer(): Promise { - await this.client.cancelBuilds(); + await this.taskServerClient.cancelBuilds(); await commands.executeCommand("workbench.action.restartExtensionHost"); } diff --git a/extension/src/languageServer/languageServer.ts b/extension/src/languageServer/languageServer.ts index d40d2cbd7..35d88d14f 100644 --- a/extension/src/languageServer/languageServer.ts +++ b/extension/src/languageServer/languageServer.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import * as net from "net"; -import * as path from "path"; import * as vscode from "vscode"; import { DidChangeConfigurationNotification, LanguageClientOptions } from "vscode-languageclient"; import { LanguageClient, StreamInfo } from "vscode-languageclient/node"; @@ -10,72 +9,33 @@ import { GradleBuildContentProvider } from "../client/GradleBuildContentProvider import { GradleBuild, GradleProject } from "../proto/gradle_pb"; import { RootProjectsStore } from "../stores"; import { - checkEnvJavaExecutable, getConfigJavaImportGradleHome, getConfigJavaImportGradleUserHome, getConfigJavaImportGradleVersion, getConfigJavaImportGradleWrapperEnabled, - findValidJavaHome, - getJavaExecutablePathFromJavaHome, } from "../util/config"; -import { prepareLanguageServerParams } from "./utils"; -const CHANNEL_NAME = "Gradle for Java (Language Server)"; export let isLanguageServerStarted = false; -export async function startLanguageServer( +export async function startLanguageClientAndWaitForConnection( context: vscode.ExtensionContext, contentProvider: GradleBuildContentProvider, - rootProjectsStore: RootProjectsStore + rootProjectsStore: RootProjectsStore, + languageServerPipePath: string ): Promise { void vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, (progress) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - return new Promise(async (resolve, reject) => { + return new Promise(async (resolve) => { progress.report({ message: "Initializing Gradle Language Server", }); const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: "file", language: "gradle" }], - outputChannel: vscode.window.createOutputChannel(CHANNEL_NAME), - outputChannelName: CHANNEL_NAME, initializationOptions: { settings: getGradleSettings(), }, }; - let serverOptions; - if (process.env.VSCODE_DEBUG_LANGUAGE_SERVER === "true") { - // debug mode - const port = process.env.VSCODE_GRADLE_PORT; - if (!port) { - void vscode.window.showErrorMessage( - "VSCODE_GRADLE_PORT is invalid, please check it in launch.json." - ); - return; - } - serverOptions = awaitServerConnection.bind(null, port); - } else { - // keep consistent with gRPC server - const javaHome = await findValidJavaHome(); - let javaCommand; - if (javaHome) { - javaCommand = getJavaExecutablePathFromJavaHome(javaHome); - } else { - if (!checkEnvJavaExecutable()) { - // we have already show error message in gRPC server for no java executable found, so here we will just reject and return - return reject(); - } - javaCommand = "java"; - } - const args = [ - ...prepareLanguageServerParams(), - "-jar", - path.resolve(context.extensionPath, "lib", "gradle-language-server.jar"), - ]; - serverOptions = { - command: javaCommand, - args: args, - }; - } + const serverOptions = () => awaitServerConnection(languageServerPipePath); const languageClient = new LanguageClient("gradle", "Gradle Language Server", serverOptions, clientOptions); void languageClient.onReady().then( () => { @@ -88,6 +48,7 @@ export async function startLanguageServer( } ); const disposable = languageClient.start(); + context.subscriptions.push(disposable); context.subscriptions.push( vscode.workspace.onDidChangeConfiguration((e) => { @@ -102,15 +63,14 @@ export async function startLanguageServer( }); } -async function awaitServerConnection(port: string): Promise { - const addr = parseInt(port); +async function awaitServerConnection(pipeName: string): Promise { return new Promise((resolve, reject) => { const server = net.createServer((stream) => { server.close(); resolve({ reader: stream, writer: stream }); }); server.on("error", reject); - server.listen(addr, () => { + server.listen(pipeName, () => { server.removeListener("error", reject); }); return server; diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index 2b36d2054..b96ebd9cc 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -10,7 +10,7 @@ import { Logger } from "../logger/index"; import { NO_JAVA_EXECUTABLE, OPT_RESTART } from "../constant"; import { redHatJavaInstalled } from "../util/config"; import { BspProxy } from "../bs/BspProxy"; - +import { generateRandomPipeName } from "../util/generateRandomPipeName"; const SERVER_LOGLEVEL_REGEX = /^\[([A-Z]+)\](.*)$/; const DOWNLOAD_PROGRESS_CHAR = "."; @@ -24,18 +24,35 @@ export class GradleServer { private ready = false; private taskServerPort: number | undefined; private restarting = false; - public readonly onDidStart: vscode.Event = this._onDidStart.event; public readonly onDidStop: vscode.Event = this._onDidStop.event; private process?: cp.ChildProcessWithoutNullStreams; + private languageServerPipePath: string; constructor( private readonly opts: ServerOptions, private readonly context: vscode.ExtensionContext, private readonly logger: Logger, private bspProxy: BspProxy - ) {} + ) { + this.setLanguageServerPipePath(); + } + + private setLanguageServerPipePath(): void { + try { + this.languageServerPipePath = generateRandomPipeName(); + } catch (error) { + this.languageServerPipePath = ""; + this.logger.error("Failed to generate language server pipe path", error.message); + sendInfo("", { + kind: "languageServerPipePathGenerationError", + }); + } + } + public getLanguageServerPipePath(): string { + return this.languageServerPipePath; + } public async start(): Promise { this.taskServerPort = await getPort(); const cwd = this.context.asAbsolutePath("lib"); @@ -47,7 +64,11 @@ export class GradleServer { return; } const startBuildServer = redHatJavaInstalled() ? "true" : "false"; - const args = [`--port=${this.taskServerPort}`, `--startBuildServer=${startBuildServer}`]; + const args = [ + `--port=${this.taskServerPort}`, + `--startBuildServer=${startBuildServer}`, + `--languageServerPipePath=${this.languageServerPipePath}`, + ]; if (startBuildServer === "true") { const buildServerPipeName = this.bspProxy.getBuildServerPipeName(); args.push(`--pipeName=${buildServerPipeName}`, `--bundleDir=${bundleDirectory}`); diff --git a/gradle-language-server/build.gradle b/gradle-language-server/build.gradle index 68ac210da..0358c3471 100644 --- a/gradle-language-server/build.gradle +++ b/gradle-language-server/build.gradle @@ -4,8 +4,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } repositories { @@ -21,10 +21,10 @@ application { } dependencies { - implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0" - implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0" + implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.19.0" + implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.19.0" implementation "org.codehaus.groovy:groovy-eclipse-batch:4.0.16-03" - implementation "com.google.code.gson:gson:2.8.9" + implementation "com.google.code.gson:gson:2.9.1" implementation "org.apache.bcel:bcel:6.6.1" testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.1" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.1" @@ -32,22 +32,18 @@ dependencies { ext.mainClass = "com.microsoft.gradle.GradleLanguageServer" -task copyJar(type: Copy) { - from "build/libs/gradle-language-server.jar" - from configurations.runtimeClasspath - into "../extension/lib" -} - -tasks.build.dependsOn tasks.copyJar -tasks.copyJar.dependsOn jar - -jar { - manifest { - attributes( - "Main-Class": "com.microsoft.gradle.GradleLanguageServer", - "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ') - ) - } +task customFatJar(type: Jar) { + manifest { + attributes( + "Main-Class": "com.microsoft.gradle.GradleLanguageServer", + ) + } + archiveBaseName = 'ls-fat-jar' + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } + exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' + destinationDirectory = file('../gradle-server/build/libs') + with jar } def mainJavaDir = 'src/main/java'; diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java index 47a92ca30..1a7e8a460 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java @@ -7,8 +7,8 @@ import com.google.gson.JsonElement; import com.microsoft.gradle.semantictokens.TokenModifier; import com.microsoft.gradle.semantictokens.TokenType; +import com.microsoft.gradle.transport.NamedPipeStream; import java.io.IOException; -import java.net.Socket; import java.net.URI; import java.nio.file.Paths; import java.util.Arrays; @@ -16,6 +16,7 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.DocumentFilter; import org.eclipse.lsp4j.ExecuteCommandOptions; @@ -35,25 +36,21 @@ import org.eclipse.lsp4j.services.LanguageServer; import org.eclipse.lsp4j.services.TextDocumentService; import org.eclipse.lsp4j.services.WorkspaceService; - public class GradleLanguageServer implements LanguageServer, LanguageClientAware { private GradleServices gradleServices; public static void main(String[] args) { GradleLanguageServer server = new GradleLanguageServer(); + if (StringUtils.isBlank(args[0])) { + server.exit(); + } try { + NamedPipeStream pipeStream = new NamedPipeStream(args[0]); + Launcher launcher; - String port = System.getenv("VSCODE_GRADLE_PORT"); - if (port == null) { - // Launch Mode - launcher = Launcher.createLauncher(server, LanguageClient.class, System.in, System.out); - } else { - // Debug Mode - Socket socket = new Socket("localhost", Integer.parseInt(port)); - launcher = Launcher.createLauncher(server, LanguageClient.class, socket.getInputStream(), - socket.getOutputStream()); - } + launcher = Launcher.createLauncher(server, LanguageClient.class, pipeStream.getInputStream(), + pipeStream.getOutputStream()); server.connect(launcher.getRemoteProxy()); launcher.startListening(); } catch (IOException e) { diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/transport/NamedPipeStream.java b/gradle-language-server/src/main/java/com/microsoft/gradle/transport/NamedPipeStream.java new file mode 100644 index 000000000..1d045814a --- /dev/null +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/transport/NamedPipeStream.java @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +package com.microsoft.gradle.transport; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +/** + * A named pipe stream implementation. + */ +public class NamedPipeStream { + private String pipeName; + private StreamProvider provider; + + public NamedPipeStream(String pipeName) { + this.pipeName = pipeName; + } + + interface StreamProvider { + InputStream getInputStream() throws IOException; + + OutputStream getOutputStream() throws IOException; + } + + /** + * getSelectedStream. + */ + public StreamProvider getSelectedStream() { + if (provider == null) { + provider = createProvider(); + } + return provider; + } + + private StreamProvider createProvider() { + PipeStreamProvider pipeStreamProvider = new PipeStreamProvider(); + pipeStreamProvider.initializeNamedPipe(); + return pipeStreamProvider; + } + + public InputStream getInputStream() throws IOException { + return getSelectedStream().getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return getSelectedStream().getOutputStream(); + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } + + /** + * PipeStreamProvider. + */ + protected final class PipeStreamProvider implements StreamProvider { + + private InputStream input; + private OutputStream output; + private String pipeName = NamedPipeStream.this.pipeName; + + @Override + public InputStream getInputStream() throws IOException { + return input; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return output; + } + + private void initializeNamedPipe() { + File pipeFile = new File(this.pipeName); + try { + attemptConnection(pipeFile); + } catch (IOException e) { + throw new IllegalStateException("Error initializing the named pipe", e); + } + } + + private void attemptConnection(File pipeFile) throws IOException { + if (isWindows()) { + AsynchronousFileChannel channel = AsynchronousFileChannel.open(pipeFile.toPath(), + StandardOpenOption.READ, StandardOpenOption.WRITE); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + } else { + UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath()); + SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); + channel.connect(socketAddress); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + } + } + } + + /** + * NamedPipeInputStream. + */ + public class NamedPipeInputStream extends InputStream { + + private ReadableByteChannel unixChannel; + private AsynchronousFileChannel winChannel; + private ByteBuffer buffer = ByteBuffer.allocate(1024); + private int readyBytes = 0; + + public NamedPipeInputStream(ReadableByteChannel channel) { + this.unixChannel = channel; + } + + public NamedPipeInputStream(AsynchronousFileChannel channel) { + this.winChannel = channel; + } + + @Override + public int read() throws IOException { + if (buffer.position() < readyBytes) { + return buffer.get() & 0xFF; + } + try { + buffer.clear(); + if (winChannel != null) { + readyBytes = winChannel.read(buffer, 0).get(); + } else { + readyBytes = unixChannel.read(buffer); + } + if (readyBytes == -1) { + return -1; // EOF + } + buffer.flip(); + return buffer.get() & 0xFF; + } catch (InterruptedException | ExecutionException e) { + throw new IOException(e); + } + } + } + + /** + * NamedPipeOutputStream. + */ + public class NamedPipeOutputStream extends OutputStream { + private WritableByteChannel unixChannel; + private AsynchronousFileChannel winChannel; + private ByteBuffer buffer = ByteBuffer.allocate(1); + + public NamedPipeOutputStream(WritableByteChannel channel) { + this.unixChannel = channel; + } + + public NamedPipeOutputStream(AsynchronousFileChannel channel) { + this.winChannel = channel; + } + + @Override + public void write(int b) throws IOException { + buffer.clear(); + buffer.put((byte) b); + buffer.position(0); + if (winChannel != null) { + Future result = winChannel.write(buffer, 0); + try { + result.get(); + } catch (Exception e) { + throw new IOException(e); + } + } else { + unixChannel.write(buffer); + } + } + + @Override + public void write(byte[] b) throws IOException { + final int buffer_size = 1024; + int blocks = b.length / buffer_size; + int writeBytes = 0; + for (int i = 0; i <= blocks; i++) { + int offset = i * buffer_size; + int length = Math.min(b.length - writeBytes, buffer_size); + if (length <= 0) { + break; + } + writeBytes += length; + ByteBuffer buffer = ByteBuffer.wrap(b, offset, length); + if (winChannel != null) { + Future result = winChannel.write(buffer, 0); + try { + result.get(); + } catch (Exception e) { + throw new IOException(e); + } + } else { + unixChannel.write(buffer); + } + } + } + } +} diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index 62720a014..bba2d9957 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -13,12 +13,14 @@ java { dependencies { implementation project(":gradle-plugin-api") implementation files('build/libs/server.jar') - implementation fileTree(dir: 'build/libs/runtime', include: ['*.jar'], exclude: ['gradle-tooling-api-*.jar', 'slf4j-api-*.jar']) + implementation fileTree(dir: 'build/libs/runtime', include: ['*.jar'], exclude: ['gradle-tooling-api-*.jar', + 'slf4j-api-*.jar']) implementation("org.gradle:gradle-tooling-api:${toolingAPIVersion}") implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation 'io.github.g00fy2:versioncompare:1.4.1' + implementation files('build/libs/ls-fat-jar.jar') runtimeOnly "io.grpc:grpc-netty:${grpcVersion}" runtimeOnly 'org.slf4j:slf4j-simple:2.0.0-alpha6' testImplementation "io.grpc:grpc-testing:${grpcVersion}" @@ -109,7 +111,6 @@ spotless { task serverStartScripts(type: CreateStartScripts) { dependsOn jar dependsOn 'copyRuntimeLibs' - dependsOn(':gradle-language-server:copyJar') outputDir = file(libsDir) mainClass = 'com.github.badsyntax.gradle.GradleServer' applicationName = project.name @@ -127,6 +128,7 @@ task copyRuntimeLibs(type: Copy) { into "../extension/lib" from configurations.runtimeClasspath duplicatesStrategy = 'exclude' + mustRunAfter ':gradle-language-server:customFatJar' } project.tasks.named("processResources") { @@ -143,6 +145,5 @@ test { '--add-opens=java.base/sun.nio.fs=ALL-UNNAMED' } - -compileJava.dependsOn 'generateProto', 'spotlessCheck' +compileJava.dependsOn 'generateProto', 'spotlessCheck', ':gradle-language-server:customFatJar' assemble.dependsOn serverStartScripts diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index bdf611ecd..231a9dc23 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -1,6 +1,7 @@ package com.github.badsyntax.gradle; import com.github.badsyntax.gradle.utils.Utils; +import com.microsoft.gradle.GradleLanguageServer; import io.grpc.Server; import io.grpc.ServerBuilder; import java.io.IOException; @@ -65,7 +66,9 @@ public static void main(String[] args) throws Exception { if (startBuildServer) { String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); + String languageServerPipePath = Utils.validateRequiredParam(params, "languageServerPipePath"); startBuildServerThread(buildServerPipeName, bundleDirectory); + startLanguageServerThread(languageServerPipePath); } } @@ -87,4 +90,11 @@ private static void startBuildServerThread(String pipeName, String directory) { Thread buildServerThread = new Thread(buildServerConnectionThread); buildServerThread.start(); } + + private static void startLanguageServerThread(String languageServerPipePath) { + Thread languageServerThread = new Thread(() -> { + GradleLanguageServer.main(new String[]{languageServerPipePath}); + }); + languageServerThread.start(); + } } From 2243c686f60b14e2ef0c20ab4324cdb961c9cba6 Mon Sep 17 00:00:00 2001 From: Jiaming <71547730+Jiaaming@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:18:58 +0800 Subject: [PATCH 2/2] doc - Update doc after merge language server (#1528) --- ARCHITECTURE.md | 7 ++++-- CONTRIBUTING.md | 35 ++++++++++++++------------- gradle-server/build.gradle | 5 +++- images/gradle-server-architecture.svg | 2 +- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8c50de238..1755c286b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,10 +5,10 @@ This extension contains three major components: - [A Gradle language server](./gradle-language-server) that provides language features such as code completion and diagnostics for gradle script files. - [A Gradle project importer](./extension/jdtls.ext/com.microsoft.gradle.bs.importer) that imports Gradle projects detected by the [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle) into the workspace. This importer works with the Language Support for Java extension. -# Gradle Server and Gradle Language Server +# Gradle Server -The gradle server is a long-running Java process that include two threads: 1. Build Server 2. Task Server +The gradle server is a long-running Java process that include three threads: 1. Build Server 2. Task Server 3. Language Server ## Build Server The Gradle Build Server communicates with the Build Client using the [Build Server Protocol](https://build-server-protocol.github.io/) through named pipes. @@ -17,6 +17,9 @@ Due to Java's limited support for named pipes on Windows, a TypeScript layer nam For information about the Build Server itself, visit the [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle). +## Language Server +Language Server provides language features such as code completion and diagnostics for Gradle script files. It communicates with the Language Client using the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) through named pipe. + ## Task Server The task server and client using [gRPC](https://grpc.io/) as the interface between each other. It uses TypeScript (Node.js) on the client and Java on the server. A long running server provides very good performance. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed6a0b136..508a6d084 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,30 +6,36 @@ Start by opening an issue using one of the issue templates, or propose a change ## Running the Project -### Build Gradle Server and Gradle Language Server. +### Prerequisites 1. Install [nvm](https://github.com/nvm-sh/nvm) 2. Install [Java version >= 17](https://adoptium.net/) -3. Change directory to the root of the project -4. Select Node version: `nvm use` -5. If using an Apple M1: +3. Select Node version: `nvm use` +4. If using an Apple M1: - Add `npm_arch=x64` to $HOME/.gradle/gradle.properties - Add `protoc_platform=osx-x86_64` to $HOME/.gradle/gradle.properties -6. If using Windows: +5. If using Windows: - The extension uses `grpc-tools@1.12.x` dependency which does not work out-of-the-box in Windows (check [this issues](https://github.com/grpc/grpc-node/issues/2338) for details), so you'll need to install some aditional DLLs if the project build is failed. - Download and start [Build Tools for Visual Studio 2022](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022). - Go to the **Individual Components** tab and select the following: - `MSVC v143 - VS 2022 C++ x64/x86 build tools (latest)` (replacing `x64/x86` with your arch) - `Windows Universal CRT SDK` - Click `Install` to add the components. -7. Build project files: `./gradlew build` -Running the build for the first time can take a bit of time, but subsequent builds should be fast. +### Build Gradle Build Server & Gradle Project Importer +Before proceeding with the build steps for Build Task Server & Language Server, you need to build the Gradle Build Server and its client (Gradle Project Importer) first. + +1. `cd extension` +2. `git clone https://github.com/microsoft/build-server-for-gradle.git ` +3. Build the Importer and Build Server jars: `../gradlew buildJars` + +### Build Task Server & Language Server +After building the Gradle Build Server and its client, proceed with the following steps. + +1. Change directory to the root of the project -### Build Gradle Project Importer -1. Install [Java version >= 17](https://adoptium.net/) -2. `cd extension` -3. `git clone https://github.com/microsoft/build-server-for-gradle.git ` -4. Build the Importer and Build Server jars: `../gradlew buildJars` +2. Build project files: `./gradlew build` + +Running the build for the first time can take a bit of time, but subsequent builds should be fast. ## Debugging Gradle plugin @@ -51,11 +57,6 @@ The extension uses a Gradle plugin (`com.microsoft.gradle.GradlePlugin`) to get > ``` > it indicates that the connection attempt to the Gradle Server was too slow. The [GradleBuildClient](/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java#L107) requires an active Gradle Server to successfully establish a connection. If you encounter this issue, please retry the connection promptly to avoid this error. -## Debugging Gradle Language Server (editing feature related) - -1. Run vscode launch configuration `Debug Language Server: Launch Extension`. -2. Run vscode launch configuration `Debug Language Server: Launch Language Server`. - ## Development Workflow Open the root of the project in VS Code. diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index bba2d9957..83dede14b 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -125,10 +125,13 @@ task serverStartScripts(type: CreateStartScripts) { } task copyRuntimeLibs(type: Copy) { - into "../extension/lib" from configurations.runtimeClasspath + into libsDir duplicatesStrategy = 'exclude' mustRunAfter ':gradle-language-server:customFatJar' + doFirst { + delete libsDir + } } project.tasks.named("processResources") { diff --git a/images/gradle-server-architecture.svg b/images/gradle-server-architecture.svg index 18fdb06d6..4e37f95bb 100644 --- a/images/gradle-server-architecture.svg +++ b/images/gradle-server-architecture.svg @@ -1,4 +1,4 @@ -
Gradle Server
Task Server
Build Server
Client
Build Client
Gradle project Importer
bspProxy
Streaming RPC's
Get/Run/Cancel build task
Progress, 
STDOUT & STDERR
Build Server Protol
Message forwarding
Gradle Tooling API
+
Gradle Server
Task Server
Build Server
Client
Build Client
Gradle project Importer
bspProxy
Streaming RPC's
Get/Run/Cancel build task
Progress, 
STDOUT & STDERR
Build Server Protol
Message forwarding
Language
Server
Language Server Protol
Gradle Tooling API
\ No newline at end of file