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

SLE-900: Speed up indexing by excluding unrelated files #729

Merged
merged 9 commits into from
Sep 2, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.reddeer.eclipse.ui.perspectives.JavaPerspective;
import org.eclipse.reddeer.swt.impl.link.DefaultLink;
import org.eclipse.reddeer.swt.impl.shell.DefaultShell;
import org.junit.After;
import org.junit.Test;
import org.sonarlint.eclipse.its.shared.AbstractSonarLintTest;

Expand All @@ -42,6 +43,18 @@ public class StandaloneSharedConnectedModeTest extends AbstractSonarLintTest {
private static final String GRADLE_SUB_PROJECT = "gradle-sub-project";
private static final String GRADLE_MAVEN_MIXED_PROJECT = "MixedProjectMavenSide";

private static final String SHELL_NAME_SONARQUBE = "SonarLint Connection Suggestion to SonarQube";
private static final String SHELL_NAME_SONARCLOUD = "SonarLint Connection Suggestion to SonarCloud";
private static final String SHELL_NAME_MULTIPLE = "SonarLint Multiple Connection Suggestions found";

// When one test fails it shouldn't let following tests fail due to the pop-ups staying.
@After
public void closeLeftoverShells() {
shellByName(SHELL_NAME_SONARQUBE).ifPresent(DefaultShell::close);
shellByName(SHELL_NAME_SONARCLOUD).ifPresent(DefaultShell::close);
shellByName(SHELL_NAME_MULTIPLE).ifPresent(DefaultShell::close);
}

@Test
public void single_project_Gradle() {
new JavaPerspective().open();
Expand Down
6 changes: 6 additions & 0 deletions org.sonarlint.eclipse.buildship/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
class="org.sonarlint.eclipse.buildship.internal.GradleProjectConfigurationExtension">
</participant>
</extension>
<extension
point="org.sonarlint.eclipse.core.projectScopeProvider">
<participant
class="org.sonarlint.eclipse.buildship.internal.GradleProjectConfigurationExtension">
</participant>
</extension>
</plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,18 @@

import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.annotation.Nullable;
import org.sonarlint.eclipse.core.resource.IProjectScopeProvider;
import org.sonarlint.eclipse.core.resource.ISonarLintProject;
import org.sonarlint.eclipse.core.resource.ISonarLintProjectHierarchyProvider;

/**
* Just like Maven integration into Eclipse (m2e) there is a hierarchical project view inside the IDE.
*/
public class GradleProjectConfigurationExtension implements ISonarLintProjectHierarchyProvider {
public class GradleProjectConfigurationExtension implements ISonarLintProjectHierarchyProvider, IProjectScopeProvider {
private final boolean isToolingApiPresent;

public GradleProjectConfigurationExtension() {
Expand All @@ -39,6 +43,7 @@ private static boolean isToolingApiPresent() {
try {
Class.forName("org.gradle.tooling.GradleConnector");
Class.forName("org.gradle.tooling.ProjectConnection");
Class.forName("org.gradle.tooling.model.eclipse.EclipseProject");
Class.forName("org.gradle.tooling.model.eclipse.HierarchicalEclipseProject");
return true;
} catch (ClassNotFoundException e) {
Expand Down Expand Up @@ -75,4 +80,12 @@ public Collection<ISonarLintProject> getSubProjects(ISonarLintProject project) {
}
return Collections.emptyList();
}

@Override
public Set<IPath> getExclusions(IProject project) {
if (isToolingApiPresent && GradleUtils.checkIfGradleProject(project)) {
return GradleUtils.getExclusions(project);
}
return Collections.emptySet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.annotation.Nullable;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProjectConnection;
import org.gradle.tooling.model.eclipse.EclipseProject;
import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
import org.sonarlint.eclipse.core.SonarLintLogger;
import org.sonarlint.eclipse.core.internal.utils.FileUtils;
Expand All @@ -44,7 +48,7 @@ private GradleUtils() {
// utility class
}

private static boolean checkIfGradleProject(IProject project) {
public static boolean checkIfGradleProject(IProject project) {
try {
return project.hasNature(GRADLE_PROJECT_NATURE);
} catch (CoreException err) {
Expand All @@ -53,6 +57,7 @@ private static boolean checkIfGradleProject(IProject project) {
return false;
}

// Move it to "model(GradleBuild.class).get().getRootProject()" at some point!
private static HierarchicalEclipseProject getRootGradleProject(HierarchicalEclipseProject project) {
var currentProject = project;
while (currentProject.getParent() != null) {
Expand Down Expand Up @@ -175,4 +180,61 @@ public static Collection<ISonarLintProject> getProjectSubProjects(ISonarLintProj

return subProjects;
}

/**
* All the exclusions that are coming from Gradle (via the Tooling API) and not from the JDT integration itself that
* is created when the project is imported.
*
* - buildSrc/build as the output directory
* - .gradle + buildSrc/.gradle as the wrapper cache
* - output directory in Eclipse, maybe a fallback
* - build directory of Gradle
* - every child projects folder relative to this project
*
* Why the trailing space for the paths? E.g. "sonar-orchestrator-junit4" starts with "sonar-orchestrator", this is
* just in case of sub-projects are named very similar to root projects.
*/
public static Set<IPath> getExclusions(IProject project) {
var exclusions = new HashSet<IPath>();

// 1) The Gradle Tooling API has no access to `buildSrc`, therefore add it manually, even if not present
exclusions.add(Path.fromOSString("/" + project.getName() + "/buildSrc/build"));
exclusions.add(Path.fromOSString("/" + project.getName() + "/buildSrc/.gradle"));

// 2) The Gradle Tooling API has no access to the wrapper, therefore add it manually, even if not present
exclusions.add(Path.fromOSString("/" + project.getName() + "/.gradle"));

try {
var connection = GradleConnector.newConnector()
.forProjectDirectory(FileUtils.toLocalFile(project))
.connect();

var gradleEclipseProject = connection.model(EclipseProject.class).get();
var projectPath = gradleEclipseProject.getProjectDirectory().toPath().toAbsolutePath().toString() + "/";

// 3) Add the output directory (this is relative to the Eclipse project, no one knows why it is inconsistent)
exclusions.add(Path.fromOSString("/" + project.getName() + "/"
+ gradleEclipseProject.getOutputLocation().getPath()));

// 4) Add the build directory (this is the absolute path on disk)
var relativeBuildDirectoryPath = gradleEclipseProject.getGradleProject()
.getBuildDirectory().getAbsolutePath()
.replace(projectPath, "/" + project.getName() + "/");
exclusions.add(Path.fromOSString(relativeBuildDirectoryPath));

// 5) For every sub-project add its project directory (these are the absolute paths on disk)
for (var child : getChildGradleProjects(gradleEclipseProject)) {
var childPath = child.getProjectDirectory().toPath().toAbsolutePath().toString();
if (childPath.startsWith(projectPath)) {
var relativePath = childPath.replace(projectPath, "/" + project.getName() + "/");
exclusions.add(Path.fromOSString(relativePath));
}
}
} catch (Exception err) {
SonarLintLogger.get().traceIdeMessage("Cannot rely on Gradle Tooling API for exclusions of project '"
+ project.getName() + "' based on Buildship!", err);
}

return exclusions;
}
}
1 change: 1 addition & 0 deletions org.sonarlint.eclipse.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Export-Package: org.sonarlint.eclipse.core,
org.sonarlint.eclipse.core.internal;x-friends:="org.sonarlint.eclipse.core.tests,org.sonarlint.eclipse.ui",
org.sonarlint.eclipse.core.internal.adapter;x-friends:="org.sonarlint.eclipse.ui",
org.sonarlint.eclipse.core.internal.backend;x-friends:="org.sonarlint.eclipse.ui,org.sonarlint.eclipse.core.tests",
org.sonarlint.eclipse.core.internal.cache;x-friends:="org.sonarlint.eclipse.ui",
org.sonarlint.eclipse.core.internal.engine;x-friends:="org.sonarlint.eclipse.ui,org.sonarlint.eclipse.core.tests",
org.sonarlint.eclipse.core.internal.engine.connected;x-friends:="org.sonarlint.eclipse.ui,org.sonarlint.eclipse.core.tests",
org.sonarlint.eclipse.core.internal.event;x-friends:="org.sonarlint.eclipse.ui",
Expand Down
3 changes: 3 additions & 0 deletions org.sonarlint.eclipse.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<extension-point id="projectHierarchyProvider"
name="SonarLint Project Hierarchy Provider"
schema="schema/projectHierarchyProvider.exsd" />
<extension-point id="projectScopeProvider"
name="SonarLint Project Scope Provider"
schema="schema/projectScopeProvider.exsd" />


<extension
Expand Down
86 changes: 86 additions & 0 deletions org.sonarlint.eclipse.core/schema/projectScopeProvider.exsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Schema file written by PDE -->
<schema targetNamespace="org.sonarlint.eclipse.core" xmlns="http://www.w3.org/2001/XMLSchema">
<annotation>
<appInfo>
<meta.schema plugin="org.sonarlint.eclipse.core" id="projectScopeProvider" name="SonarLint Project Scope Provider"/>
</appInfo>
<documentation>
Used for indexing projects and narrowing down the focus for SonarLint to improve performance and lower the memory footprint by removing irrelevant resources from the analysis.
</documentation>
</annotation>

<element name="extension">
<annotation>
<appInfo>
<meta.element />
</appInfo>
</annotation>
<complexType>
<sequence minOccurs="0" maxOccurs="unbounded">
<element ref="participant"/>
</sequence>
<attribute name="point" type="string" use="required">
<annotation>
<documentation>

</documentation>
</annotation>
</attribute>
<attribute name="id" type="string">
<annotation>
<documentation>

</documentation>
</annotation>
</attribute>
<attribute name="name" type="string">
<annotation>
<documentation>

</documentation>
<appInfo>
<meta.attribute translatable="true"/>
</appInfo>
</annotation>
</attribute>
</complexType>
</element>

<element name="participant">
<complexType>
<attribute name="class" type="string" use="required">
<annotation>
<documentation>

</documentation>
<appInfo>
<meta.attribute kind="java" basedOn=":org.sonarlint.eclipse.core.resource.IProjectScopeProvider"/>
</appInfo>
</annotation>
</attribute>
</complexType>
</element>

<annotation>
<appInfo>
<meta.section type="since"/>
</appInfo>
<documentation>
10.6
</documentation>
</annotation>

<annotation>
<appInfo>
<meta.section type="examples"/>
</appInfo>
<documentation>
Please take a look at the &quot;SonarLint for Eclipse JDT&quot; bundle for a reference implementation.
</documentation>
</annotation>




</schema>
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,13 @@ public void traceIdeMessage(String msg) {
listener.traceIdeMessage(msg);
}
}

public void traceIdeMessage(String msg, Throwable t) {
for (var listener : logListeners) {
listener.traceIdeMessage(msg);
var stack = new StringWriter();
t.printStackTrace(new PrintWriter(stack));
listener.traceIdeMessage(stack.toString());
}
}
}
Loading