[maven] IDEA-359760 handle transitive dependencies in Maven incremental sync

GitOrigin-RevId: 83fd261e83cfe692ab4b4249d960bb650bc3cf5b
This commit is contained in:
Dmitry Kichinsky
2024-09-27 21:25:00 +02:00
committed by intellij-monorepo-bot
parent 3286019377
commit 184410038c
7 changed files with 91 additions and 50 deletions

View File

@@ -6,18 +6,36 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
public class PomHashMap implements Serializable {
@NotNull
private final Map<@NotNull File, @NotNull PomHashValue> pomMap = new HashMap<>();
@NotNull
private final Map<@NotNull File, @NotNull HashSet<@NotNull File>> pomDependencies = new HashMap<>();
public void put(@NotNull File pom, @Nullable String dependencyHash) {
pomMap.put(pom, new PomHashValue(dependencyHash));
}
public void addFileDependency(@NotNull File pom, @NotNull File dependentPom) {
pomDependencies.putIfAbsent(pom, new HashSet<>());
pomDependencies.get(pom).add(dependentPom);
}
public void addFileDependencies(@NotNull File pom, @NotNull Collection<@NotNull File> dependentPoms) {
for (File dependentPom : dependentPoms) {
addFileDependency(pom, dependentPom);
}
}
@NotNull
public Set<@NotNull File> getFileDependencies(@NotNull File pom) {
Set<@NotNull File> dependencies = pomDependencies.get(pom);
return dependencies == null ? Collections.emptySet() : dependencies;
}
@NotNull
public Set<@NotNull File> keySet() {
return pomMap.keySet();

View File

@@ -35,8 +35,6 @@ import org.jetbrains.idea.maven.server.*;
import org.jetbrains.idea.maven.server.embedder.CustomMaven3ModelInterpolator2;
import java.io.File;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -178,7 +176,7 @@ public class Maven3XProjectResolver {
}
String newDependencyHash = fileToNewDependencyHash.get(pomFile);
if (!dependenciesChanged(pomFile, newDependencyHash, fileToNewDependencyHash)) {
if (!transitiveDependenciesChanged(pomFile, newDependencyHash, fileToNewDependencyHash)) {
executionResults.add(createExecutionResult(project, newDependencyHash));
continue;
}
@@ -215,11 +213,17 @@ public class Maven3XProjectResolver {
return executionResults;
}
private boolean dependenciesChanged(@NotNull File pomFile, String newDependencyHash, Map<File, String> fileToNewDependencyHash) {
private boolean transitiveDependenciesChanged(@NotNull File pomFile, String newDependencyHash, Map<File, String> fileToNewDependencyHash) {
if (dependenciesChanged(pomFile, newDependencyHash)) return true;
for (File dependencyPomFile : myPomHashMap.getFileDependencies(pomFile)) {
if (dependenciesChanged(dependencyPomFile, fileToNewDependencyHash.get(dependencyPomFile))) return true;
}
return false;
}
private boolean dependenciesChanged(@NotNull File pomFile, String newDependencyHash) {
String previousDependencyHash = myPomHashMap.getDependencyHash(pomFile);
if (null == previousDependencyHash) return true;
if (previousDependencyHash.equals(newDependencyHash)) return false;
return true;
return previousDependencyHash == null || !previousDependencyHash.equals(newDependencyHash);
}
@NotNull

View File

@@ -30,8 +30,6 @@ import org.jetbrains.idea.maven.server.MavenServerGlobals;
import org.jetbrains.idea.maven.server.PomHashMap;
import java.io.File;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -159,7 +157,7 @@ public class Maven40ProjectResolver {
}
String newDependencyHash = fileToNewDependencyHash.get(pomFile);
if (!dependenciesChanged(pomFile, newDependencyHash, fileToNewDependencyHash)) {
if (!transitiveDependenciesChanged(pomFile, newDependencyHash, fileToNewDependencyHash)) {
executionResults.add(createExecutionResult(project, newDependencyHash));
continue;
}
@@ -196,11 +194,17 @@ public class Maven40ProjectResolver {
return executionResults;
}
private boolean dependenciesChanged(@NotNull File pomFile, String newDependencyHash, Map<File, String> fileToNewDependencyHash) {
private boolean transitiveDependenciesChanged(@NotNull File pomFile, String newDependencyHash, Map<File, String> fileToNewDependencyHash) {
if (dependenciesChanged(pomFile, newDependencyHash)) return true;
for (File dependencyPomFile : myPomHashMap.getFileDependencies(pomFile)) {
if (dependenciesChanged(dependencyPomFile, fileToNewDependencyHash.get(dependencyPomFile))) return true;
}
return false;
}
private boolean dependenciesChanged(@NotNull File pomFile, String newDependencyHash) {
String previousDependencyHash = myPomHashMap.getDependencyHash(pomFile);
if (null == previousDependencyHash) return true;
if (previousDependencyHash.equals(newDependencyHash)) return false;
return true;
return previousDependencyHash == null || !previousDependencyHash.equals(newDependencyHash);
}
@NotNull

View File

@@ -29,6 +29,7 @@ import org.jetbrains.idea.maven.server.*
import org.jetbrains.idea.maven.telemetry.tracer
import org.jetbrains.idea.maven.utils.MavenLog
import org.jetbrains.idea.maven.utils.MavenUtil
import java.io.File
import java.lang.reflect.InvocationTargetException
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
@@ -82,7 +83,7 @@ class MavenProjectResolver(private val myProject: Project) {
eventHandler: MavenEventHandler): MavenProjectResolutionResult {
val updateSnapshots = MavenProjectsManager.getInstance(myProject).forceUpdateSnapshots || generalSettings.isAlwaysUpdateSnapshots
val projectsWithUnresolvedPlugins = HashMap<String, Collection<MavenProject>>()
val fileToDependencyHash = tree.projects.associate { it.file to if (incrementally) it.dependencyHash else null }
val pomToDependencyHash = tree.projects.associate { it.file to if (incrementally) it.dependencyHash else null }
val projectMultiMap = MavenUtil.groupByBasedir(mavenProjects, tree)
for ((baseDir, mavenProjectsInBaseDir) in projectMultiMap.entrySet()) {
val embedder = embeddersManager.getEmbedder(MavenEmbeddersManager.FOR_DEPENDENCIES_RESOLVE, baseDir)
@@ -96,7 +97,7 @@ class MavenProjectResolver(private val myProject: Project) {
}
val projectsWithUnresolvedPluginsChunk = withContext(tracer.span("doResolve $baseDir")) {
doResolve(
fileToDependencyHash,
pomToDependencyHash,
mavenProjectsInBaseDir,
tree,
generalSettings,
@@ -132,13 +133,13 @@ class MavenProjectResolver(private val myProject: Project) {
MavenUtil.restartConfigHighlighting(mavenProjects)
if (incrementally && updateSnapshots) {
updateSnapshotsAfterIncrementalSync(tree, fileToDependencyHash, embeddersManager, progressReporter, eventHandler)
updateSnapshotsAfterIncrementalSync(tree, pomToDependencyHash, embeddersManager, progressReporter, eventHandler)
}
return MavenProjectResolutionResult(projectsWithUnresolvedPlugins)
}
private suspend fun doResolve(
fileToDependencyHash: Map<VirtualFile, String?>,
pomToDependencyHash: Map<VirtualFile, String?>,
mavenProjects: Collection<MavenProject>,
tree: MavenProjectsTree,
generalSettings: MavenGeneralSettings,
@@ -156,9 +157,14 @@ class MavenProjectResolver(private val myProject: Project) {
val text = StringUtil.shortenPathWithEllipsis(StringUtil.join(names, ", "), 200)
progressReporter.text(MavenProjectBundle.message("maven.resolving.pom", text))
val explicitProfiles = tree.explicitProfiles
val projects = tree.projects
val pomDependencies = projects
.associate { it.file to it.dependencies.filter { it.file.path.endsWith(MavenConstants.POM_XML) }.map { it.file }.toSet() }
.filterValues { it.isNotEmpty() }
val resultsAndProblems = resolveProjectsInEmbedder(
embedder,
fileToDependencyHash,
pomToDependencyHash,
pomDependencies,
explicitProfiles,
progressReporter,
eventHandler,
@@ -309,21 +315,25 @@ class MavenProjectResolver(private val myProject: Project) {
}
}
private suspend fun resolveProjectsInEmbedder(embedder: MavenEmbedderWrapper,
fileToDependencyHash: Map<VirtualFile, String?>,
explicitProfiles: MavenExplicitProfiles,
progressReporter: RawProgressReporter,
eventHandler: MavenEventHandler,
workspaceMap: MavenWorkspaceMap?,
updateSnapshots: Boolean,
userProperties: Properties): Pair<Collection<MavenProjectResolverResult>, Collection<MavenProjectProblem>> {
val files = fileToDependencyHash.keys
private suspend fun resolveProjectsInEmbedder(
embedder: MavenEmbedderWrapper,
pomToDependencyHash: Map<VirtualFile, String?>,
pomDependencies: Map<VirtualFile, Set<File>>,
explicitProfiles: MavenExplicitProfiles,
progressReporter: RawProgressReporter,
eventHandler: MavenEventHandler,
workspaceMap: MavenWorkspaceMap?,
updateSnapshots: Boolean,
userProperties: Properties
): Pair<Collection<MavenProjectResolverResult>, Collection<MavenProjectProblem>> {
val files = pomToDependencyHash.keys
val resolverResults: MutableCollection<MavenProjectResolverResult> = ArrayList()
val readingProblems = mutableListOf<MavenProjectProblem>()
try {
val executionResults = withContext(tracer.span("embedder.resolveProject")) {
embedder.resolveProject(
fileToDependencyHash,
pomToDependencyHash,
pomDependencies,
explicitProfiles,
progressReporter,
eventHandler,
@@ -456,6 +466,7 @@ class MavenProjectResolver(private val myProject: Project) {
resolveProjectsInEmbedder(
embedder,
files.associateWith { null },
mapOf(),
explicitProfiles,
object : RawProgressReporter {},
MavenLogEventHandler,

View File

@@ -53,23 +53,32 @@ abstract class MavenEmbedderWrapper internal constructor(private val project: Pr
}
}
suspend fun resolveProject(fileToDependencyHash: Map<VirtualFile, String?>,
explicitProfiles: MavenExplicitProfiles,
progressReporter: RawProgressReporter,
eventHandler: MavenEventHandler,
workspaceMap: MavenWorkspaceMap?,
updateSnapshots: Boolean,
userProperties: Properties): Collection<MavenServerExecutionResult> {
val transformer = if (fileToDependencyHash.isEmpty()) RemotePathTransformerFactory.Transformer.ID
suspend fun resolveProject(
pomToDependencyHash: Map<VirtualFile, String?>,
pomDependencies: Map<VirtualFile, Set<File>>,
explicitProfiles: MavenExplicitProfiles,
progressReporter: RawProgressReporter,
eventHandler: MavenEventHandler,
workspaceMap: MavenWorkspaceMap?,
updateSnapshots: Boolean,
userProperties: Properties,
): Collection<MavenServerExecutionResult> {
val transformer = if (pomToDependencyHash.isEmpty()) RemotePathTransformerFactory.Transformer.ID
else RemotePathTransformerFactory.createForProject(project)
val pomHashMap = PomHashMap()
fileToDependencyHash.mapNotNull { (file, checkSum) ->
pomToDependencyHash.mapNotNull { (file, checkSum) ->
transformer.toRemotePath(file.getPath())?.let {
pomHashMap.put(File(it), checkSum)
}
}
pomDependencies.map { (file, dependencies) ->
transformer.toRemotePath(file.getPath())?.let {
pomHashMap.addFileDependencies(File(it), dependencies.mapNotNull { transformer.toRemotePath(it.path) }.map { File(it) })
}
}
val serverWorkspaceMap = convertWorkspaceMap(workspaceMap)
val request = ProjectResolutionRequest(

View File

@@ -70,6 +70,7 @@ class MavenConnectorApiTest : MavenMultiVersionImportingTestCase() {
map.register(MavenId("test:m2:1"), m2.toNioPath().toFile())
map.register(MavenId("test:project:1"), project.toNioPath().toFile())
val executionResults = embedder.resolveProject(mapOf(project to null, m1 to null, m2 to null),
mapOf(),
MavenExplicitProfiles(listOf("test"), emptyList()),
MockReporter(),
MavenLogEventHandler,

View File

@@ -32,7 +32,7 @@ class MavenProjectsManagerAutoImportTest : MavenMultiVersionImportingTestCase()
fun testResolvingEnvVariableInRepositoryPath() = runBlocking {
val temp = System.getenv(envVar)
waitForImportWithinTimeout {
updateSettingsXml("<localRepository>\${env." + envVar + "}/tmpRepo</localRepository>")
updateSettingsXml("<localRepository>\${env.$envVar}/tmpRepo</localRepository>")
}
val repo = File("$temp/tmpRepo").getCanonicalFile()
assertEquals(repo.path, mavenGeneralSettings.getEffectiveLocalRepository().path)
@@ -444,10 +444,7 @@ class MavenProjectsManagerAutoImportTest : MavenMultiVersionImportingTestCase()
scheduleProjectImportAndWait()
assertModuleModuleDeps("m1", "m2")
// relying on transitive dependencies is not a good practice
// transitive dependency updating is not fully supported by incremental sync
// run full sync to pick up transitive dependency
updateAllProjectsFullSync()
updateAllProjects()
assertModuleLibDeps("m1", "Maven: junit:junit:4.0")
}
@@ -533,10 +530,7 @@ class MavenProjectsManagerAutoImportTest : MavenMultiVersionImportingTestCase()
assertModules("project", "m1")
assertModuleModuleDeps("m1")
// relying on transitive dependencies is not a good practice
// transitive dependency updating is not fully supported by incremental sync
// run full sync to pick up transitive dependency changes
updateAllProjectsFullSync()
updateAllProjects()
assertModuleLibDeps("m1", "Maven: test:m2:1")
}