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

Merge Task Server & Build Server #1512

Merged
merged 37 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9006161
merge
Jiaaming Apr 29, 2024
bbe05ef
merge server
Jiaaming Jul 1, 2024
7f79375
merge both server
Jiaaming Jul 1, 2024
bfac717
fix lint
Jiaaming Jul 1, 2024
b1e2962
fix generateRandomPipeName
Jiaaming Jul 2, 2024
42f3a9f
refine named pipe repost design
Jiaaming Jul 2, 2024
38038da
refine jdk search logic
Jiaaming Jul 3, 2024
1d916bb
update messageProxy, add error handling for task server, fix bugs
Jiaaming Jul 3, 2024
29aa9ef
fix nit
Jiaaming Jul 3, 2024
d60ff9f
fix nit
Jiaaming Jul 3, 2024
383aabc
Merge remote-tracking branch 'origin' into merge-with-named-pipe
Jiaaming Jul 4, 2024
1f8122f
fix comments
Jiaaming Jul 4, 2024
58b85f8
expand jdk search logic and fix comments
Jiaaming Jul 5, 2024
d713497
fix typo
Jiaaming Jul 5, 2024
c117618
fix typo
Jiaaming Jul 5, 2024
d0ec674
fix comments
Jiaaming Jul 5, 2024
a097a87
fix comments
Jiaaming Jul 5, 2024
5f7b4d7
Merge branch 'develop' into merge-with-named-pipe
jdneo Jul 5, 2024
c629a81
Merge branch 'develop' into merge-with-named-pipe
Jiaaming Jul 10, 2024
c201306
1. Add local build-server-for-gradle jar import and related gradle bu…
Jiaaming Jul 10, 2024
609a781
Fix: handle empty param in bspProxy repost
Jiaaming Jul 10, 2024
f582668
Fix: comments
Jiaaming Jul 11, 2024
9365ac6
Merge branch 'develop' into merge-with-named-pipe
Jiaaming Jul 11, 2024
9214957
1.Fix: comment
Jiaaming Jul 12, 2024
d7e3668
1.Add error handle for rpc connection
Jiaaming Jul 15, 2024
807f9a9
Fix: typo
Jiaaming Jul 16, 2024
9deaeb6
1.Update github workflow to feat local dependency
Jiaaming Jul 17, 2024
6f1701f
Update: lint ignore
Jiaaming Jul 17, 2024
3291eb6
fix: powermock jdk17
Jiaaming Jul 17, 2024
b2eaba7
Merge branch 'develop' into merge-with-named-pipe
jdneo Jul 19, 2024
8b5e1d9
Refine jdk search logic that start gradle-server
Jiaaming Jul 19, 2024
b3c13e3
Fix: comments
Jiaaming Jul 19, 2024
d59d605
Fix: comment
Jiaaming Jul 19, 2024
7507a3c
fix comments
Jiaaming Jul 22, 2024
7446361
Fix: comment
Jiaaming Jul 22, 2024
722406a
Fix: build.gradle file
Jiaaming Jul 22, 2024
be63f6d
Fix: extension start sequence
Jiaaming Jul 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public boolean applies(IProgressMonitor monitor) throws OperationCanceledExcepti
return false;
}

//TODO: support multi-root workspaces
if (getPreferences().getRootPaths().size() != 1) {
return false;
}

Jiaaming marked this conversation as resolved.
Show resolved Hide resolved
if (!Utils.isBuildServerEnabled(getPreferences())) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

Jiaaming marked this conversation as resolved.
Show resolved Hide resolved
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -38,8 +36,6 @@ public class ImporterPlugin extends Plugin {
*/
private DigestStore digestStore;

private static String bundleDirectory;

private static String bundleVersion = "";

@Override
Expand All @@ -52,7 +48,6 @@ public void start(BundleContext context) throws Exception {
if (!bundleFile.isPresent()) {
throw new IllegalStateException("Failed to get bundle location.");
}
bundleDirectory = bundleFile.get().getParent();
}

@Override
Expand Down Expand Up @@ -102,31 +97,18 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo
return null;
}

Jiaaming marked this conversation as resolved.
Show resolved Hide resolved
String javaExecutablePath = getJavaExecutablePath();
String[] classpaths = getBuildServerClasspath();

String pluginPath = getBuildServerPluginPath();

List<String> command = new ArrayList<>();
command.add(javaExecutablePath);
if (Boolean.parseBoolean(System.getenv("DEBUG_GRADLE_BUILD_SERVER"))) {
command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8989");
if (instance.buildServers.size() > 0) {
throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID,
"Not support multiple workspaces."));
jdneo marked this conversation as resolved.
Show resolved Hide resolved
}
command.add("--add-opens=java.base/java.lang=ALL-UNNAMED");
command.add("--add-opens=java.base/java.io=ALL-UNNAMED");
command.add("--add-opens=java.base/java.util=ALL-UNNAMED");
command.add("-Dplugin.dir=" + pluginPath);
command.add("-cp");
command.add(String.join(getClasspathSeparator(), classpaths));
command.add("com.microsoft.java.bs.core.Launcher");

ProcessBuilder build = new ProcessBuilder(command);

try {
Process process = build.start();
BuildClient client = new GradleBuildClient();
NamedPipeStream pipeStream = new NamedPipeStream();

GradleBuildClient client = new GradleBuildClient();
Launcher<BuildServerConnection> launcher = new Launcher.Builder<BuildServerConnection>()
.setOutput(process.getOutputStream())
.setInput(process.getInputStream())
.setOutput(pipeStream.getOutputStream())
.setInput(pipeStream.getInputStream())
.setLocalService(client)
.setExecutorService(Executors.newCachedThreadPool())
.setRemoteInterface(BuildServerConnection.class)
Expand All @@ -142,37 +124,4 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo
"Failed to start build server.", e));
}
}

/**
* Get the Java executable used by JDT.LS, which will be higher than JDK 17.
*/
private static String getJavaExecutablePath() {
Optional<String> command = ProcessHandle.current().info().command();
if (command.isPresent()) {
return command.get();
}

throw new IllegalStateException("Failed to get Java executable path.");
}

private static String[] getBuildServerClasspath() {
return new String[]{
Paths.get(bundleDirectory, "server.jar").toString(),
Paths.get(bundleDirectory, "runtime").toString() + File.separatorChar + "*"
};
}

private static String getBuildServerPluginPath() {
return Paths.get(bundleDirectory, "plugins").toString();
}

private static String getClasspathSeparator() {
String os = System.getProperty("os.name").toLowerCase();

if (os.contains("win")) {
return ";";
}

return ":"; // Linux or Mac
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package com.microsoft.gradle.bs.importer;

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.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;

import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.core.runtime.Platform;

/**
* A class to create a named pipe stream for the importer to communicate with the extension.
*/
public class NamedPipeStream {
Jiaaming marked this conversation as resolved.
Show resolved Hide resolved

private StreamProvider provider;

private final int MAX_ATTEMPTS = 5;
interface StreamProvider {
InputStream getInputStream() throws IOException;

OutputStream getOutputStream() throws IOException;
}

protected final class PipeStreamProvider implements StreamProvider {

private InputStream input;
private OutputStream output;

@Override
public InputStream getInputStream() throws IOException {
return input;
}

@Override
public OutputStream getOutputStream() throws IOException {
return output;
}

private void initializeNamedPipe() {
String pathName = generateRandomPipeName();
sendImporterPipeName(pathName);
File pipeFile = new File(pathName);

int attempts = 0;
// Need to retry until the pipeName was sent and pipe is created by Extension side
while (attempts < MAX_ATTEMPTS) {
try {
attemptConnection(pipeFile);
Jiaaming marked this conversation as resolved.
Show resolved Hide resolved
break;
} catch (IOException e) {
sleep(e, attempts);
attempts++;
}
}
if (attempts == MAX_ATTEMPTS) {
throw new RuntimeException("Failed to connect to the named pipe after " + MAX_ATTEMPTS + " attempts");
}
}

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);
}
}

private void sleep(IOException e, int attempts) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Thread interrupted while handling connection failure", ie);
}
}
}

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);
}
}
}

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<Integer> 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<Integer> result = winChannel.write(buffer, 0);
try {
result.get();
} catch (Exception e) {
throw new IOException(e);
}
} else {
unixChannel.write(buffer);
}
}
}
}

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();
}

protected static boolean isWindows() {
return Platform.OS_WIN32.equals(Platform.getOS());
}

private void sendImporterPipeName(String pipeName){
JavaLanguageServerPlugin.getInstance().getClientConnection()
.sendNotification("gradle.getImporterPipeName", pipeName);
}

private static String generateRandomHex(int numBytes) {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[numBytes];
random.nextBytes(bytes);
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
}

private String generateRandomPipeName() {
Jiaaming marked this conversation as resolved.
Show resolved Hide resolved
if (System.getProperty("os.name").startsWith("Windows")) {
return Paths.get("\\\\.\\pipe\\", generateRandomHex(16) + "-sock").toString();
}
String tmpDir = System.getenv("XDG_RUNTIME_DIR");
if (tmpDir == null || tmpDir.isEmpty()) {
tmpDir = System.getProperty("java.io.tmpdir");
}
int fixedLength = ".sock".length();
int safeIpcPathLengths = 103;
int availableLength = safeIpcPathLengths - fixedLength - tmpDir.length();
int randomLength = 32;
int bytesLength = Math.min(availableLength / 2, randomLength);

if (bytesLength < 16) {
throw new IllegalArgumentException("Unable to generate a random pipe name with character length less than 16");
}
return Paths.get(tmpDir, generateRandomHex(bytesLength) + ".sock").toString();
}
}
Loading