diff --git a/plugins/gradle/tooling-extension-api/src/com/intellij/gradle/toolingExtension/modelAction/GradleModelController.kt b/plugins/gradle/tooling-extension-api/src/com/intellij/gradle/toolingExtension/modelAction/GradleModelController.kt index a3bbbe479c3f..eadf0cc1dc82 100644 --- a/plugins/gradle/tooling-extension-api/src/com/intellij/gradle/toolingExtension/modelAction/GradleModelController.kt +++ b/plugins/gradle/tooling-extension-api/src/com/intellij/gradle/toolingExtension/modelAction/GradleModelController.kt @@ -4,6 +4,7 @@ package com.intellij.gradle.toolingExtension.modelAction import org.gradle.api.Action import org.gradle.tooling.model.gradle.BasicGradleProject import org.gradle.tooling.model.gradle.GradleBuild +import org.jetbrains.annotations.ApiStatus.Experimental import org.jetbrains.annotations.ApiStatus.NonExtendable import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider.GradleModelConsumer import org.gradle.tooling.model.Model as GradleModel @@ -81,6 +82,18 @@ interface GradleModelController { parameterInitializer: Action, ): GradleModelFetchRequest + /** + * Suppresses failures for this model fetch request. + * + * When suppressed: + * - With the resilient Gradle API: failures returned by [org.gradle.tooling.BuildController.fetch] are not forwarded. + * - Without the resilient Gradle API: exceptions thrown by [org.gradle.tooling.BuildController.getModel] are caught and discarded. + * + * Use this when the model may not be available in all Gradle configurations. + */ + @Experimental + fun suppressFailures(): GradleModelFetchRequest + fun execute(modelConsumer: GradleModelConsumer) enum class GradleExecutionMode { diff --git a/plugins/gradle/tooling-extension-impl/src/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerImpl.kt b/plugins/gradle/tooling-extension-impl/src/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerImpl.kt index f293c51e7901..60c036129831 100644 --- a/plugins/gradle/tooling-extension-impl/src/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerImpl.kt +++ b/plugins/gradle/tooling-extension-impl/src/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerImpl.kt @@ -54,7 +54,7 @@ class GradleModelControllerImpl( error("Strict Gradle model fetch unexpectedly returned invalid model for ${modelClass.name}") } - sendModelFetchFailures(null, result) + sendModelFetchFailures(null, result, false) return model } @@ -63,7 +63,7 @@ class GradleModelControllerImpl( return buildController.findModel(modelClass) } return buildController.fetch(modelClass) - .also { sendModelFetchFailures(null, it) } + .also { sendModelFetchFailures(null, it, false) } .getModel() } @@ -71,23 +71,19 @@ class GradleModelControllerImpl( target: Target, modelClass: Class, modelParameter: GradleModelParameter<*>?, + suppressFailures: Boolean, ): Model? { if (modelParameter != null) { - return fetchModelOrNull(target, modelClass, modelParameter.parameterClass, modelParameter.parameterInitializer) + return fetchModelOrNull(target, modelClass, modelParameter.parameterClass, modelParameter.parameterInitializer, suppressFailures) } - return fetchModelOrNull(target, modelClass) + return fetchModelOrNull(target, modelClass, suppressFailures) } override fun fetchModelOrNull( target: Target, modelClass: Class, ): Model? { - if (!isResilientModelFetchApiUsed()) { - return buildController.findModel(target, modelClass) - } - return buildController.fetch(target, modelClass) - .also { sendModelFetchFailures(target, it) } - .getModel() + return fetchModelOrNull(target, modelClass, false) } override fun fetchModelOrNull( @@ -95,12 +91,39 @@ class GradleModelControllerImpl( modelClass: Class, modelParameterClass: Class, modelParameterInitializer: Action, + ): Model? { + return fetchModelOrNull(target, modelClass, modelParameterClass, modelParameterInitializer, false) + } + + private fun fetchModelOrNull( + target: Target, + modelClass: Class, + suppressFailures: Boolean, ): Model? { if (!isResilientModelFetchApiUsed()) { - return buildController.findModel(target, modelClass, modelParameterClass, modelParameterInitializer) + return handleModelFetchFailures(suppressFailures) { + buildController.findModel(target, modelClass) + } + } + return buildController.fetch(target, modelClass) + .also { sendModelFetchFailures(target, it, suppressFailures) } + .getModel() + } + + private fun fetchModelOrNull( + target: Target, + modelClass: Class, + modelParameterClass: Class, + modelParameterInitializer: Action, + suppressFailures: Boolean, + ): Model? { + if (!isResilientModelFetchApiUsed()) { + return handleModelFetchFailures(suppressFailures) { + buildController.findModel(target, modelClass, modelParameterClass, modelParameterInitializer) + } } return buildController.fetch(target, modelClass, modelParameterClass, modelParameterInitializer) - .also { sendModelFetchFailures(target, it) } + .also { sendModelFetchFailures(target, it, suppressFailures) } .getModel() } @@ -109,14 +132,35 @@ class GradleModelControllerImpl( private fun fetchModels(request: GradleModelFetchRequestImpl, modelConsumer: GradleModelConsumer) { + fun fetchTargetModelsInParallel(targets: Collection, consumer: (Target, Model, Class) -> Unit) { + val buildActions = targets.map { target -> + BuildAction { innerBuildController -> + val innerController = GradleModelControllerImpl(innerBuildController) + val model = innerController.fetchModelOrNull(target, request.modelClass, request.modelParameter, request.suppressFailures) + target to model + } + } + val models = buildController.run(buildActions) + for ((target, model) in models) { + consumer(target, model ?: continue, request.modelClass) + } + } + + fun fetchTargetModelsInSequence(targets: Collection, consumer: (Target, Model, Class) -> Unit) { + for (target in targets) { + val model = fetchModelOrNull(target, request.modelClass, request.modelParameter, request.suppressFailures) + consumer(target, model ?: continue, request.modelClass) + } + } + fun fetchModels(targets: Collection, consumer: (Target, Model, Class) -> Unit) { when (request.executionMode) { GradleExecutionMode.DEFAULT -> when (isParallelModelFetchEnabled()) { - true -> fetchTargetModelsInParallel(targets, request.modelClass, request.modelParameter, consumer) - else -> fetchTargetModelsInSequence(targets, request.modelClass, request.modelParameter, consumer) + true -> fetchTargetModelsInParallel(targets, consumer) + else -> fetchTargetModelsInSequence(targets, consumer) } - GradleExecutionMode.PARALLEL -> fetchTargetModelsInParallel(targets, request.modelClass, request.modelParameter, consumer) - GradleExecutionMode.SEQUENTIAL -> fetchTargetModelsInSequence(targets, request.modelClass, request.modelParameter, consumer) + GradleExecutionMode.PARALLEL -> fetchTargetModelsInParallel(targets, consumer) + GradleExecutionMode.SEQUENTIAL -> fetchTargetModelsInSequence(targets, consumer) } } @@ -134,39 +178,6 @@ class GradleModelControllerImpl( } } - private fun fetchTargetModelsInParallel( - targets: Collection, - modelClass: Class, - modelParameter: GradleModelParameter<*>?, - modelConsumer: (Target, Model, Class) -> Unit, - ) { - val buildActions = targets.map { target -> - BuildAction { innerBuildController -> - val innerController = GradleModelControllerImpl(innerBuildController) - val model = innerController.fetchModelOrNull(target, modelClass, modelParameter) - target to (model ?: return@BuildAction null) - } - } - val models = buildController.run(buildActions) - for (model in models) { - if (model != null) { - modelConsumer(model.first, model.second, modelClass) - } - } - } - - private fun fetchTargetModelsInSequence( - targets: Collection, - modelClass: Class, - modelParameter: GradleModelParameter<*>?, - modelConsumer: (Target, Model, Class) -> Unit, - ) { - for (target in targets) { - val model = fetchModelOrNull(target, modelClass, modelParameter) ?: continue - modelConsumer(target, model, modelClass) - } - } - private fun collectAllProjectModels(buildModels: Collection): Collection { val projectModels = ArrayList() for (buildModel in buildModels) { @@ -196,8 +207,12 @@ class GradleModelControllerImpl( return buildModel.projects.filter { it.parent == null } } - private fun sendModelFetchFailures(target: GradleModel?, result: FetchModelResult<*>) { - val failures = result.failures.takeIf { it.isNotEmpty() } ?: return + private fun handleModelFetchFailures(suppressFailures: Boolean, action: () -> Model?): Model? { + return runCatching(action).getOrElse { if (!suppressFailures) throw it else null } + } + + private fun sendModelFetchFailures(target: GradleModel?, result: FetchModelResult<*>, suppressFailures: Boolean) { + val failures = result.failures.takeIf { it.isNotEmpty() && !suppressFailures } ?: return val targetPath = when (target) { is BasicGradleProject -> target.projectDirectory is GradleBuild -> target.buildIdentifier.rootDir @@ -215,6 +230,7 @@ class GradleModelControllerImpl( val executionMode: GradleExecutionMode = GradleExecutionMode.DEFAULT, val targetLevel: GradleModelLevel = GradleModelLevel.PROJECT, val projectTraversal: GradleTraversalMode = GradleTraversalMode.DIRECT, + val suppressFailures: Boolean = false, ) : GradleModelFetchRequest { override fun modelLevel(targetLevel: GradleModelLevel): GradleModelFetchRequest = @@ -232,6 +248,9 @@ class GradleModelControllerImpl( ): GradleModelFetchRequest = copy(modelParameter = GradleModelParameter(parameterClass, parameterInitializer)) + override fun suppressFailures(): GradleModelFetchRequest = + copy(suppressFailures = true) + override fun execute(modelConsumer: GradleModelConsumer): Unit = modelController.fetchModels(this, modelConsumer) } diff --git a/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerTest.kt b/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerTest.kt index 5cd3eb11837b..9ba0061bfdee 100644 --- a/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerTest.kt +++ b/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/GradleModelControllerTest.kt @@ -283,6 +283,32 @@ class GradleModelControllerTest(val isResilientSyncEnabled: Boolean) { modelConsumer.assertProjectModels(emptyList()) } + @Test + fun `suppressed model fetch request suppresses all failures`() { + val rootProject = MockGradleProject("root") + val subProject = MockGradleProject("sub-project", rootProject) + val projectModels = listOf(rootProject, subProject) + val buildModel = MockGradleBuild(rootProject, projectModels) + + val buildController = TestBuildController().apply { + registerModelFailure(rootProject, TestModel::class.java, TestModelFetchException()) + } + val modelConsumer = TestModelConsumer() + + GradleModelControllerImpl(buildController) + .fetchRequest(listOf(buildModel), TestModel::class.java) + .suppressFailures() + .execute(modelConsumer) + + buildController.assertRunActionCounts(emptyList()) + buildController.assertModelRequests(projectModels.map { + TestModelRequest(it, TestModel::class.java) + }) + + modelConsumer.assertBuildModels(emptyList()) + modelConsumer.assertProjectModels(emptyList()) + } + @Test fun `resilient model fetch api propagates project directory as target path in failure result`() { val projectDirectory = File("root-dir") diff --git a/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/TestBuildController.kt b/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/TestBuildController.kt index d5f011f6a30e..c0ca4a4a8e92 100644 --- a/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/TestBuildController.kt +++ b/plugins/gradle/tooling-extension-impl/testSources/com/intellij/gradle/toolingExtension/impl/modelAction/TestBuildController.kt @@ -53,21 +53,34 @@ internal class TestBuildController : BuildController by notImplemented(BuildCont assertEqualsOrdered(expectedTargetPaths, actualTargetPaths) } - override fun findModel(target: Model?, modelType: Class): T? { - modelRequests.add(TestModelRequest(target!!, modelType)) + override fun getModel(target: Model, modelType: Class): T { + return findModel(target, modelType) ?: error("No model of type ${modelType.name} for $target") + } + + override fun getModel( + target: Model, + modelType: Class, + parameterType: Class

, + parameterInitializer: Action, + ): T { + return findModel(target, modelType, parameterType, parameterInitializer) ?: error("No model of type ${modelType.name} for $target") + } + + override fun findModel(target: Model, modelType: Class): T? { + modelRequests.add(TestModelRequest(target, modelType)) throwModelFailure(target, modelType) @Suppress("UNCHECKED_CAST") return models[target to modelType] as T? } override fun findModel( - target: Model?, + target: Model, modelType: Class, - parameterType: Class

?, - parameterInitializer: Action?, + parameterType: Class

, + parameterInitializer: Action, ): T? { - val parameter = createParameter(parameterType!!, parameterInitializer!!) - modelRequests.add(TestModelRequest(target!!, modelType, parameterType, parameter)) + val parameter = createParameter(parameterType, parameterInitializer) + modelRequests.add(TestModelRequest(target, modelType, parameterType, parameter)) throwModelFailure(target, modelType) @Suppress("UNCHECKED_CAST") return models[target to modelType] as T? @@ -87,26 +100,16 @@ internal class TestBuildController : BuildController by notImplemented(BuildCont return parameter } - override fun fetch(modelType: Class): FetchModelResult = - fetchModelResult { findModel(modelType) } - override fun fetch(target: Model, modelType: Class): FetchModelResult = fetchModelResult { findModel(target, modelType) } - override fun fetch( - modelType: Class, - parameterType: Class

?, - parameterInitializer: Action?, - ): FetchModelResult = - fetchModelResult { findModel(modelType, parameterType, parameterInitializer) } - override fun fetch( target: Model?, modelType: Class, parameterType: Class

?, parameterInitializer: Action?, ): FetchModelResult = - fetchModelResult { findModel(target, modelType, parameterType, parameterInitializer) } + fetchModelResult { findModel(target!!, modelType, parameterType!!, parameterInitializer!!) } override fun run(actions: Collection>): List { runActionCounts.add(actions.size)