Skip to content

Commit

Permalink
Support HCR for gradle build server projects (#555)
Browse files Browse the repository at this point in the history
* Support HCR for gradle build server projects

* Fix checkstyle violations

* Fix NPE when auto build is disabled

Signed-off-by: Sheng Chen <sheche@microsoft.com>

* Address comments

* Fix checkstyle violations

* Use isNotBlank()

---------

Signed-off-by: Sheng Chen <sheche@microsoft.com>
  • Loading branch information
jdneo authored Jun 14, 2024
1 parent 83403a4 commit 3657243
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package com.microsoft.java.debug.plugin.internal;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

Expand All @@ -28,14 +29,16 @@
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.ls.core.internal.BuildWorkspaceStatus;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.handlers.BuildWorkspaceHandler;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.extended.ProjectBuildParams;

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

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

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

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

try {
if (monitor.isCanceled()) {
return BuildWorkspaceStatus.CANCELLED;
}
if (monitor.isCanceled()) {
return BuildWorkspaceStatus.CANCELLED;
}

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

buildParams.setIdentifiers(identifiers);
long compileAt = System.currentTimeMillis();
BuildWorkspaceHandler buildWorkspaceHandler = new BuildWorkspaceHandler(JavaLanguageServerPlugin.getProjectsManager());
BuildWorkspaceStatus status = buildWorkspaceHandler.buildProjects(buildParams, monitor);
logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms");
if (status == BuildWorkspaceStatus.FAILED || status == BuildWorkspaceStatus.CANCELLED) {
return status;
}

try {
IResource currentResource = mainProject;
if (isUnmanagedFolder(mainProject) && StringUtils.isNotBlank(params.getMainClass())) {
IType mainType = ProjectUtils.getJavaProject(mainProject).findType(params.getMainClass());
Expand Down Expand Up @@ -135,29 +147,21 @@ public static Object compile(CompileParams params, IProgressMonitor monitor) {
}
}

if (problemMarkers.isEmpty()) {
return BuildWorkspaceStatus.SUCCEED;
if (!problemMarkers.isEmpty()) {
return BuildWorkspaceStatus.WITH_ERROR;
}

return BuildWorkspaceStatus.WITH_ERROR;
} catch (CoreException e) {
JavaLanguageServerPlugin.logException("Failed to build workspace.", e);
return BuildWorkspaceStatus.FAILED;
} catch (OperationCanceledException e) {
return BuildWorkspaceStatus.CANCELLED;
JavaLanguageServerPlugin.log(e);
}

return BuildWorkspaceStatus.SUCCEED;
}

private static boolean isUnmanagedFolder(IProject project) {
return project != null && ProjectUtils.isUnmanagedFolder(project)
&& ProjectUtils.isJavaProject(project);
}

private static boolean isBspProject(IProject project) {
return project != null && ProjectUtils.isJavaProject(project)
&& ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature");
}

private static IProject getDefaultProject() {
return getWorkspaceRoot().getProject(ProjectsManager.DEFAULT_PROJECT_NAME);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,20 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
Expand All @@ -55,6 +59,7 @@
import org.eclipse.jdt.core.util.ISourceAttribute;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jdt.ls.core.internal.JobHelpers;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;

import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
Expand All @@ -63,6 +68,7 @@
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.StackFrameUtility;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.Constants;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
Expand Down Expand Up @@ -104,6 +110,8 @@ public class JavaHotCodeReplaceProvider implements IHotCodeReplaceProvider, IRes

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

private String mainProjectName = "";

/**
* Visitor for resource deltas.
*/
Expand Down Expand Up @@ -269,6 +277,7 @@ public void initialize(IDebugAdapterContext context, Map<String, Object> options
}
this.context = context;
currentDebugSession = context.getDebugSession();
this.mainProjectName = ((String) options.get(Constants.PROJECT_NAME));
}

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

@Override
public CompletableFuture<List<String>> redefineClasses() {
triggerBuildForBspProject();
JobHelpers.waitForBuildJobs(10 * 1000);
return CompletableFuture.supplyAsync(() -> {
List<String> classNames = new ArrayList<>();
Expand Down Expand Up @@ -737,4 +747,39 @@ private List<StackFrame> getStackFrames(ThreadReference thread, boolean refresh)
}
});
}

/**
* Trigger build separately if the main project is a BSP project.
* This is because auto build for BSP project will not update the class files to disk.
*/
private void triggerBuildForBspProject() {
// check if the workspace contains BSP project first. This is for performance consideration.
// Due to that getJavaProjectFromType() is a heavy operation.
if (!containsBspProjects()) {
return;
}

IProject mainProject = JdtUtils.getMainProject(this.mainProjectName, context.getMainClass());
if (mainProject != null && JdtUtils.isBspProject(mainProject)) {
try {
ResourcesPlugin.getWorkspace().build(
new IBuildConfiguration[]{mainProject.getActiveBuildConfig()},
IncrementalProjectBuilder.INCREMENTAL_BUILD,
false /*buildReference*/,
new NullProgressMonitor()
);
} catch (CoreException e) {
// ignore compilation errors
}
}
}

private boolean containsBspProjects() {
for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
if (JdtUtils.isBspProject(javaProject.getProject())) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer;
import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;

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

return Objects.equals(resource1.getLocation(), resource2.getLocation());
}

/**
* Check if the project is managed by Gradle Build Server.
*/
public static boolean isBspProject(IProject project) {
return project != null && ProjectUtils.isJavaProject(project)
&& ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature");
}

/**
* Get main project according to the main project name or main class name,
* or return <code>null</code> if the main project cannot be resolved.
*/
public static IProject getMainProject(String mainProjectName, String mainClassName) {
IProject mainProject = null;
if (StringUtils.isNotBlank(mainProjectName)) {
mainProject = ProjectUtils.getProject(mainProjectName);
}

if (mainProject == null && StringUtils.isNotBlank(mainClassName)) {
try {
List<IJavaProject> javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(mainClassName);
if (javaProjects.size() == 1) {
mainProject = javaProjects.get(0).getProject();
}
} catch (CoreException e) {
JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e);
}
}

return mainProject;
}
}

0 comments on commit 3657243

Please # to comment.