diff --git a/plugins/gradle/java/testSources/importing/GradleBuildSrcImportingTest.kt b/plugins/gradle/java/testSources/importing/GradleBuildSrcImportingTest.kt index 0f6811e4701a..5496c11c6b94 100644 --- a/plugins/gradle/java/testSources/importing/GradleBuildSrcImportingTest.kt +++ b/plugins/gradle/java/testSources/importing/GradleBuildSrcImportingTest.kt @@ -261,6 +261,54 @@ class GradleBuildSrcImportingTest : GradleImportingTestCase() { "build2.buildSrc", "build2.buildSrc.main", "build2.buildSrc.test") } + /* + + Builds inclusion and buildSrc presence graph + + A--> B--> D--> buildSrc + | └--> buildSrc + | + └--> C--> D--> buildSrc + └--> buildSrc + */ + @Test + @TargetVersions("8.0+") + fun `test buildSrc in a composite with build names duplication`() { + createSettingsFile(""" + rootProject.name = "A" + includeBuild("B") + includeBuild("C") + """.trimIndent()) + + createProjectSubFile("B/settings.gradle", """ + rootProject.name = "B" + includeBuild("D") + """.trimIndent()) + + createProjectSubFile("B/buildSrc/settings.gradle", "") + + createProjectSubFile("B/D/settings.gradle", "rootProject.name = 'D'") + createProjectSubFile("B/D/buildSrc/settings.gradle", "") + + + createProjectSubFile("C/settings.gradle", """ + rootProject.name = "C" + includeBuild("D") + """.trimIndent()) + + createProjectSubFile("C/buildSrc/settings.gradle", "") + + createProjectSubFile("C/D/settings.gradle", "rootProject.name = 'D'") + createProjectSubFile("C/D/buildSrc/settings.gradle", "") + + importProject("") + assertModules("A", "B", "C", "D", "C.D", + "B.buildSrc", "B.buildSrc.main", "B.buildSrc.test", + "C.buildSrc", "C.buildSrc.main", "C.buildSrc.test", + "D.buildSrc", "D.buildSrc.main", "D.buildSrc.test", + "C.D.buildSrc", "C.D.buildSrc.main", "C.D.buildSrc.test") + } + private fun assertBuildScriptClassPathContains(moduleName: String, expectedEntries: Collection) { val module = ModuleManager.getInstance(myProject).findModuleByName(moduleName) val modulePath = ExternalSystemApiUtil.getExternalProjectPath(module) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/DefaultProjectResolverContext.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/DefaultProjectResolverContext.java index 6c8b6c0553a2..78087465d3e4 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/DefaultProjectResolverContext.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/DefaultProjectResolverContext.java @@ -18,6 +18,7 @@ import org.gradle.tooling.model.idea.IdeaModule; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.model.Build; import org.jetbrains.plugins.gradle.model.ProjectImportAction; import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings; @@ -201,6 +202,20 @@ public class DefaultProjectResolverContext extends UserDataHolderBase implements return myBuildSrcGroup; } + @Nullable + @Override + public String getBuildSrcGroup(@NotNull IdeaModule module) { + if (!"buildSrc".equals(module.getProject().getName())) { + return myBuildSrcGroup; + } + String parentRootDir = module.getGradleProject().getProjectIdentifier().getBuildIdentifier().getRootDir().getParent(); + return getModels().getAllBuilds().stream() + .filter(b -> b.getBuildIdentifier().getRootDir().toString().equals(parentRootDir)) + .findFirst() + .map(Build::getName) + .orElse(myBuildSrcGroup); + } + @Override public void report(@NotNull MessageEvent.Kind kind, @NotNull BuildIssue buildIssue) { BuildIssueEventImpl buildIssueEvent = new BuildIssueEventImpl(myExternalSystemTaskId, buildIssue, kind); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java index dd4024d1efc7..a1b5b711eb5f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java @@ -47,6 +47,7 @@ import org.jetbrains.plugins.gradle.execution.target.TargetBuildLauncher; import org.jetbrains.plugins.gradle.issue.DeprecatedGradleVersionIssue; import org.jetbrains.plugins.gradle.model.*; import org.jetbrains.plugins.gradle.model.data.BuildParticipant; +import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData; import org.jetbrains.plugins.gradle.model.data.CompositeBuildData; import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData; import org.jetbrains.plugins.gradle.remote.impl.GradleLibraryNamesMixer; @@ -57,8 +58,10 @@ import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings; import org.jetbrains.plugins.gradle.util.GradleConstants; import java.io.File; +import java.nio.file.Path; import java.util.*; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.stream.Stream; @@ -156,9 +159,12 @@ public class GradleProjectResolver implements ExternalSystemProjectResolver projectDataNode) { + // since Gradle 8.0 buildSrc are available as composite build members + DataNode compositeNode = find(projectDataNode, CompositeBuildData.KEY); + if (compositeNode == null) return; + + GradleBuildSrcProjectsResolver.Index index = GradleBuildSrcProjectsResolver.prepareIndexes(projectDataNode); + + CompositeBuildData compositeBuildData = compositeNode.getData(); + for (BuildParticipant participant : compositeBuildData.getCompositeParticipants()) { + if (participant.getRootProjectName().endsWith("buildSrc")) { + Set buildSrcProjectPaths = participant.getProjects(); + + @NotNull Collection> buildClasspathNodes = + index.buildClasspathNodesMap().get(Path.of(participant.getRootPath()).getParent()); + + @NotNull Map> buildSrcModules = new HashMap<>(); + @Nullable AtomicReference> buildSrcModuleNode = new AtomicReference<>(); + + findAll(projectDataNode, ProjectKeys.MODULE).stream() + .filter(node -> buildSrcProjectPaths.contains(node.getData().getLinkedExternalProjectPath())) + .forEach(node -> { + buildSrcModules.put(node.getData().getId(), node); + findAll(node, GradleSourceSetData.KEY).forEach( + sourceSetNode -> buildSrcModules.put(sourceSetNode.getData().getId(), sourceSetNode)); + + if (participant.getRootPath().equals(node.getData().getLinkedExternalProjectPath())) { + if (ctx.isResolveModulePerSourceSet()) { + buildSrcModuleNode.set(findChild(node, GradleSourceSetData.KEY, + sourceSetNode -> sourceSetNode.getData().getExternalName().endsWith(":main"))); + } + else { + buildSrcModuleNode.set(node); + } + } + }); + + GradleBuildSrcProjectsResolver.addBuildSrcToBuildScriptClasspathData(buildClasspathNodes, + buildSrcModules, + buildSrcModuleNode.get()); + } + } + } + private static boolean isCustomSerializationSupported(@NotNull DefaultProjectResolverContext resolverCtx, GradleVersion gradleVersion, boolean isCompositeBuildsSupported) { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverUtil.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverUtil.java index 1f59ce8a6af7..d166c25a957d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverUtil.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolverUtil.java @@ -177,7 +177,7 @@ public final class GradleProjectResolverUtil { @NotNull ProjectResolverContext resolverCtx) { String delimiter; StringBuilder moduleName = new StringBuilder(); - String buildSrcGroup = resolverCtx.getBuildSrcGroup(); + String buildSrcGroup = resolverCtx.getBuildSrcGroup(gradleModule); if (resolverCtx.isUseQualifiedModuleNames()) { delimiter = "."; if (StringUtil.isNotEmpty(buildSrcGroup)) { @@ -228,8 +228,16 @@ public final class GradleProjectResolverUtil { String gradlePath = projectIdentifier.getProjectPath(); String compositePrefix = ""; boolean isRootPath = StringUtil.isEmpty(gradlePath) || ":".equals(gradlePath); + + if (GradleVersion.version(resolverCtx.getProjectGradleVersion()).compareTo(GradleVersion.version("8.0")) >= 0) { + if (build != resolverCtx.getModels().getMainBuild() || isRootPath) { + compositePrefix = buildCompositePrefixFromBuildNames(resolverCtx, build.getBuildIdentifier()); + } + return compositePrefix + (StringUtil.isEmpty(gradlePath) || ":".equals(gradlePath) ? "" : gradlePath); + } + if (!isRootPath && build != resolverCtx.getModels().getMainBuild()) { - compositePrefix = buildCompositePrefixFromBuildNames(resolverCtx, build.getBuildIdentifier()); + compositePrefix = build.getName(); } else if (!StringUtil.isEmpty(resolverCtx.getBuildSrcGroup())) { compositePrefix = resolverCtx.getBuildSrcGroup() + (isRootPath ? ":" : ":buildSrc"); } @@ -244,9 +252,15 @@ public final class GradleProjectResolverUtil { IdeaProject ideaProject = gradleModule.getProject(); boolean isRootPath = StringUtil.isEmpty(gradlePath) || ":".equals(gradlePath); + if (GradleVersion.version(resolverCtx.getProjectGradleVersion()).compareTo(GradleVersion.version("8.0")) >= 0) { + if (ideaProject != resolverCtx.getModels().getModel(IdeaProject.class) || isRootPath) { + compositePrefix = buildCompositePrefixFromBuildNames(resolverCtx, gradleProject.getProjectIdentifier().getBuildIdentifier()); + } + return compositePrefix + (StringUtil.isEmpty(gradlePath) || ":".equals(gradlePath) ? "" : gradlePath); + } + if (!isRootPath && ideaProject != resolverCtx.getModels().getModel(IdeaProject.class)) { - BuildIdentifier buildId = gradleModule.getGradleProject().getProjectIdentifier().getBuildIdentifier(); - compositePrefix = buildCompositePrefixFromBuildNames(resolverCtx, buildId); + compositePrefix = ideaProject.getName(); } else if (!StringUtil.isEmpty(resolverCtx.getBuildSrcGroup())) { compositePrefix = resolverCtx.getBuildSrcGroup() + (isRootPath ? ":" : ":buildSrc"); } @@ -255,18 +269,48 @@ public final class GradleProjectResolverUtil { } @NotNull - private static String buildCompositePrefixFromBuildNames(@NotNull ProjectResolverContext resolverCtx, BuildIdentifier buildId) { - String compositePrefix; - List list = Stream.iterate( - findIncludedBuild(resolverCtx, buildId), - Objects::nonNull, - b -> findIncludedBuild(resolverCtx, b.getParentBuildIdentifier()) - ) - .map(Build::getName) - .collect(Collectors.toList()); - Collections.reverse(list); - compositePrefix = StringUtil.join(list, "."); - return compositePrefix; + private static String buildCompositePrefixFromBuildNames(@NotNull ProjectResolverContext resolverCtx, @NotNull BuildIdentifier buildId) { + String result = ""; + Build build = findBuild(resolverCtx, buildId); + // if this build can not be found, return empty prefix + if (build == null) { + return result; + } + + boolean isBuildSrc = build.getBuildIdentifier().getRootDir().getName().equals("buildSrc"); + + if (isBuildSrc) { + build = findBuild(resolverCtx, new BuildIdentifier() { + @Override + public File getRootDir() { + return buildId.getRootDir().getParentFile(); + } + }); + } + + List includedParentsList = Stream.iterate( + build, + Objects::nonNull, + b -> findIncludedBuild(resolverCtx, b.getParentBuildIdentifier()) + ).map(b -> { + var ideaProject = resolverCtx.getModels().getModel(b, IdeaProject.class); + if (ideaProject != null) { + return ideaProject.getName(); + } + else { + return b.getName(); + } + }).collect(Collectors.toList()); + + Collections.reverse(includedParentsList); + + result = StringUtil.join(includedParentsList, "."); + + if (isBuildSrc) { + result += ":buildSrc"; + } + + return result; } @Nullable @@ -277,6 +321,14 @@ public final class GradleProjectResolverUtil { return ContainerUtil.find(resolverCtx.getModels().getIncludedBuilds(), b -> FileUtil.filesEqual(b.getBuildIdentifier().getRootDir(), buildId.getRootDir())); } + @Nullable + private static Build findBuild(@NotNull ProjectResolverContext resolverCtx, @Nullable BuildIdentifier buildId) { + if (buildId == null) { + return null; + } + return ContainerUtil.find(resolverCtx.getModels().getAllBuilds(), b -> FileUtil.filesEqual(b.getBuildIdentifier().getRootDir(), buildId.getRootDir())); + } + @NotNull public static String getModuleId(String gradlePath, String moduleName) { return StringUtil.isEmpty(gradlePath) || ":".equals(gradlePath) ? moduleName : gradlePath; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java index 18272501d6a4..411931bfc4b8 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/ProjectResolverContext.java @@ -69,6 +69,9 @@ public interface ProjectResolverContext extends UserDataHolderEx { @Nullable String getBuildSrcGroup(); + @Nullable + String getBuildSrcGroup(IdeaModule module); + @ApiStatus.Experimental void report(@NotNull MessageEvent.Kind kind, @NotNull BuildIssue buildIssue); } diff --git a/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java b/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java index a87bd7013d4f..9cdf9b7b3d13 100644 --- a/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java +++ b/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java @@ -19,6 +19,7 @@ import org.gradle.tooling.model.build.JavaEnvironment; import org.gradle.tooling.model.gradle.BasicGradleProject; import org.gradle.tooling.model.gradle.GradleBuild; import org.gradle.tooling.model.idea.IdeaProject; +import org.gradle.util.GradleVersion; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -132,7 +133,7 @@ public class ProjectImportAction implements BuildAction nestedBuilds = getNestedBuilds(myGradleBuild); + final Set nestedBuilds = getNestedBuilds(myGradleBuild, controller); final MyBuildController wrappedController = new MyBuildController(controller, myGradleBuild); fetchProjectBuildModels(wrappedController, isProjectsLoadedAction, myGradleBuild); addBuildModels(wrappedController, myAllModels, myGradleBuild, isProjectsLoadedAction); @@ -218,7 +219,13 @@ public class ProjectImportAction implements BuildAction getNestedBuilds(@NotNull GradleBuild rootBuild) { + private Set getNestedBuilds(@NotNull GradleBuild rootBuild, BuildController controller) { + BuildEnvironment environment = controller.getModel(BuildEnvironment.class); + GradleVersion envGradleVersion = null; + if (environment != null) { + // call to GradleVersion.current() will load version class from client classloader and return TAPI version number + envGradleVersion = GradleVersion.version(environment.getGradle().getGradleVersion()); + } if (!myIsCompositeBuildsSupported) { return Collections.emptySet(); } @@ -226,18 +233,32 @@ public class ProjectImportAction implements BuildAction nestedBuilds = new LinkedHashSet<>(); String rootBuildPath = rootBuild.getBuildIdentifier().getRootDir().getPath(); processedBuildsPaths.add(rootBuildPath); - Queue queue = new ArrayDeque<>(rootBuild.getIncludedBuilds()); + Queue queue = new ArrayDeque<>(getEditableBuilds(rootBuild, envGradleVersion)); while (!queue.isEmpty()) { GradleBuild includedBuild = queue.remove(); String includedBuildPath = includedBuild.getBuildIdentifier().getRootDir().getPath(); if (processedBuildsPaths.add(includedBuildPath)) { nestedBuilds.add(includedBuild); - queue.addAll(includedBuild.getIncludedBuilds()); + queue.addAll(getEditableBuilds(includedBuild, envGradleVersion)); } } return nestedBuilds; } + /** + * Get nested builds to be imported by IDEA + * @param build parent build + * @return builds to be imported by IDEA. Before Gradle 8.0 - included builds, 8.0 and later - included and buildSrc builds + */ + private static DomainObjectSet getEditableBuilds(@NotNull GradleBuild rootBuild, + @Nullable GradleVersion version) { + if (version != null && version.compareTo(GradleVersion.version("8.0")) >= 0) { + return rootBuild.getEditableBuilds(); + } else { + return rootBuild.getIncludedBuilds(); + } + } + private void fetchProjectBuildModels(BuildController controller, final boolean isProjectsLoadedAction, GradleBuild build) { // Prepare nested build actions. List>> buildActions = new ArrayList<>(); @@ -482,6 +503,13 @@ public class ProjectImportAction implements BuildAction getAllBuilds() { + List result = new ArrayList<>(includedBuilds); + result.add(0, getMainBuild()); + return result; + } + @Nullable public BuildEnvironment getBuildEnvironment() { return getModel(BuildEnvironment.class);