Skip to content

Commit 2446de8

Browse files
committed
Added gradle support
1 parent c17d2b7 commit 2446de8

File tree

5 files changed

+228
-134
lines changed

5 files changed

+228
-134
lines changed

server/src/main/kotlin/org/javacs/kt/SourcePath.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,18 @@ class SourcePath(
269269
*/
270270
fun save(uri: URI) {
271271
files[uri]?.let {
272-
cp.compiler.removeGeneratedCode(listOfNotNull(it.lastSavedFile))
273-
it.compiledContainer?.let { container ->
274-
it.compiledContext?.let { context ->
275-
cp.compiler.generateCode(container, context, listOfNotNull(it.compiledFile))
276-
it.lastSavedFile = it.compiledFile
272+
if (!it.isScript) {
273+
// If the code generation fails for some reason, we generate code for the other files anyway
274+
try {
275+
cp.compiler.removeGeneratedCode(listOfNotNull(it.lastSavedFile))
276+
it.compiledContainer?.let { container ->
277+
it.compiledContext?.let { context ->
278+
cp.compiler.generateCode(container, context, listOfNotNull(it.compiledFile))
279+
it.lastSavedFile = it.compiledFile
280+
}
281+
}
282+
} catch (ex: Exception) {
283+
LOG.printStackTrace(ex)
277284
}
278285
}
279286
}

vscode-java-kotlin/jdt-ls-extension/org.javacs.kt.jdt.ls.extension/plugin.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
id="importers"
66
point="org.eclipse.jdt.ls.core.importers">
77
<importer
8-
id = "kotlinProjectImporter"
8+
id = "kotlinGradleImporter"
9+
order = "125"
10+
class = "org.javacs.kt.jdt.ls.extension.KotlinGradleImporter"/>
11+
<importer
12+
id = "kotlinMavenImporter"
913
order = "150"
10-
class = "org.javacs.kt.jdt.ls.extension.KotlinMavenImporter"/>
14+
class = "org.javacs.kt.jdt.ls.extension.KotlinMavenImporter"/>
1115
</extension>
1216
</plugin>
1317

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.javacs.kt.jdt.ls.extension;
2+
3+
import java.nio.file.Paths;
4+
5+
import org.eclipse.core.resources.IProject;
6+
import org.eclipse.core.runtime.CoreException;
7+
import org.eclipse.core.runtime.IProgressMonitor;
8+
import org.eclipse.core.runtime.Status;
9+
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
10+
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
11+
import org.eclipse.jdt.ls.core.internal.managers.GradleBuildSupport;
12+
import org.eclipse.jdt.ls.core.internal.managers.GradleProjectImporter;
13+
14+
/**
15+
* Custom importer for kotlin gradle projects.
16+
* This relies on the JDT LS GradleProjectImporter to actually import the base project.
17+
* It then updates the classpath to include the kotlin compiled classes.
18+
*/
19+
@SuppressWarnings("restriction")
20+
public class KotlinGradleImporter extends GradleProjectImporter {
21+
22+
/**
23+
* Checks if this is a gradle kotlin project.
24+
* For now, we rely on GradleProjectImporter.applies to check if this is a gradle project.
25+
* On top of that, we check if we have any kotlin files in the workspace.
26+
* If we do, we assume this is a kotlin gradle project.
27+
* TODO: We should make this more robust in the future.
28+
*/
29+
@Override
30+
public boolean applies(IProgressMonitor monitor) throws CoreException {
31+
return super.applies(monitor) && KotlinImporterUtils.anyKotlinFiles(rootFolder.toPath());
32+
}
33+
34+
@Override
35+
public void reset() {
36+
super.reset();
37+
}
38+
39+
/**
40+
* This imports the project. It relies on the GradleProjectImporter.importToWorkspace to do most of the work.
41+
* Once the base gradle project is imported, we add the kls folder to the classpath as a library.
42+
* The kls folder is the folder used by the kotlin language server to output classfiles.
43+
*/
44+
@Override
45+
public void importToWorkspace(IProgressMonitor monitor) throws CoreException {
46+
super.importToWorkspace(monitor);
47+
48+
for (IProject project : ProjectUtils.getGradleProjects()) {
49+
java.nio.file.Path path = Paths.get(project.getLocation().toOSString(), "kls");
50+
KotlinImporterUtils.setKlsClasspathEntry(project, path, monitor, new GradleBuildSupport());
51+
KotlinImporterUtils.registerKlsWatcher(path, subMonitor -> {
52+
try {
53+
for (IProject gradleProject : ProjectUtils.getGradleProjects()) {
54+
KotlinImporterUtils.setKlsClasspathEntry(gradleProject, path, subMonitor, new GradleBuildSupport());
55+
}
56+
57+
return Status.OK_STATUS;
58+
} catch (CoreException ex) {
59+
JavaLanguageServerPlugin.logException(ex);
60+
return Status.error("An error occurred while updating the kotlin classpath entry");
61+
}
62+
});
63+
}
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package org.javacs.kt.jdt.ls.extension;
2+
3+
import java.io.IOException;
4+
import java.nio.file.FileSystems;
5+
import java.nio.file.FileVisitResult;
6+
import java.nio.file.Files;
7+
import java.nio.file.LinkOption;
8+
import java.nio.file.SimpleFileVisitor;
9+
import java.nio.file.StandardWatchEventKinds;
10+
import java.nio.file.WatchEvent;
11+
import java.nio.file.WatchKey;
12+
import java.nio.file.WatchService;
13+
import java.nio.file.attribute.BasicFileAttributes;
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.function.Function;
20+
import java.util.stream.Stream;
21+
22+
import org.eclipse.core.resources.IProject;
23+
import org.eclipse.core.runtime.CoreException;
24+
import org.eclipse.core.runtime.IPath;
25+
import org.eclipse.core.runtime.IProgressMonitor;
26+
import org.eclipse.core.runtime.IStatus;
27+
import org.eclipse.core.runtime.Path;
28+
import org.eclipse.core.runtime.jobs.Job;
29+
import org.eclipse.jdt.core.IClasspathEntry;
30+
import org.eclipse.jdt.core.IJavaProject;
31+
import org.eclipse.jdt.core.JavaCore;
32+
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
33+
import org.eclipse.jdt.ls.core.internal.managers.IBuildSupport;
34+
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;
35+
36+
@SuppressWarnings("restriction")
37+
public class KotlinImporterUtils {
38+
39+
/**
40+
* Checks if any kotlin files exist in the given directory and subdirectories.
41+
*/
42+
public static boolean anyKotlinFiles(java.nio.file.Path directory) {
43+
try (Stream<java.nio.file.Path> files = Files.walk(directory)) {
44+
return files.anyMatch(f -> f.getFileName().toString().endsWith(".kt"));
45+
} catch (IOException ex) {
46+
return false;
47+
}
48+
}
49+
50+
public static void setKlsClasspathEntry(IProject project, java.nio.file.Path path, IProgressMonitor monitor, IBuildSupport buildSupport) throws CoreException {
51+
IJavaProject javaProject = JavaCore.create(project);
52+
53+
List<IClasspathEntry> entries = new ArrayList<>(Arrays.asList(javaProject.getRawClasspath()));
54+
IPath klsPath = Path.fromOSString(path.toString());
55+
56+
entries.removeIf(entry -> entry.getPath().equals(klsPath));
57+
IClasspathEntry classpathEntry = JavaCore.newLibraryEntry(klsPath, null, null);
58+
entries.add(classpathEntry);
59+
60+
javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[0]), monitor);
61+
buildSupport.refresh(project, CHANGE_TYPE.CHANGED, monitor);
62+
}
63+
64+
/**
65+
* Registers a watcher for the kls directory.
66+
* Any file change leads to an update in the classpath.
67+
* The classpath update is done using a function send as parameter, since it's different for each importer.
68+
*/
69+
public static void registerKlsWatcher(java.nio.file.Path path, Function<IProgressMonitor, IStatus> updateClasspathJob) {
70+
new Thread(() -> {
71+
try {
72+
Files.createDirectories(path);
73+
Map<WatchKey, java.nio.file.Path> keys = new HashMap<>();
74+
// Create the watcher and watch all directories under the base kls directory.
75+
WatchService watcher = FileSystems.getDefault().newWatchService();
76+
keys.putAll(watchDirectory(watcher, path));
77+
78+
// Wair for file related events.
79+
// When a file is added or deleted, we update the classpath (we remove and re-add the kls classpath entry)
80+
while (true) {
81+
WatchKey key;
82+
if ((key = watcher.take()) != null) {
83+
java.nio.file.Path dir = keys.get(key);
84+
if (dir != null) {
85+
key.pollEvents().forEach(event -> {
86+
@SuppressWarnings("unchecked")
87+
WatchEvent<java.nio.file.Path> ev = (WatchEvent<java.nio.file.Path>) event;
88+
89+
java.nio.file.Path name = ev.context();
90+
java.nio.file.Path child = dir.resolve(name);
91+
92+
// If a new directory was created, we need to watch it as well.
93+
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
94+
if (Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) {
95+
keys.putAll(watchDirectory(watcher, dir));
96+
}
97+
}
98+
});
99+
Job job = new Job("Updating kotlin classpath entry") {
100+
@Override
101+
protected IStatus run(IProgressMonitor monitor) {
102+
return updateClasspathJob.apply(monitor);
103+
}
104+
};
105+
job.schedule();
106+
job.join();
107+
key.reset();
108+
}
109+
}
110+
}
111+
} catch (IOException | InterruptedException ex) {
112+
JavaLanguageServerPlugin.logException(ex);
113+
}
114+
}).start();
115+
}
116+
117+
private static Map<WatchKey, java.nio.file.Path> watchDirectory(WatchService watcher, java.nio.file.Path path) {
118+
Map<WatchKey, java.nio.file.Path> keys = new HashMap<>();
119+
120+
try {
121+
Files.walkFileTree(path, new SimpleFileVisitor<java.nio.file.Path>() {
122+
@Override
123+
public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs) throws IOException {
124+
WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
125+
keys.put(key, dir);
126+
return FileVisitResult.CONTINUE;
127+
}
128+
});
129+
} catch (IOException ex) {
130+
JavaLanguageServerPlugin.logException(ex);
131+
}
132+
133+
return keys;
134+
}
135+
}

0 commit comments

Comments
 (0)