IDEA-243817 API for incremental gradle import: models for buildSrc and composite builds are missing

GitOrigin-RevId: 981423bd56074978651f7fdbd5c9887ba6db4e25
This commit is contained in:
Vladislav.Soroka
2020-06-19 11:05:38 +03:00
committed by intellij-monorepo-bot
parent 483de39a27
commit 4b03338ba1
6 changed files with 264 additions and 101 deletions
@@ -1,4 +1,4 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.importing
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
@@ -7,7 +7,10 @@ import com.intellij.openapi.externalSystem.service.project.ProjectDataManager
import com.intellij.openapi.externalSystem.test.ExternalSystemTestUtil.assertMapsEqual
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.io.FileUtil.pathsEqual
import com.intellij.testFramework.registerServiceInstance
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Condition
import org.gradle.tooling.BuildController
import org.gradle.tooling.model.BuildModel
import org.gradle.tooling.model.Model
@@ -19,6 +22,8 @@ import org.jetbrains.plugins.gradle.model.Project
import org.jetbrains.plugins.gradle.model.ProjectImportAction
import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider
import org.jetbrains.plugins.gradle.service.project.*
import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions
import org.jetbrains.plugins.gradle.tooling.builder.ProjectPropertiesTestModelBuilder.ProjectProperties
import org.jetbrains.plugins.gradle.util.GradleConstants.SYSTEM_ID
import org.junit.Test
import java.io.Serializable
@@ -28,8 +33,8 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
override fun setUp() {
super.setUp()
myProject.registerServiceInstance(ModelConsumer::class.java, ModelConsumer())
GradleProjectResolverExtension.EP_NAME.getPoint().registerExtension(TestPartialProjectResolverExtension(), testRootDisposable)
ProjectModelContributor.EP_NAME.getPoint().registerExtension(TestProjectModelContributor(), testRootDisposable)
GradleProjectResolverExtension.EP_NAME.point.registerExtension(TestPartialProjectResolverExtension(), testRootDisposable)
ProjectModelContributor.EP_NAME.point.registerExtension(TestProjectModelContributor(), testRootDisposable)
}
@@ -37,8 +42,9 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
fun `test re-import with partial project data resolve`() {
createAndImportTestProject()
assertReceivedModels(
mapOf("prop1" to "val1"),
mapOf("prop2" to "val2")
projectPath, "project",
mapOf("name" to "project", "prop_loaded_1" to "val1"),
mapOf("name" to "project", "prop_finished_2" to "val2")
)
val initialProjectStructure = ProjectDataManager.getInstance()
@@ -48,8 +54,8 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
createProjectSubFile(
"gradle.properties",
"prop1=val1_inc\n" +
"prop2=val2_inc\n"
"prop_loaded_1=val1_inc\n" +
"prop_finished_2=val2_inc\n"
)
cleanupBeforeReImport()
@@ -68,8 +74,9 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
)
assertReceivedModels(
mapOf("prop1" to "val1_inc"),
mapOf("prop2" to "val2_inc")
projectPath, "project",
mapOf("name" to "project", "prop_loaded_1" to "val1_inc"),
mapOf("name" to "project", "prop_finished_2" to "val2_inc")
)
val projectStructureAfterIncrementalImport = ProjectDataManager.getInstance()
@@ -80,18 +87,142 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
assertEquals(initialProjectStructure, projectStructureAfterIncrementalImport)
}
@Test
@TargetVersions("3.3+")
fun `test composite project partial re-import`() {
createAndImportTestCompositeProject()
assertReceivedModels(
projectPath, "project",
mapOf("name" to "project", "prop_loaded_1" to "val1"),
mapOf("name" to "project", "prop_finished_2" to "val2")
)
assertReceivedModels(
path("buildSrc"), "buildSrc",
mapOf("name" to "buildSrc"),
mapOf("name" to "buildSrc")
)
assertReceivedModels(
path("includedBuild"), "includedBuild",
mapOf("name" to "includedBuild", "prop_loaded_included" to "val1"),
mapOf("name" to "includedBuild", "prop_finished_included" to "val2")
)
assertReceivedModels(
path("includedBuild"), "subProject",
mapOf("name" to "subProject", "prop_loaded_included" to "val1"),
mapOf("name" to "subProject", "prop_finished_included" to "val2")
)
assertReceivedModels(
path("includedBuild/buildSrc"), "buildSrc",
mapOf("name" to "buildSrc"),
mapOf("name" to "buildSrc")
)
val initialProjectStructure = ProjectDataManager.getInstance()
.getExternalProjectData(myProject, SYSTEM_ID, projectPath)!!
.externalProjectStructure!!
.graphCopy()
createProjectSubFile(
"gradle.properties",
"prop_loaded_1=val1_inc\n" +
"prop_finished_2=val2_inc\n"
)
createProjectSubFile(
"includedBuild/gradle.properties",
"prop_loaded_included=val1_1\n" +
"prop_finished_included=val2_2\n"
)
cleanupBeforeReImport()
ExternalSystemUtil.refreshProject(
projectPath,
ImportSpecBuilder(myProject, SYSTEM_ID)
.use(ProgressExecutionMode.MODAL_SYNC)
.projectResolverPolicy(
GradlePartialResolverPolicy(Predicate { it is TestPartialProjectResolverExtension })
)
)
assertReceivedModels(
projectPath, "project",
mapOf("name" to "project", "prop_loaded_1" to "val1_inc"),
mapOf("name" to "project", "prop_finished_2" to "val2_inc")
)
assertReceivedModels(
path("buildSrc"), "buildSrc",
mapOf("name" to "buildSrc"),
mapOf("name" to "buildSrc")
)
assertReceivedModels(
path("includedBuild"), "includedBuild",
mapOf("name" to "includedBuild", "prop_loaded_included" to "val1_1"),
mapOf("name" to "includedBuild", "prop_finished_included" to "val2_2")
)
assertReceivedModels(
path("includedBuild"), "subProject",
mapOf("name" to "subProject", "prop_loaded_included" to "val1_1"),
mapOf("name" to "subProject", "prop_finished_included" to "val2_2")
)
assertReceivedModels(
path("includedBuild/buildSrc"), "buildSrc",
mapOf("name" to "buildSrc"),
mapOf("name" to "buildSrc")
)
val projectStructureAfterIncrementalImport = ProjectDataManager.getInstance()
.getExternalProjectData(myProject, SYSTEM_ID, projectPath)!!
.externalProjectStructure!!
.graphCopy()
assertEquals(initialProjectStructure, projectStructureAfterIncrementalImport)
}
private fun createAndImportTestCompositeProject() {
createProjectSubFile(
"buildSrc/build.gradle",
"apply plugin: 'groovy'\n" +
"dependencies {\n" +
" compile gradleApi()\n" +
" compile localGroovy()\n" +
"}"
)
createProjectSubFile(
"gradle.properties",
"prop_loaded_1=val1\n" +
"prop_finished_2=val2\n"
)
createProjectSubFile("includedBuild/settings.gradle", "include 'subProject'")
createProjectSubDir("includedBuild/subProject")
createProjectSubFile(
"includedBuild/buildSrc/build.gradle",
"apply plugin: 'groovy'\n" +
"dependencies {\n" +
" compile gradleApi()\n" +
" compile localGroovy()\n" +
"}"
)
createSettingsFile("includeBuild 'includedBuild'")
createProjectSubFile(
"includedBuild/gradle.properties",
"prop_loaded_included=val1\n" +
"prop_finished_included=val2\n"
)
importProject("")
}
@Test
fun `test import cancellation on project loaded phase`() {
createAndImportTestProject()
assertReceivedModels(
mapOf("prop1" to "val1"),
mapOf("prop2" to "val2")
assertReceivedModels(projectPath, "project",
mapOf("name" to "project", "prop_loaded_1" to "val1"),
mapOf("name" to "project", "prop_finished_2" to "val2")
)
createProjectSubFile(
"gradle.properties",
"prop1=error\n" +
"prop2=val22\n"
"prop_loaded_1=error\n" +
"prop_finished_2=val22\n"
)
cleanupBeforeReImport()
@@ -105,14 +236,16 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
" Build cancelled"
)
}
assertReceivedModels(mapOf("prop1" to "error"))
assertReceivedModels(projectPath, "project", mapOf("name" to "project", "prop_loaded_1" to "error"))
}
else {
assertSyncViewTreeEquals(
"-\n" +
" finished"
)
assertReceivedModels(mapOf("prop1" to "error"), mapOf("prop2" to "val22"))
assertReceivedModels(projectPath, "project",
mapOf("name" to "project", "prop_loaded_1" to "error"),
mapOf("name" to "project", "prop_finished_2" to "val22"))
}
}
@@ -122,76 +255,44 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
}
private fun createAndImportTestProject() {
val injectModelBuilder = """
import org.gradle.api.Project;
import javax.inject.Inject;
import org.gradle.tooling.provider.model.ToolingModelBuilder;
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
class TestPlugin implements Plugin<Project> {
private ToolingModelBuilderRegistry registry;
@Inject
TestPlugin(ToolingModelBuilderRegistry registry) {
this.registry = registry;
}
void apply(Project project) {
registry.register(new MyModelBuilder1())
registry.register(new MyModelBuilder2())
}
private static class MyModelBuilder1 implements ToolingModelBuilder {
boolean canBuild(String modelName) {
return 'java.util.HashMap' == modelName;
}
@Override
Object buildAll(String modelName, Project project) {
def map = new HashMap<>()
map.put("prop1", project.properties['prop1'])
return map;
}
}
private static class MyModelBuilder2 implements ToolingModelBuilder {
boolean canBuild(String modelName) {
return 'java.util.LinkedHashMap' == modelName;
}
@Override
Object buildAll(String modelName, Project project) {
def map = new LinkedHashMap<>()
map.put("prop2", project.properties['prop2'])
return map;
}
}
}
""".trimIndent()
createProjectSubFile(
"gradle.properties",
"prop1=val1\n" +
"prop2=val2\n"
"prop_loaded_1=val1\n" +
"prop_finished_2=val2\n"
)
importProject(
GradleBuildScriptBuilderEx()
.withJavaPlugin()
.addPostfix(injectModelBuilder)
.applyPlugin("TestPlugin")
.generate()
)
}
private fun assertReceivedModels(
buildPath: String, projectName: String,
expectedProjectLoadedModelsMap: Map<String, String>,
expectedBuildFinishedModelsMap: Map<String, String>? = null
) {
val modelConsumer = myProject.getService(ModelConsumer::class.java)
assertSize(1, modelConsumer.projectLoadedModels)
assertMapsEqual(expectedProjectLoadedModelsMap, modelConsumer.projectLoadedModels.first().second.map)
val projectLoadedPredicate = Predicate<Pair<Project, ProjectLoadedModel>> {
val project = it.first
project.name == projectName &&
pathsEqual(project.projectIdentifier.buildIdentifier.rootDir.path, buildPath)
}
assertThat(modelConsumer.projectLoadedModels)
.haveExactly(1, Condition(projectLoadedPredicate, "project loaded model for '$projectName' at '$buildPath'"))
val (_, projectLoadedModel) = modelConsumer.projectLoadedModels.find(projectLoadedPredicate::test)!!
assertMapsEqual(expectedProjectLoadedModelsMap, projectLoadedModel.map)
if (expectedBuildFinishedModelsMap != null) {
assertSize(1, modelConsumer.buildFinishedModels)
assertMapsEqual(expectedBuildFinishedModelsMap, modelConsumer.buildFinishedModels.first().second.map)
val buildFinishedPredicate = Predicate<Pair<Project, BuildFinishedModel>> {
val project = it.first
project.name == projectName &&
pathsEqual(project.projectIdentifier.buildIdentifier.rootDir.path, buildPath)
}
assertThat(modelConsumer.buildFinishedModels)
.haveExactly(1, Condition(buildFinishedPredicate, "build finished model for '$projectName' at '$buildPath'"))
val (_, buildFinishedModel) = modelConsumer.buildFinishedModels.find(buildFinishedPredicate::test)!!
assertMapsEqual(expectedBuildFinishedModelsMap, buildFinishedModel.map)
}
else {
assertEmpty(modelConsumer.buildFinishedModels)
@@ -201,6 +302,10 @@ class GradlePartialImportingTest : BuildViewMessagesImportingTestCase() {
class TestPartialProjectResolverExtension : AbstractProjectResolverExtension() {
override fun getToolingExtensionsClasses(): Set<Class<*>> {
return setOf(ProjectProperties::class.java)
}
override fun projectsLoaded(models: ModelsHolder<BuildModel, ProjectModel>?) {
val buildFinishedModel = models?.getModel(BuildFinishedModel::class.java)
if (buildFinishedModel != null) {
@@ -229,8 +334,9 @@ class TestPartialProjectResolverExtension : AbstractProjectResolverExtension() {
projectModel: Model,
modelConsumer: ProjectImportModelProvider.ProjectModelConsumer
) {
val model = controller.getModel(projectModel, HashMap::class.java)
modelConsumer.consume(ProjectLoadedModel(model), ProjectLoadedModel::class.java)
val model = controller.getModel(projectModel, ProjectProperties::class.java)
modelConsumer.consume(ProjectLoadedModel(model.propertiesMap.filterKeys { it == "name" || it.startsWith("prop_loaded_") }),
ProjectLoadedModel::class.java)
}
override fun populateBuildModels(
@@ -249,8 +355,9 @@ class TestPartialProjectResolverExtension : AbstractProjectResolverExtension() {
projectModel: Model,
modelConsumer: ProjectImportModelProvider.ProjectModelConsumer
) {
val model = controller.getModel(projectModel, LinkedHashMap::class.java)
modelConsumer.consume(BuildFinishedModel(model), BuildFinishedModel::class.java)
val model = controller.getModel(projectModel, ProjectProperties::class.java)
modelConsumer.consume(BuildFinishedModel(model.propertiesMap.filterKeys { it == "name" || it.startsWith("prop_finished_") }),
BuildFinishedModel::class.java)
}
override fun populateBuildModels(
@@ -263,7 +370,7 @@ class TestPartialProjectResolverExtension : AbstractProjectResolverExtension() {
}
}
internal class TestProjectModelContributor() : ProjectModelContributor {
internal class TestProjectModelContributor : ProjectModelContributor {
override fun accept(
modifiableGradleProjectModel: ModifiableGradleProjectModel,
toolingModelsProvider: ToolingModelsProvider,
@@ -15,6 +15,7 @@ import org.gradle.tooling.ProjectConnection;
import org.gradle.tooling.model.build.BuildEnvironment;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.model.Build;
import org.jetbrains.plugins.gradle.model.data.BuildParticipant;
import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData;
import org.jetbrains.plugins.gradle.model.data.CompositeBuildData;
@@ -25,6 +26,7 @@ import org.jetbrains.plugins.gradle.util.GradleConstants;
import java.io.File;
import java.util.*;
import java.util.stream.Stream;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.*;
import static com.intellij.openapi.util.text.StringUtil.isEmpty;
@@ -110,8 +112,10 @@ public final class GradleBuildSrcProjectsResolver {
jvmOptions.addAll(myMainBuildExecutionSettings.getJvmArguments());
}
for (String buildPath : buildClasspathNodesMap.keySet()) {
Collection<DataNode<BuildScriptClasspathData>> buildClasspathNodes = buildClasspathNodesMap.get(buildPath);
Stream<Build> builds = new ToolingModelsProviderImpl(myResolverContext.getModels()).builds();
builds.forEach(build -> {
String buildPath = build.getBuildIdentifier().getRootDir().getPath();
Collection<DataNode<BuildScriptClasspathData>> buildClasspathNodes = buildClasspathNodesMap.getModifiable(buildPath);
GradleExecutionSettings buildSrcProjectSettings;
if (gradleHome != null) {
@@ -155,7 +159,7 @@ public final class GradleBuildSrcProjectsResolver {
includedModulesPaths,
buildSrcResolverCtx,
myProjectResolver.getProjectDataFunction(buildSrcResolverCtx, myResolverChain, true));
}
});
}
private void handleBuildSrcProject(@NotNull DataNode<ProjectData> resultProjectDataNode,
@@ -477,21 +477,24 @@ public class GradleProjectResolver implements ExternalSystemProjectResolver<Grad
for (Build build : includedBuilds) {
if (!build.getProjects().isEmpty()) {
IdeaProject ideaProject = allModels.getModel(build, IdeaProject.class);
assert ideaProject != null;
if (ideaProject != null) {
gradleIncludedModules.addAll(ideaProject.getModules());
}
String rootProjectName = build.getName();
BuildParticipant buildParticipant = new BuildParticipant();
gradleIncludedModules.addAll(ideaProject.getModules());
try {
String projectPath = toCanonicalPath(build.getBuildIdentifier().getRootDir().getCanonicalPath());
buildParticipant.setRootProjectName(rootProjectName);
buildParticipant.setRootPath(projectPath);
for (IdeaModule module : ideaProject.getModules()) {
try {
String modulePath = toCanonicalPath(module.getGradleProject().getProjectDirectory().getCanonicalPath());
buildParticipant.getProjects().add(modulePath);
}
catch (IOException e) {
LOG.warn("construction of the canonical path for the module fails", e);
if (ideaProject != null) {
for (IdeaModule module : ideaProject.getModules()) {
try {
String modulePath = toCanonicalPath(module.getGradleProject().getProjectDirectory().getCanonicalPath());
buildParticipant.getProjects().add(modulePath);
}
catch (IOException e) {
LOG.warn("construction of the canonical path for the module fails", e);
}
}
}
compositeBuildData.getCompositeParticipants().add(buildParticipant);
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.model;
import com.intellij.openapi.externalSystem.model.ExternalSystemException;
@@ -492,6 +478,14 @@ public class ProjectImportAction implements BuildAction<ProjectImportAction.AllM
public ProjectIdentifier getProjectIdentifier() {
return myProjectIdentifier;
}
@Override
public String toString() {
return "ProjectModel{" +
"name='" + myName + '\'' +
", id=" + myProjectIdentifier +
'}';
}
}
}
@@ -1,3 +1,4 @@
# Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
#
# Copyright 2000-2014 JetBrains s.r.o.
#
@@ -17,3 +18,4 @@ org.jetbrains.plugins.gradle.tooling.internal.ExtraModelBuilderTest$TestModelBui
org.jetbrains.plugins.gradle.tooling.internal.ExtraModelBuilderTest$TestModelBuilderService_v_1_8_or_Above
org.jetbrains.plugins.gradle.tooling.internal.ExtraModelBuilderTest$TestModelBuilderService_between_v_1_8_and_1_9
org.jetbrains.plugins.gradle.tooling.internal.ExtraModelBuilderTest$TestModelBuilderService_between_v_1_8_and_1_9_NotBase
org.jetbrains.plugins.gradle.tooling.builder.ProjectPropertiesTestModelBuilder
@@ -0,0 +1,53 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.tooling.builder;
import org.gradle.api.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder;
import org.jetbrains.plugins.gradle.tooling.ModelBuilderService;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
public class ProjectPropertiesTestModelBuilder implements ModelBuilderService {
@Override
public boolean canBuild(String modelName) {
return ProjectProperties.class.getName().equals(modelName);
}
@Override
public Object buildAll(String modelName, Project project) {
Map<String, String> propertiesMap = new LinkedHashMap<>();
for (Map.Entry<String, ?> entry : project.getProperties().entrySet()) {
propertiesMap.put(entry.getKey(), Objects.toString(entry.getValue(), null));
}
return new ProjectPropertiesImpl(propertiesMap);
}
@NotNull
@Override
public ErrorMessageBuilder getErrorMessageBuilder(@NotNull Project project, @NotNull Exception e) {
return ErrorMessageBuilder
.create(project, e, "Test model import errors")
.withDescription("Unable to import Test model");
}
public interface ProjectProperties {
Map<String, String> getPropertiesMap();
}
public static class ProjectPropertiesImpl implements ProjectProperties, Serializable {
private final Map<String, String> myPropertiesMap;
public ProjectPropertiesImpl(Map<String, String> propertiesMap) {
myPropertiesMap = propertiesMap;
}
@Override
public Map<String, String> getPropertiesMap() {
return myPropertiesMap;
}
}
}