[gradle] IDEA-370426 fix deep DefaultGradleLightBuild hierarchy

Before, a root build was not specified as a parent for any of its included builds.

Also moves `GradleDaemonModelHolder#convertNestedGradleBuilds` into `DefaultGradleLightBuild#convertGradleBuilds`.

Tests:
- org.jetbrains.plugins.gradle.model.DefaultGradleLightBuildTest.test DefaultGradleLightBuild.convertGradleBuilds

GitOrigin-RevId: 360ad9ccc4332a91bde203e1669f0f82a309ad3e
This commit is contained in:
Nikita Biriukov
2025-06-24 18:34:21 +00:00
committed by intellij-monorepo-bot
parent 304d52eb3e
commit ce7ec3afd6
7 changed files with 207 additions and 71 deletions
+1
View File
@@ -146,6 +146,7 @@ jvm_library(
"//platform/external-system-impl:externalSystem-tests_test_lib",
"//platform/external-system-api/testFramework",
"//java/testFramework",
"//plugins/gradle/tooling-extension-impl:toolingExtension-impl",
"//plugins/gradle/tooling-extension-impl:toolingExtension-tests_test_lib",
"//java/execution/impl",
"@lib//:assert_j",
+1
View File
@@ -38,6 +38,7 @@
<orderEntry type="module" module-name="intellij.platform.externalSystem.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.externalSystem.testFramework" exported="" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.gradle.toolingExtension.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.gradle.toolingExtension.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.execution.impl" scope="TEST" />
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
@@ -620,8 +620,8 @@ public final class GradleProjectResolver implements ExternalSystemProjectResolve
String rootProjectName = build.getName();
BuildParticipant buildParticipant = new BuildParticipant();
String projectPath = toCanonicalPath(build.getBuildIdentifier().getRootDir().getPath());
String parentPath = build.getParentBuildIdentifier() != null ?
toCanonicalPath(build.getParentBuildIdentifier().getRootDir().getPath()) : null;
String parentPath = build.getParentBuild() != null ?
toCanonicalPath(build.getParentBuild().getBuildIdentifier().getRootDir().getPath()) : null;
buildParticipant.setRootProjectName(rootProjectName);
buildParticipant.setRootPath(projectPath);
buildParticipant.setParentRootPath(parentPath);
@@ -0,0 +1,145 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.model
import org.gradle.tooling.internal.gradle.DefaultBuildIdentifier
import org.gradle.tooling.model.gradle.BasicGradleProject
import org.gradle.tooling.model.gradle.GradleBuild
import org.gradle.tooling.model.internal.ImmutableDomainObjectSet
import org.jetbrains.plugins.gradle.tooling.serialization.internal.adapter.InternalBuildIdentifier
import org.jetbrains.plugins.gradle.tooling.serialization.internal.adapter.InternalProjectIdentifier
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertNotNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.io.File
class DefaultGradleLightBuildTest {
@Test
fun `test DefaultGradleLightBuild#convertGradleBuilds`() {
// GIVEN
val rootBuildPath = "rootBuild"
val includedBuildPath = "rootBuild/includedBuild"
val subIncludedBuildPath = "rootBuild/includedBuild/subIncludedBuild"
val gradleRootBuild = mockBuildWithProjectAndSubproject(rootBuildPath)
val gradleIncludedBuild = mockBuildWithProjectAndSubproject(includedBuildPath, parent = gradleRootBuild)
val gradleSubIncludedBuild = mockBuildWithProjectAndSubproject(subIncludedBuildPath, parent = gradleIncludedBuild)
val gradleBuilds = listOf(gradleRootBuild, gradleIncludedBuild, gradleSubIncludedBuild)
// WHEN
val convertedBuilds = DefaultGradleLightBuild.convertGradleBuilds(gradleBuilds)
// THEN
Assertions.assertIterableEquals(gradleBuilds.map { it.buildIdentifier.rootDir.name },
convertedBuilds.map { it.buildIdentifier.rootDir.name },
"The order of builds in `DefaultGradleLightBuild.convertGradleBuilds` should be the same as in the input")
val rootBuild = convertedBuilds[0]
verifyBuildRootProjectAndSubproject(rootBuild, rootBuildPath)
val includedBuild = convertedBuilds[1]
verifyBuildRootProjectAndSubproject(includedBuild, includedBuildPath, parentBuild = rootBuild)
val subIncluded = convertedBuilds[2]
verifyBuildRootProjectAndSubproject(subIncluded, subIncludedBuildPath, parentBuild = includedBuild)
}
private fun mockBuildWithProjectAndSubproject(path: String, parent: GradleBuild? = null): GradleBuild {
val buildDir = File(path)
val rootProject = mockGradleProject(name = buildDir.name,
path = ":",
buildDir = buildDir,
projectDir = buildDir)
val subproject = mockGradleProject(name = "subproject",
path = ":subproject",
buildDir = buildDir,
projectDir = File("$path/subproject"),
parent = rootProject)
val gradleBuild = mockGradleBuild(buildDir, listOf(rootProject, subproject))
if (parent != null) {
whenever(parent.includedBuilds).thenReturn(ImmutableDomainObjectSet(listOf(gradleBuild)))
}
return gradleBuild
}
private fun mockGradleProject(
name: String,
path: String,
buildDir: File,
projectDir: File? = null,
parent: BasicGradleProject? = null,
): BasicGradleProject =
mock<BasicGradleProject>().also {
whenever(it.projectIdentifier).thenReturn(
InternalProjectIdentifier(InternalBuildIdentifier(buildDir), path))
whenever(it.name).thenReturn(name)
whenever(it.path).thenReturn(path)
whenever(it.projectDirectory).thenReturn(projectDir ?: buildDir)
whenever(it.parent).thenReturn(parent)
whenever(it.children).thenReturn(ImmutableDomainObjectSet(emptyList()))
whenever(it.buildTreePath).thenThrow(NotMockedMemberError::class.java)
if (parent != null) {
whenever(parent.children).thenReturn(ImmutableDomainObjectSet(listOf(it)))
}
}
private fun mockGradleBuild(buildDir: File, projects: List<BasicGradleProject>): GradleBuild =
mock<GradleBuild>().also {
whenever(it.buildIdentifier).thenReturn(DefaultBuildIdentifier(buildDir))
whenever(it.rootProject).thenReturn(projects.first())
whenever(it.projects).thenReturn(ImmutableDomainObjectSet(projects))
whenever(it.includedBuilds).thenReturn(ImmutableDomainObjectSet(emptyList()))
whenever(it.editableBuilds).thenThrow(NotMockedMemberError::class.java)
}
private fun verifyBuildRootProjectAndSubproject(
build: DefaultGradleLightBuild,
buildPath: String,
parentBuild: DefaultGradleLightBuild? = null,
) {
val buildDir = File(buildPath)
val buildName = buildDir.name
Assertions.assertEquals(buildName, build.name)
Assertions.assertEquals(buildDir.absolutePath, build.buildIdentifier.rootDir.absolutePath)
Assertions.assertEquals(parentBuild, build.parentBuild)
Assertions.assertEquals(2, build.projects.count())
// The order of elements in `DefaultGradleLightBuild.getProjects` is random
val rootProject = build.projects.find { it.name == buildName }
.also { Assertions.assertEquals(build.rootProject, it) }!!
val subproject = build.projects.find { it.name == "subproject" }
.also { assertNotNull(it) }!!
verifyProject(project = rootProject,
name = buildName,
path = ":",
build = build,
projectDir = buildDir,
subprojects = listOf(subproject))
verifyProject(project = subproject,
name = "subproject",
path = ":subproject",
build = build,
projectDir = File("$buildPath/subproject"))
}
private fun verifyProject(
project: DefaultGradleLightProject,
name: String,
path: String,
build: DefaultGradleLightBuild,
projectDir: File,
subprojects: List<DefaultGradleLightProject>? = emptyList(),
) {
Assertions.assertEquals(build, project.build)
Assertions.assertEquals(name, project.name)
Assertions.assertEquals(path, project.path)
Assertions.assertEquals(projectDir.absolutePath, project.projectDirectory.absolutePath)
Assertions.assertEquals(build.buildIdentifier.rootDir.absolutePath, project.projectIdentifier.buildIdentifier.rootDir.absolutePath)
Assertions.assertEquals(path, project.projectIdentifier.projectPath)
Assertions.assertIterableEquals(subprojects, project.childProjects)
}
private class NotMockedMemberError : IllegalStateException("The accessed member is not mocked. Please mock it in the test.")
}
@@ -27,7 +27,7 @@ public interface GradleLightBuild extends BuildModel {
BuildIdentifier getBuildIdentifier();
@Nullable
BuildIdentifier getParentBuildIdentifier();
GradleLightBuild getParentBuild();
@NotNull
GradleLightProject getRootProject();
@@ -4,23 +4,21 @@ package com.intellij.gradle.toolingExtension.impl.modelAction;
import com.intellij.gradle.toolingExtension.impl.modelSerialization.ToolingSerializerConverter;
import com.intellij.gradle.toolingExtension.impl.util.GradleExecutorServiceUtil;
import org.gradle.tooling.BuildController;
import org.gradle.tooling.internal.gradle.DefaultBuildIdentifier;
import org.gradle.tooling.model.BuildIdentifier;
import org.gradle.tooling.model.BuildModel;
import org.gradle.tooling.model.gradle.BasicGradleProject;
import org.gradle.tooling.model.gradle.GradleBuild;
import org.gradle.util.GradleVersion;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.model.DefaultGradleLightBuild;
import org.jetbrains.plugins.gradle.model.DefaultBuildController;
import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider.GradleModelConsumer;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import static org.jetbrains.plugins.gradle.model.DefaultGradleLightBuild.convertGradleBuilds;
/**
* Holder for the Gradle models that consumes and holds data on the Gradle Daemon side.
* <p>
@@ -53,8 +51,7 @@ public class GradleDaemonModelHolder {
private final @NotNull GradleVersion myGradleVersion;
private final @NotNull BlockingQueue<Future<DefaultGradleLightBuild>> myConvertedRootBuild = new LinkedBlockingQueue<>();
private final @NotNull BlockingQueue<Future<Collection<DefaultGradleLightBuild>>> myConvertedNestedBuilds = new LinkedBlockingQueue<>();
private final @NotNull BlockingQueue<Future<List<DefaultGradleLightBuild>>> myConvertedBuilds = new LinkedBlockingQueue<>();
private final @NotNull BlockingQueue<Future<ConvertedModel>> myConvertedModelQueue = new LinkedBlockingQueue<>();
public GradleDaemonModelHolder(
@@ -68,11 +65,12 @@ public class GradleDaemonModelHolder {
myRootGradleBuild = rootGradleBuild;
myNestedGradleBuilds = nestedGradleBuilds;
myGradleVersion = gradleVersion;
GradleExecutorServiceUtil.submitTask(converterExecutor, myConvertedRootBuild, () -> {
return DefaultGradleLightBuild.convertGradleBuild(rootGradleBuild);
});
GradleExecutorServiceUtil.submitTask(converterExecutor, myConvertedNestedBuilds, () -> {
return convertNestedGradleBuilds(nestedGradleBuilds);
GradleExecutorServiceUtil.submitTask(converterExecutor, myConvertedBuilds, () -> {
List<GradleBuild> gradleBuilds = new ArrayList<>();
gradleBuilds.add(myRootGradleBuild);
gradleBuilds.addAll(myNestedGradleBuilds);
return convertGradleBuilds(gradleBuilds);
});
}
@@ -109,18 +107,16 @@ public class GradleDaemonModelHolder {
}
public @NotNull GradleModelHolderState pollPendingState() {
DefaultGradleLightBuild rootBuild = pollPendingConvertedRootBuild();
Collection<DefaultGradleLightBuild> nestedBuilds = pollPendingConvertedNestedBuilds();
List<DefaultGradleLightBuild> builds = pollPendingConvertedNestedBuilds();
DefaultGradleLightBuild rootBuild = builds.isEmpty() ? null : builds.get(0);
List<DefaultGradleLightBuild> nestedBuilds = builds.size() > 1 ? builds.subList(1, builds.size()) : Collections.emptyList();
Map<GradleModelId, Object> models = pollAllPendingConvertedModels();
return new GradleModelHolderState(rootBuild, nestedBuilds, models);
return new GradleModelHolderState(rootBuild, new ArrayList<>(nestedBuilds), models);
}
private @Nullable DefaultGradleLightBuild pollPendingConvertedRootBuild() {
return GradleExecutorServiceUtil.poolPendingResult(myConvertedRootBuild);
}
private @NotNull Collection<DefaultGradleLightBuild> pollPendingConvertedNestedBuilds() {
Collection<DefaultGradleLightBuild> builds = GradleExecutorServiceUtil.poolPendingResult(myConvertedNestedBuilds);
private @NotNull List<DefaultGradleLightBuild> pollPendingConvertedNestedBuilds() {
List<DefaultGradleLightBuild> builds = GradleExecutorServiceUtil.poolPendingResult(myConvertedBuilds);
return builds == null ? Collections.emptyList() : builds;
}
@@ -133,45 +129,6 @@ public class GradleDaemonModelHolder {
return modelMap;
}
private static @NotNull Collection<DefaultGradleLightBuild> convertNestedGradleBuilds(
@NotNull Collection<? extends GradleBuild> nestedGradleBuilds
) {
List<DefaultGradleLightBuild> nestedBuilds = new ArrayList<>();
for (GradleBuild gradleBuild : nestedGradleBuilds) {
DefaultGradleLightBuild build = DefaultGradleLightBuild.convertGradleBuild(gradleBuild);
nestedBuilds.add(build);
}
setupNestedBuildHierarchy(nestedBuilds, nestedGradleBuilds);
return nestedBuilds;
}
private static void setupNestedBuildHierarchy(
@NotNull Collection<DefaultGradleLightBuild> builds,
@NotNull Collection<? extends GradleBuild> gradleBuilds
) {
Set<DefaultGradleLightBuild> updatedBuilds = new HashSet<>();
Map<File, DefaultGradleLightBuild> rootDirsToBuilds = new HashMap<>();
for (DefaultGradleLightBuild build : builds) {
BuildIdentifier buildIdentifier = build.getBuildIdentifier();
rootDirsToBuilds.put(buildIdentifier.getRootDir(), build);
}
for (GradleBuild gradleBuild : gradleBuilds) {
BuildIdentifier buildIdentifier = gradleBuild.getBuildIdentifier();
DefaultGradleLightBuild build = rootDirsToBuilds.get(buildIdentifier.getRootDir());
if (build == null) {
continue;
}
for (GradleBuild includedGradleBuild : gradleBuild.getIncludedBuilds()) {
DefaultGradleLightBuild buildToUpdate = rootDirsToBuilds.get(includedGradleBuild.getBuildIdentifier().getRootDir());
if (buildToUpdate != null && updatedBuilds.add(buildToUpdate)) {
buildToUpdate.setParentBuildIdentifier(new DefaultBuildIdentifier(buildIdentifier.getRootDir()));
}
}
}
}
private static class ConvertedModel {
private final @NotNull GradleModelId myId;
@@ -23,7 +23,7 @@ public final class DefaultGradleLightBuild implements GradleLightBuild, Serializ
private final @NotNull DefaultGradleLightProject myRootProject;
private final @NotNull List<DefaultGradleLightProject> myProjects;
private @Nullable DefaultBuildIdentifier myParentBuildIdentifier = null;
private @Nullable DefaultGradleLightBuild myParentBuild = null;
public DefaultGradleLightBuild(@NotNull GradleBuild gradleBuild) {
BasicGradleProject rootGradleProject = gradleBuild.getRootProject();
@@ -66,12 +66,12 @@ public final class DefaultGradleLightBuild implements GradleLightBuild, Serializ
}
@Override
public @Nullable DefaultBuildIdentifier getParentBuildIdentifier() {
return myParentBuildIdentifier;
public @Nullable DefaultGradleLightBuild getParentBuild() {
return myParentBuild;
}
public void setParentBuildIdentifier(@Nullable DefaultBuildIdentifier parentBuildIdentifier) {
myParentBuildIdentifier = parentBuildIdentifier;
public void setParentBuild(@Nullable DefaultGradleLightBuild parentBuild) {
myParentBuild = parentBuild;
}
@Override
@@ -82,10 +82,6 @@ public final class DefaultGradleLightBuild implements GradleLightBuild, Serializ
'}';
}
public static @NotNull DefaultGradleLightBuild convertGradleBuild(@NotNull GradleBuild gradleBuild) {
return new DefaultGradleLightBuild(gradleBuild);
}
public static <ModelA, ModelB> void replicateModelHierarchy(
@NotNull ModelA rootModelA,
@NotNull Function<@NotNull ModelA, @Nullable ModelB> getModel,
@@ -115,4 +111,40 @@ public final class DefaultGradleLightBuild implements GradleLightBuild, Serializ
}
}
}
/**
* @return {@code gradleBuilds} converted to {@link DefaultGradleLightBuild} instances.
* Original order is preserved: if a root build is a first element of {@code gradleBuilds},
* then the first element of returned list is also a root build.
*/
public static @NotNull List<DefaultGradleLightBuild> convertGradleBuilds(
@NotNull Collection<? extends GradleBuild> gradleBuilds
) {
Map<GradleBuild, DefaultGradleLightBuild> gradleBuildsToConverted = new HashMap<>();
List<DefaultGradleLightBuild> convertedBuilds = new ArrayList<>();
// TODO traverse builds via graph to avoid separated parent build field initialization
for (GradleBuild gradleBuild : gradleBuilds) {
DefaultGradleLightBuild build = new DefaultGradleLightBuild(gradleBuild);
gradleBuildsToConverted.put(gradleBuild, build);
convertedBuilds.add(build);
}
setHierarchy(gradleBuilds, gradleBuildsToConverted);
return convertedBuilds;
}
private static void setHierarchy(
@NotNull Collection<? extends GradleBuild> gradleBuilds,
Map<GradleBuild, DefaultGradleLightBuild> gradleBuildsToConverted
) {
for (GradleBuild gradleBuild : gradleBuilds) {
DefaultGradleLightBuild build = gradleBuildsToConverted.get(gradleBuild);
assert build != null;
for (GradleBuild includedGradleBuild : gradleBuild.getIncludedBuilds()) {
DefaultGradleLightBuild buildToUpdate = gradleBuildsToConverted.get(includedGradleBuild);
assert buildToUpdate != null;
buildToUpdate.setParentBuild(build);
}
}
}
}