Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eclipse language server, handle directory corruption #3016

Merged
merged 10 commits into from
Jan 24, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import org.apache.tools.tar.TarInputStream;
import org.slf4j.Logger;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.URI;
Expand All @@ -20,11 +22,12 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

public class EclipseLanguageServerFacade {
private static final String DOWNLOAD_BASE_URL
= "https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/";
= "https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/";

private final Process server;

Expand All @@ -34,8 +37,8 @@ public EclipseLanguageServerFacade(String pathToLanguageServerPlugin, Logger log
int javaVersion = Runtime.version().feature();

Path languageServerPath = (pathToLanguageServerPlugin == null)
? getLanguageServerDirectory(javaVersion, logger)
: Paths.get(pathToLanguageServerPlugin).resolve("jdt-language-server");
? getLanguageServerDirectory(javaVersion, logger, false)
: Paths.get(pathToLanguageServerPlugin).resolve("jdt-language-server");

List<String> command = new ArrayList<>();
command.add("java");
Expand Down Expand Up @@ -72,18 +75,55 @@ public EclipseLanguageServerFacade(String pathToLanguageServerPlugin, Logger log
command.add("./config_linux");
}

logger.info("Starting Eclipse JDT language server at {}", languageServerPath);
server = new ProcessBuilder(command).redirectOutput(ProcessBuilder.Redirect.PIPE)
Process server = startServer(command, languageServerPath, logger);
if (!server.isAlive()) {
if (pathToLanguageServerPlugin == null) {
// If user didn't specify language server path, we do a clean re-download.
logger
.warn("Eclipse language server failed to start. The folder may be corrupted. Try re-download.");
server = startServer(command, getLanguageServerDirectory(javaVersion, logger, true), logger);
if (!server.isAlive()) {
// if server failed to start anyway, throw with server output.
throw new RuntimeException(String.format(
"Eclipse language server failed to start, error output:\n %s", readServerOutput(server)));
}
} else {
// if user specify the language server path, we just throw with server output.
throw new RuntimeException(String.format(
"Eclipse language server failed to start, error output:\n %s", readServerOutput(server)));
}
}
this.server = server;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private String readServerOutput(Process server) throws IOException {
if (server.getInputStream() == null) {
return null;
}
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream()))) {
reader.lines().forEachOrdered(line -> stringBuilder.append(line).append("\n"));
}
return stringBuilder.toString();
}

private Process startServer(List<String> command, Path languageServerPath, Logger logger) throws Exception {
logger.info("Starting Eclipse JDT language server at {}", languageServerPath);
final Process server;
server = new ProcessBuilder(command).redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectInput(ProcessBuilder.Redirect.PIPE)
.redirectErrorStream(true)
.directory(languageServerPath.toFile())
.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
server.waitFor(1, TimeUnit.SECONDS);
return server;
}

private static Path getLanguageServerDirectory(int javaVersion, Logger logger) throws IOException {
private static Path getLanguageServerDirectory(int javaVersion, Logger logger, boolean forceReDownload)
throws IOException {
Path tmp = Paths.get(System.getProperty("java.io.tmpdir"));
Path autorestLanguageServer = tmp.resolve("autorest-java-language-server");

Expand All @@ -93,21 +133,24 @@ private static Path getLanguageServerDirectory(int javaVersion, Logger logger) t
// Eclipse JDT language server version 1.12.0 is the last version that supports Java 11, which is
// autorest.java's baseline.
downloadUrl
= URI.create(DOWNLOAD_BASE_URL + "1.12.0/jdt-language-server-1.12.0-202206011637.tar.gz").toURL();
= URI.create(DOWNLOAD_BASE_URL + "1.12.0/jdt-language-server-1.12.0-202206011637.tar.gz").toURL();
languageServerPath = autorestLanguageServer.resolve("1.12.0");
} else {
// Eclipse JDT language server version 1.39.0 is the latest version that supports Java 17.
// In the future this else statement may need to be replaced with an else if as newer versions of
// Eclipse JDT language server may baseline on Java 21 (or later).
downloadUrl
= URI.create(DOWNLOAD_BASE_URL + "1.39.0/jdt-language-server-1.39.0-202408291433.tar.gz").toURL();
= URI.create(DOWNLOAD_BASE_URL + "1.39.0/jdt-language-server-1.39.0-202408291433.tar.gz").toURL();
languageServerPath = autorestLanguageServer.resolve("1.39.0");
}

Path languageServer = languageServerPath.resolve("jdt-language-server");
if (!Files.exists(languageServerPath) || !Files.exists(languageServer)) {
Files.createDirectories(languageServerPath);

if (!Files.exists(languageServer) || forceReDownload) {
Files.createDirectories(languageServer);
Path zipPath = languageServerPath.resolve("jdt-language-server.tar.gz");
Files.deleteIfExists(zipPath);

logger.info("Downloading Eclipse JDT language server from {} to {}", downloadUrl, zipPath);
try (InputStream in = downloadUrl.openStream()) {
Files.copy(in, zipPath);
Expand All @@ -123,13 +166,15 @@ private static Path getLanguageServerDirectory(int javaVersion, Logger logger) t
private static Path unzipLanguageServer(Path zipPath) throws IOException {
try (TarInputStream tar = new TarInputStream(new GZIPInputStream(Files.newInputStream(zipPath)))) {
Path languageServerDirectory = zipPath.getParent().resolve("jdt-language-server");
Files.createDirectory(languageServerDirectory);
TarEntry entry;
while ((entry = tar.getNextEntry()) != null) {
if (entry.isDirectory()) {
Files.createDirectories(languageServerDirectory.resolve(entry.getName()));
} else {
Files.copy(tar, languageServerDirectory.resolve(entry.getName()));
Path entryPath = languageServerDirectory.resolve(entry.getName());
// In case of corrupted folder, delete before create.
Files.deleteIfExists(entryPath);
Files.copy(tar, entryPath);
}
}

Expand Down
Loading