Skip to content

Commit

Permalink
Merge pull request #7063 from sdedic/maven/lsp-priming
Browse files Browse the repository at this point in the history
Improve priming from LSP client
  • Loading branch information
sdedic committed Mar 25, 2024
2 parents dbb8050 + 301fc75 commit 13e62d7
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import java.util.concurrent.CompletionException;
import java.util.stream.Collectors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.eclipse.lsp4j.CallHierarchyRegistrationOptions;
import org.eclipse.lsp4j.CodeActionKind;
Expand Down Expand Up @@ -644,13 +645,11 @@ private void asyncOpenSelectedProjects0(CompletableFuture<Project[]> f, List<Fil
f.completeExceptionally(ex);
}
}

private void asyncOpenSelectedProjects1(CompletableFuture<Project[]> f, Project[] previouslyOpened, Project[] candidateMapping, List<Project> projects, boolean addToWorkspace) {
int id = this.openRequestId.getAndIncrement();

List<CompletableFuture> primingBuilds = new ArrayList<>();

CompletableFuture<Void>[] primeProjects(Collection<Project> projects, int id, Map<Project, CompletableFuture<Void>> local) {
List<Project> toOpen = new ArrayList<>();
Map<Project, CompletableFuture<Void>> local = new HashMap<>();
List<CompletableFuture<Void>> primingBuilds = new ArrayList<>();

synchronized (this) {
LOG.log(Level.FINER, "{0}: Opening project(s): {1}", new Object[]{ id, Arrays.asList(projects) });
for (Project p : projects) {
Expand All @@ -664,7 +663,7 @@ private void asyncOpenSelectedProjects1(CompletableFuture<Project[]> f, Project[
}
beingOpened.putAll(local);
}
long t = System.currentTimeMillis();

LOG.log(Level.FINER, id + ": Opening projects: {0}", Arrays.asList(toOpen));

// before the projects are officialy 'opened', try to prime the projects
Expand Down Expand Up @@ -698,9 +697,20 @@ public void finished(boolean success) {
pap.invokeAction(ActionProvider.COMMAND_PRIME, Lookups.fixed(progress));
}
}
return primingBuilds.toArray(new CompletableFuture[0]);
}

private void asyncOpenSelectedProjects1(CompletableFuture<Project[]> f, Project[] previouslyOpened, Project[] candidateMapping, List<Project> initialProjects, boolean addToWorkspace) {
long t = System.currentTimeMillis();
int id = this.openRequestId.getAndIncrement();
Map<Project, CompletableFuture<Void>> local = new HashMap<>();

// Wait for all priming builds, even those already pending, to finish:
CompletableFuture.allOf(primingBuilds.toArray(new CompletableFuture[0])).thenRun(() -> {
CompletableFuture[] primingBuilds = primeProjects(initialProjects, id, local);

AtomicReference<Consumer<Collection<Project>>> subprojectProcessor = new AtomicReference();
Set<Project> processedProjects = new HashSet<>();
AtomicInteger level = new AtomicInteger(1);
subprojectProcessor.set((projects) -> {
Set<Project> additionalProjects = new LinkedHashSet<>();
for (Project prj : projects) {
Set<Project> containedProjects = ProjectUtils.getContainedProjects(prj, true);
Expand All @@ -709,53 +719,75 @@ public void finished(boolean success) {
additionalProjects.addAll(containedProjects);
}
}
projects.addAll(additionalProjects);
OpenProjects.getDefault().open(projects.toArray(new Project[0]), false);
try {
LOG.log(Level.FINER, "{0}: Calling openProjects() for : {1}", new Object[]{id, Arrays.asList(projects)});
OpenProjects.getDefault().openProjects().get();
} catch (InterruptedException | ExecutionException ex) {
throw new IllegalStateException(ex);
}
for (Project prj : projects) {
//init source groups/FileOwnerQuery:
ProjectUtils.getSources(prj).getSourceGroups(Sources.TYPE_GENERIC);
final CompletableFuture<Void> prjF = local.get(prj);
if (prjF != null) {
prjF.complete(null);
additionalProjects.removeAll(processedProjects);
additionalProjects.removeAll(projects);

processedProjects.addAll(projects);

LOG.log(Level.FINE, "Processing subprojects, level {0}: {1}", new Object[] { level.getAndIncrement(), additionalProjects });

if (additionalProjects.isEmpty()) {
OpenProjects.getDefault().open(processedProjects.toArray(new Project[processedProjects.size()]), false);
try {
LOG.log(Level.FINER, "{0}: Calling openProjects() for : {1}", new Object[]{id, Arrays.asList(processedProjects)});
OpenProjects.getDefault().openProjects().get();
} catch (InterruptedException | ExecutionException ex) {
throw new IllegalStateException(ex);
}
}
Set<Project> projectSet = new HashSet<>(Arrays.asList(OpenProjects.getDefault().getOpenProjects()));
projectSet.retainAll(openedProjects);
projectSet.addAll(projects);

Project[] prjsRequested = projects.toArray(new Project[0]);
Project[] prjs = projects.toArray(new Project[0]);
LOG.log(Level.FINER, "{0}: Finished opening projects: {1}", new Object[]{id, Arrays.asList(projects)});
synchronized (this) {
openedProjects = projectSet;
if (addToWorkspace) {
Set<Project> ns = new HashSet<>(projects);
List<Project> current = Arrays.asList(workspaceProjects.getNow(new Project[0]));
int s = current.size();
ns.addAll(current);
LOG.log(Level.FINER, "Current is: {0}, ns: {1}", new Object[] { current, ns });
if (s != ns.size()) {
prjs = ns.toArray(new Project[0]);
workspaceProjects = CompletableFuture.completedFuture(prjs);
for (Project prj : processedProjects) {
//init source groups/FileOwnerQuery:
ProjectUtils.getSources(prj).getSourceGroups(Sources.TYPE_GENERIC);
final CompletableFuture<Void> prjF = local.get(prj);
if (prjF != null) {
prjF.complete(null);
}
}
for (Project p : prjs) {
// override flag in opening cache, no further questions asked.
openingFileOwners.put(p, f.thenApply(unused -> p));
Set<Project> projectSet = new HashSet<>(Arrays.asList(OpenProjects.getDefault().getOpenProjects()));
projectSet.retainAll(openedProjects);
projectSet.addAll(processedProjects);

Project[] prjsRequested = projects.toArray(new Project[processedProjects.size()]);
Project[] prjs = projects.toArray(new Project[processedProjects.size()]);
LOG.log(Level.FINER, "{0}: Finished opening projects: {1}", new Object[]{id, Arrays.asList(processedProjects)});
synchronized (this) {
openedProjects = projectSet;
if (addToWorkspace) {
Set<Project> ns = new HashSet<>(processedProjects);
List<Project> current = Arrays.asList(workspaceProjects.getNow(new Project[0]));
int s = current.size();
ns.addAll(current);
LOG.log(Level.FINER, "Current is: {0}, ns: {1}", new Object[] { current, ns });
if (s != ns.size()) {
prjs = ns.toArray(new Project[ns.size()]);
workspaceProjects = CompletableFuture.completedFuture(prjs);
}
}
for (Project p : prjs) {
// override flag in opening cache, no further questions asked.
openingFileOwners.put(p, f.thenApply(unused -> p));
}
}
f.complete(candidateMapping);
LOG.log(Level.INFO, "{0} projects opened in {1}ms", new Object[] { prjsRequested.length, (System.currentTimeMillis() - t) });
} else {
LOG.log(Level.FINER, "{0}: Collecting projects to prime from: {1}", new Object[]{id, Arrays.asList(additionalProjects)});
CompletableFuture[] nextPrimingBuilds = primeProjects(additionalProjects, id, local);
CompletableFuture.allOf(nextPrimingBuilds).thenRun(() -> {
subprojectProcessor.get().accept(additionalProjects);
}).exceptionally(e -> {
f.completeExceptionally(e);
return null;
});
}
f.complete(candidateMapping);
LOG.log(Level.INFO, "{0} projects opened in {1}ms", new Object[] { prjsRequested.length, (System.currentTimeMillis() - t) });
});

// Wait for all priming builds, even those already pending, to finish:
CompletableFuture.allOf(primingBuilds).thenRun(() -> {
subprojectProcessor.get().accept(initialProjects);
}).exceptionally(e -> {
f.completeExceptionally(e);
return null;
});
});
}

private JavaSource checkJavaSupport() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,15 @@
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.impl.VersionResolver;
import org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManagerFactory;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.eclipse.aether.util.repository.DefaultProxySelector;
import org.netbeans.modules.maven.embedder.impl.NbVersionResolver2;

/**
* Handle for the embedded Maven system, used to parse POMs and more.
Expand All @@ -123,6 +125,7 @@ public final class MavenEmbedder {
private final SettingsBuilder settingsBuilder;
private final EmbedderConfiguration embedderConfiguration;
private final SettingsDecrypter settingsDecrypter;
private final NbVersionResolver2 versionResolver;
private long settingsTimestamp;
private static final Object lastLocalRepositoryLock = new Object();
private static URI lastLocalRepository;
Expand All @@ -137,6 +140,13 @@ public final class MavenEmbedder {
this.settingsBuilder = plexus.lookup(SettingsBuilder.class);
this.populator = plexus.lookup(MavenExecutionRequestPopulator.class);
settingsDecrypter = plexus.lookup(SettingsDecrypter.class);

VersionResolver vr = plexus.lookup(VersionResolver.class);
if (vr instanceof NbVersionResolver2) {
versionResolver = (NbVersionResolver2)vr;
} else {
versionResolver = null;
}
}

public PlexusContainer getPlexus() {
Expand Down Expand Up @@ -235,7 +245,7 @@ public MavenExecutionResult readProjectWithDependencies(MavenExecutionRequest re

public MavenExecutionResult readProjectWithDependencies(MavenExecutionRequest req, boolean useWorkspaceResolution) {
if (useWorkspaceResolution) {
req.setWorkspaceReader(new NbWorkspaceReader());
req.setWorkspaceReader(new NbWorkspaceReader(versionResolver));
}
File pomFile = req.getPom();
MavenExecutionResult result = new DefaultMavenExecutionResult();
Expand All @@ -258,7 +268,7 @@ public MavenExecutionResult readProjectWithDependencies(MavenExecutionRequest re

public List<MavenExecutionResult> readProjectsWithDependencies(MavenExecutionRequest req, List<File> poms, boolean useWorkspaceResolution) {
if (useWorkspaceResolution) {
req.setWorkspaceReader(new NbWorkspaceReader());
req.setWorkspaceReader(new NbWorkspaceReader(versionResolver));
}
// File pomFile = req.getPom();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@

import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Scopes;
import org.apache.maven.model.building.ModelBuilder;
import org.apache.maven.plugin.internal.PluginDependenciesResolver;
import org.eclipse.aether.impl.ArtifactDescriptorReader;
import org.eclipse.aether.impl.VersionRangeResolver;
import org.eclipse.aether.impl.VersionResolver;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
Expand All @@ -48,6 +52,12 @@ public void configure(Binder binder) {
//exxperimental only.
// binder.bind(InheritanceAssembler.class).to(NbInheritanceAssembler.class);
binder.bind(ModelBuilder.class).to(NBModelBuilder.class);

// This allows to capture origin for version and artifact queries, so that ArtifactFixer can determine
// if a pom was really requested, or some other artifact's classifier/type was
binder.bind(VersionResolver.class).to(NbVersionResolver2.class).in(Scopes.SINGLETON);
binder.bind(VersionRangeResolver.class).to(NbVersionResolver2.class).in(Scopes.SINGLETON);
binder.bind(ArtifactDescriptorReader.class).to(NbVersionResolver2.class).in(Scopes.SINGLETON);
}

}
Loading

0 comments on commit 13e62d7

Please sign in to comment.