Skip to content

Commit 3657243

Browse files
authored
Support HCR for gradle build server projects (#555)
* Support HCR for gradle build server projects * Fix checkstyle violations * Fix NPE when auto build is disabled Signed-off-by: Sheng Chen <[email protected]> * Address comments * Fix checkstyle violations * Use isNotBlank() --------- Signed-off-by: Sheng Chen <[email protected]>
1 parent 83403a4 commit 3657243

File tree

3 files changed

+120
-37
lines changed

3 files changed

+120
-37
lines changed

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java

+41-37
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package com.microsoft.java.debug.plugin.internal;
1515

1616
import java.util.ArrayList;
17+
import java.util.LinkedList;
1718
import java.util.List;
1819
import java.util.logging.Logger;
1920

@@ -28,14 +29,16 @@
2829
import org.eclipse.core.resources.ResourcesPlugin;
2930
import org.eclipse.core.runtime.CoreException;
3031
import org.eclipse.core.runtime.IProgressMonitor;
31-
import org.eclipse.core.runtime.OperationCanceledException;
3232
import org.eclipse.jdt.core.IJavaProject;
3333
import org.eclipse.jdt.core.IType;
3434
import org.eclipse.jdt.ls.core.internal.BuildWorkspaceStatus;
3535
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
3636
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
3737
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
38+
import org.eclipse.jdt.ls.core.internal.handlers.BuildWorkspaceHandler;
3839
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
40+
import org.eclipse.lsp4j.TextDocumentIdentifier;
41+
import org.eclipse.lsp4j.extended.ProjectBuildParams;
3942

4043
import com.microsoft.java.debug.core.Configuration;
4144

@@ -45,20 +48,12 @@ public class Compile {
4548
private static final int GRADLE_BS_COMPILATION_ERROR = 100;
4649

4750
public static Object compile(CompileParams params, IProgressMonitor monitor) {
48-
IProject mainProject = params == null ? null : ProjectUtils.getProject(params.getProjectName());
49-
if (mainProject == null) {
50-
try {
51-
// Q: is infer project by main class name necessary? perf impact?
52-
List<IJavaProject> javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(params.getMainClass());
53-
if (javaProjects.size() == 1) {
54-
mainProject = javaProjects.get(0).getProject();
55-
}
56-
} catch (CoreException e) {
57-
JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e);
58-
}
51+
if (params == null) {
52+
throw new IllegalArgumentException("The compile parameters should not be null.");
5953
}
6054

61-
if (isBspProject(mainProject) && !ProjectUtils.isGradleProject(mainProject)) {
55+
IProject mainProject = JdtUtils.getMainProject(params.getProjectName(), params.getMainClass());
56+
if (JdtUtils.isBspProject(mainProject) && !ProjectUtils.isGradleProject(mainProject)) {
6257
// Just need to trigger a build for the target project, the Gradle build server will
6358
// handle the build dependencies for us.
6459
try {
@@ -78,20 +73,37 @@ public static Object compile(CompileParams params, IProgressMonitor monitor) {
7873
return BuildWorkspaceStatus.SUCCEED;
7974
}
8075

81-
try {
82-
if (monitor.isCanceled()) {
83-
return BuildWorkspaceStatus.CANCELLED;
84-
}
76+
if (monitor.isCanceled()) {
77+
return BuildWorkspaceStatus.CANCELLED;
78+
}
8579

86-
long compileAt = System.currentTimeMillis();
87-
if (params != null && params.isFullBuild()) {
88-
ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, monitor);
89-
ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, monitor);
90-
} else {
91-
ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
80+
ProjectBuildParams buildParams = new ProjectBuildParams();
81+
List<TextDocumentIdentifier> identifiers = new LinkedList<>();
82+
buildParams.setFullBuild(params.isFullBuild);
83+
for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
84+
if (ProjectsManager.getDefaultProject().equals(javaProject.getProject())) {
85+
continue;
9286
}
93-
logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms");
87+
// we only build project which is not a BSP project, in case that the compile request is triggered by
88+
// HCR with auto-build disabled, the build for BSP projects will be triggered by JavaHotCodeReplaceProvider.
89+
if (!JdtUtils.isBspProject(javaProject.getProject())) {
90+
identifiers.add(new TextDocumentIdentifier(javaProject.getProject().getLocationURI().toString()));
91+
}
92+
}
93+
if (identifiers.size() == 0) {
94+
return BuildWorkspaceStatus.SUCCEED;
95+
}
9496

97+
buildParams.setIdentifiers(identifiers);
98+
long compileAt = System.currentTimeMillis();
99+
BuildWorkspaceHandler buildWorkspaceHandler = new BuildWorkspaceHandler(JavaLanguageServerPlugin.getProjectsManager());
100+
BuildWorkspaceStatus status = buildWorkspaceHandler.buildProjects(buildParams, monitor);
101+
logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms");
102+
if (status == BuildWorkspaceStatus.FAILED || status == BuildWorkspaceStatus.CANCELLED) {
103+
return status;
104+
}
105+
106+
try {
95107
IResource currentResource = mainProject;
96108
if (isUnmanagedFolder(mainProject) && StringUtils.isNotBlank(params.getMainClass())) {
97109
IType mainType = ProjectUtils.getJavaProject(mainProject).findType(params.getMainClass());
@@ -135,29 +147,21 @@ public static Object compile(CompileParams params, IProgressMonitor monitor) {
135147
}
136148
}
137149

138-
if (problemMarkers.isEmpty()) {
139-
return BuildWorkspaceStatus.SUCCEED;
150+
if (!problemMarkers.isEmpty()) {
151+
return BuildWorkspaceStatus.WITH_ERROR;
140152
}
141-
142-
return BuildWorkspaceStatus.WITH_ERROR;
143153
} catch (CoreException e) {
144-
JavaLanguageServerPlugin.logException("Failed to build workspace.", e);
145-
return BuildWorkspaceStatus.FAILED;
146-
} catch (OperationCanceledException e) {
147-
return BuildWorkspaceStatus.CANCELLED;
154+
JavaLanguageServerPlugin.log(e);
148155
}
156+
157+
return BuildWorkspaceStatus.SUCCEED;
149158
}
150159

151160
private static boolean isUnmanagedFolder(IProject project) {
152161
return project != null && ProjectUtils.isUnmanagedFolder(project)
153162
&& ProjectUtils.isJavaProject(project);
154163
}
155164

156-
private static boolean isBspProject(IProject project) {
157-
return project != null && ProjectUtils.isJavaProject(project)
158-
&& ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature");
159-
}
160-
161165
private static IProject getDefaultProject() {
162166
return getWorkspaceRoot().getProject(ProjectsManager.DEFAULT_PROJECT_NAME);
163167
}

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java

+45
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,20 @@
3434
import java.util.logging.Level;
3535
import java.util.logging.Logger;
3636

37+
import org.eclipse.core.resources.IBuildConfiguration;
3738
import org.eclipse.core.resources.IFile;
3839
import org.eclipse.core.resources.IMarker;
40+
import org.eclipse.core.resources.IProject;
3941
import org.eclipse.core.resources.IResource;
4042
import org.eclipse.core.resources.IResourceChangeEvent;
4143
import org.eclipse.core.resources.IResourceChangeListener;
4244
import org.eclipse.core.resources.IResourceDelta;
4345
import org.eclipse.core.resources.IResourceDeltaVisitor;
46+
import org.eclipse.core.resources.IncrementalProjectBuilder;
4447
import org.eclipse.core.resources.ResourcesPlugin;
4548
import org.eclipse.core.runtime.CoreException;
4649
import org.eclipse.core.runtime.IPath;
50+
import org.eclipse.core.runtime.NullProgressMonitor;
4751
import org.eclipse.core.runtime.Path;
4852
import org.eclipse.jdt.core.ICompilationUnit;
4953
import org.eclipse.jdt.core.IJavaElement;
@@ -55,6 +59,7 @@
5559
import org.eclipse.jdt.core.util.ISourceAttribute;
5660
import org.eclipse.jdt.internal.core.util.Util;
5761
import org.eclipse.jdt.ls.core.internal.JobHelpers;
62+
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
5863

5964
import com.microsoft.java.debug.core.Configuration;
6065
import com.microsoft.java.debug.core.DebugException;
@@ -63,6 +68,7 @@
6368
import com.microsoft.java.debug.core.IDebugSession;
6469
import com.microsoft.java.debug.core.StackFrameUtility;
6570
import com.microsoft.java.debug.core.adapter.AdapterUtils;
71+
import com.microsoft.java.debug.core.adapter.Constants;
6672
import com.microsoft.java.debug.core.adapter.ErrorCode;
6773
import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent;
6874
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
@@ -104,6 +110,8 @@ public class JavaHotCodeReplaceProvider implements IHotCodeReplaceProvider, IRes
104110

105111
private List<String> deltaClassNames = new ArrayList<>();
106112

113+
private String mainProjectName = "";
114+
107115
/**
108116
* Visitor for resource deltas.
109117
*/
@@ -269,6 +277,7 @@ public void initialize(IDebugAdapterContext context, Map<String, Object> options
269277
}
270278
this.context = context;
271279
currentDebugSession = context.getDebugSession();
280+
this.mainProjectName = ((String) options.get(Constants.PROJECT_NAME));
272281
}
273282

274283
@Override
@@ -319,6 +328,7 @@ public void onClassRedefined(Consumer<List<String>> consumer) {
319328

320329
@Override
321330
public CompletableFuture<List<String>> redefineClasses() {
331+
triggerBuildForBspProject();
322332
JobHelpers.waitForBuildJobs(10 * 1000);
323333
return CompletableFuture.supplyAsync(() -> {
324334
List<String> classNames = new ArrayList<>();
@@ -737,4 +747,39 @@ private List<StackFrame> getStackFrames(ThreadReference thread, boolean refresh)
737747
}
738748
});
739749
}
750+
751+
/**
752+
* Trigger build separately if the main project is a BSP project.
753+
* This is because auto build for BSP project will not update the class files to disk.
754+
*/
755+
private void triggerBuildForBspProject() {
756+
// check if the workspace contains BSP project first. This is for performance consideration.
757+
// Due to that getJavaProjectFromType() is a heavy operation.
758+
if (!containsBspProjects()) {
759+
return;
760+
}
761+
762+
IProject mainProject = JdtUtils.getMainProject(this.mainProjectName, context.getMainClass());
763+
if (mainProject != null && JdtUtils.isBspProject(mainProject)) {
764+
try {
765+
ResourcesPlugin.getWorkspace().build(
766+
new IBuildConfiguration[]{mainProject.getActiveBuildConfig()},
767+
IncrementalProjectBuilder.INCREMENTAL_BUILD,
768+
false /*buildReference*/,
769+
new NullProgressMonitor()
770+
);
771+
} catch (CoreException e) {
772+
// ignore compilation errors
773+
}
774+
}
775+
}
776+
777+
private boolean containsBspProjects() {
778+
for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
779+
if (JdtUtils.isBspProject(javaProject.getProject())) {
780+
return true;
781+
}
782+
}
783+
return false;
784+
}
740785
}

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java

+34
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.eclipse.jdt.launching.JavaRuntime;
4141
import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer;
4242
import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer;
43+
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
44+
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
4345

4446
import com.microsoft.java.debug.core.DebugException;
4547
import com.microsoft.java.debug.core.StackFrameUtility;
@@ -415,4 +417,36 @@ public static boolean isSameFile(IResource resource1, IResource resource2) {
415417

416418
return Objects.equals(resource1.getLocation(), resource2.getLocation());
417419
}
420+
421+
/**
422+
* Check if the project is managed by Gradle Build Server.
423+
*/
424+
public static boolean isBspProject(IProject project) {
425+
return project != null && ProjectUtils.isJavaProject(project)
426+
&& ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature");
427+
}
428+
429+
/**
430+
* Get main project according to the main project name or main class name,
431+
* or return <code>null</code> if the main project cannot be resolved.
432+
*/
433+
public static IProject getMainProject(String mainProjectName, String mainClassName) {
434+
IProject mainProject = null;
435+
if (StringUtils.isNotBlank(mainProjectName)) {
436+
mainProject = ProjectUtils.getProject(mainProjectName);
437+
}
438+
439+
if (mainProject == null && StringUtils.isNotBlank(mainClassName)) {
440+
try {
441+
List<IJavaProject> javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(mainClassName);
442+
if (javaProjects.size() == 1) {
443+
mainProject = javaProjects.get(0).getProject();
444+
}
445+
} catch (CoreException e) {
446+
JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e);
447+
}
448+
}
449+
450+
return mainProject;
451+
}
418452
}

0 commit comments

Comments
 (0)