IDEA-390110 [gradle] fix: use partial resolve API for resilient sync result

(cherry picked from commit 5c62d87ca91da9a7b9bd485a0387cc9c41bd9dc8)
IJ-CR-214557

GitOrigin-RevId: 6f4a6a5576e5434fba6a60531638a2ec4ec70f30
This commit is contained in:
Sergei Vorobyov
2026-08-11 10:29:34 +00:00
committed by intellij-monorepo-bot
parent 3f12e6c578
commit 0b87b7a8af
9 changed files with 212 additions and 70 deletions
@@ -1,39 +0,0 @@
// 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 org.assertj.core.api.Assertions.assertThat
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtension
import org.jetbrains.plugins.gradle.tooling.builder.FailingTestModelBuilder
import org.junit.Test
class GradleFailingModelBuilderImportingTest : BuildViewMessagesImportingTestCase() {
override fun setUp() {
super.setUp()
GradleProjectResolverExtension.EP_NAME.point.registerExtension(TestFailingModelBuilderProjectResolver(), testRootDisposable)
}
@Test
fun `test simple project`() {
importProject("")
assertSyncViewTree {
assertNode("finished") {
assertNodeWithDeprecatedGradleWarning()
assertNode("root project 'project': Test import errors")
}
}
assertSyncViewNode("root project 'project': Test import errors") {
assertThat(it).startsWith("""
|Unable to import Test model
|
|java.lang.RuntimeException: Boom! '"{}}${'\n'}${'\t'}
|${'\t'}at org.jetbrains.plugins.gradle.tooling.builder.FailingTestModelBuilder.buildAll(FailingTestModelBuilder.java:
""".trimMargin())
}
}
private class TestFailingModelBuilderProjectResolver : AbstractProjectResolverExtension() {
override fun getToolingExtensionsClasses(): Set<Class<*>> = setOf(FailingTestModelBuilder.Model::class.java)
override fun getExtraProjectModelClasses(): Set<Class<*>> = setOf(FailingTestModelBuilder.Model::class.java)
}
}
@@ -8,10 +8,10 @@ import com.intellij.openapi.util.UserDataHolderBase
import org.gradle.tooling.CancellationToken
import org.gradle.tooling.model.build.BuildEnvironment
import org.gradle.util.GradleVersion
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings
@ApiStatus.Internal
@Internal
open class GradleExecutionContextImpl(
override val projectPath: String,
override val taskId: ExternalSystemTaskId,
@@ -37,8 +37,8 @@ open class GradleExecutionContextImpl(
override val gradleVersion: GradleVersion
get() = GradleVersion.version(buildEnvironment.gradle.gradleVersion)
private var _reporter: GradleExecutionReporter = GradleExecutionReporterImpl(this)
override val reporter: GradleExecutionReporter by ::_reporter
private var _reporter: GradleExecutionReporterImpl = GradleExecutionReporterImpl(this)
override val reporter: GradleExecutionReporterImpl by ::_reporter
constructor(context: GradleExecutionContextImpl) :
this(context, context.projectPath, GradleExecutionSettings(context.settings))
@@ -10,6 +10,7 @@ import com.intellij.openapi.externalSystem.model.task.event.ExternalSystemBuildE
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.registry.Registry
import com.intellij.pom.Navigatable
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.plugins.gradle.issue.GradleIssueChecker
import org.jetbrains.plugins.gradle.issue.GradleIssueData
import org.jetbrains.plugins.gradle.issue.GradleIssueFailure
@@ -19,20 +20,29 @@ import org.jetbrains.plugins.gradle.statistics.GradleModelBuilderMessageCollecto
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.nio.file.Path
import kotlin.io.path.isRegularFile
import java.util.concurrent.atomic.AtomicBoolean
private val LOG = logger<GradleExecutionReporter>()
internal class GradleExecutionReporterImpl(
@Internal
class GradleExecutionReporterImpl(
private val context: GradleExecutionContextImpl,
) : GradleExecutionReporter {
private val hasFailures = AtomicBoolean(false)
private val processedMessageKeys = HashSet<MessageKey>()
private val processedIssueKeys = HashSet<IssueKey>()
fun hasFailures(): Boolean = hasFailures.get()
override fun failure(failure: GradleIssueFailure): GradleExecutionFailureReport =
GradleExecutionFailureReportImpl(this, failure)
private fun reportFailure(report: GradleExecutionFailureReportImpl) {
if (report.kind == MessageEvent.Kind.ERROR) {
hasFailures.set(true)
}
reportFusEvent(report)
reportLoggerEvent(report)
reportBuildEvent(report)
@@ -12,6 +12,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.importing.ProjectResolverPolicy;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ExternalSystemException;
import com.intellij.openapi.externalSystem.service.internal.ExternalSystemPartialResolutionException;
import com.intellij.openapi.externalSystem.model.ProjectKeys;
import com.intellij.openapi.externalSystem.model.project.ContentRootData;
import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType;
@@ -212,6 +213,10 @@ public final class GradleProjectResolver implements ExternalSystemProjectResolve
.discoverAndAppendTo(projectDataNode);
}
if (resolverContext.getReporter().hasFailures()) {
throw new ExternalSystemPartialResolutionException(projectDataNode);
}
return projectDataNode;
}
catch (CancellationException ce) {
@@ -9,6 +9,7 @@ import com.intellij.gradle.toolingExtension.util.GradleVersionUtil
import com.intellij.openapi.Disposable
import com.intellij.platform.testFramework.assertion.treeAssertion.SimpleTreeAssertion
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.RegistryKey
import com.intellij.testFramework.junit5.fixture.tempPathFixture
import com.intellij.util.asDisposable
import kotlinx.coroutines.runBlocking
@@ -45,6 +46,7 @@ import java.util.function.Consumer
@TestApplication
@ParameterizedClass
@AllGradleVersionsSource
@RegistryKey("gradle.use.resilient.model.fetch.unstable", true.toString())
class GradleBuildIssueImportingTest(private val gradleVersion: GradleVersion) {
private val testRootFixture = tempPathFixture()
@@ -81,7 +83,7 @@ class GradleBuildIssueImportingTest(private val gradleVersion: GradleVersion) {
gradle.linkProject(project, projectRoot)
buildView.assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertFilePositionNode(gradleVersion, gradleDsl, brokenFile) {
assertNode(TEST_BUILD_ISSUE_TITLE)
@@ -108,7 +110,7 @@ class GradleBuildIssueImportingTest(private val gradleVersion: GradleVersion) {
gradle.linkProject(project, projectRoot)
buildView.assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertFilePositionNode(gradleVersion, gradleDsl, BrokenFile.BUILD_SCRIPT) {
assertNode(TEST_BUILD_ISSUE_TITLE)
@@ -2,6 +2,7 @@
package org.jetbrains.plugins.gradle.importing
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.RegistryKey
import com.intellij.testFramework.junit5.fixture.tempPathFixture
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
@@ -26,6 +27,7 @@ import java.util.function.Consumer
@TestApplication
@ParameterizedClass
@AllGradleVersionsSource
@RegistryKey("gradle.use.resilient.model.fetch.unstable", true.toString())
class GradleBuildIssuesMiscImportingTest(private val gradleVersion: GradleVersion) {
private val testRootFixture = tempPathFixture()
@@ -73,7 +75,7 @@ class GradleBuildIssuesMiscImportingTest(private val gradleVersion: GradleVersio
assertAnyOf({
buildView.assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertNode("build.gradle") {
assertNode("(Java heap space|GC overhead limit exceeded)".toRegex())
@@ -82,7 +84,7 @@ class GradleBuildIssuesMiscImportingTest(private val gradleVersion: GradleVersio
}
}, {
buildView.assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertNode("(Java heap space|GC overhead limit exceeded)".toRegex())
}
@@ -28,7 +28,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
importProject("subprojects { apply plugin: 'java' }")
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("build.gradle") {
assertNode("Could not find method ghostConf() for arguments [project ':api'] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler")
@@ -55,7 +55,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
importProject("apply plugin: example.SomePlugin")
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode(":buildSrc:compileJava")
assertNode(":buildSrc:compileGroovy")
@@ -270,7 +270,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
addBuildScriptDependency("classpath 'junit:junit:4.12'")
}
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("Could Not Resolve junit:junit:4.12 because no repositories are defined")
}
@@ -315,7 +315,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
addBuildScriptDependency("classpath 'junit:junit:99.99'")
}
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("Could Not Resolve junit:junit:99.99")
}
@@ -345,7 +345,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
addBuildScriptDependency("classpath 'junit:junit:99.99'")
}
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("Could Not Resolve junit:junit:99.99")
}
@@ -375,7 +375,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
when {
isGradleOlderThan("7.0") -> {
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("build.gradle") {
assertNode("expecting ''', found '\\n'")
@@ -385,7 +385,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
}
isGradleOlderThan("7.4") -> {
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("build.gradle") {
assertNode("Unexpected input: '{'")
@@ -395,7 +395,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
}
else -> {
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("build.gradle") {
assertNode("Unexpected character: '\\''")
@@ -414,7 +414,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
)
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("build.gradle") {
assertNode("only buildscript {}" +
@@ -438,7 +438,7 @@ class GradleOutputParsersMessagesImportingTest : GradleOutputParsersMessagesImpo
)
assertSyncViewTree {
assertNode("(failed|finished)".toRegex()) {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning()
assertNode("build.gradle") {
assertNode("Cannot get property 'foo' on null object")
@@ -1,13 +1,21 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.importing
import com.intellij.platform.testFramework.assertion.treeAssertion.SimpleTreeAssertion.NodeMatcher.Companion.or
import com.intellij.gradle.toolingExtension.util.GradleVersionUtil
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.registry.Registry
import com.intellij.testFramework.junit5.RegistryKey
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.TestDisposable
import com.intellij.testFramework.junit5.fixture.tempPathFixture
import com.intellij.util.asDisposable
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.gradle.util.GradleVersion
import org.jetbrains.plugins.gradle.frameworkSupport.buildscript.isTaskConfigurationAvoidanceSupported
import org.jetbrains.plugins.gradle.importing.BuildViewMessagesImportingTestCase.Companion.assertNodeWithDeprecatedGradleWarning
import org.jetbrains.plugins.gradle.importing.syncAction.GradleProjectResolverTestCase.TestProjectResolverExtension
import org.jetbrains.plugins.gradle.importing.syncAction.registerProjectResolverExtension
import org.jetbrains.plugins.gradle.testFramework.annotations.AllGradleVersionsSource
import org.jetbrains.plugins.gradle.testFramework.fixtures.buildViewFixture
import org.jetbrains.plugins.gradle.testFramework.fixtures.gradleFixture
@@ -16,17 +24,20 @@ import org.jetbrains.plugins.gradle.testFramework.projectInfo.buildFile
import org.jetbrains.plugins.gradle.testFramework.projectInfo.gradleProjectInfo
import org.jetbrains.plugins.gradle.testFramework.projectInfo.gradleWrapper
import org.jetbrains.plugins.gradle.testFramework.projectInfo.initProject
import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions
import org.jetbrains.plugins.gradle.tooling.builder.FailingTestModelBuilder
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedClass
@TestApplication
@ParameterizedClass
@AllGradleVersionsSource
@TargetVersions("9.3+")
@AllGradleVersionsSource("true,false")
@RegistryKey("gradle.use.resilient.model.fetch.unstable", true.toString())
@RegistryKey("gradle.show.suppressed.failure.events", true.toString())
class GradleSyncOutputFailureTest(private val gradleVersion: GradleVersion) {
class GradleSyncOutputFailureTest(
private val gradleVersion: GradleVersion,
private val showSuppressedFailures: Boolean,
) {
private val testRootFixture = tempPathFixture()
private val testRoot by testRootFixture
@@ -39,6 +50,38 @@ class GradleSyncOutputFailureTest(private val gradleVersion: GradleVersion) {
private val buildView by buildViewFixture(projectFixture)
@BeforeEach
fun setUp(@TestDisposable disposable: Disposable) {
Registry.get("gradle.show.suppressed.failure.events")
.setValue(showSuppressedFailures, disposable)
Assumptions.assumeFalse(
GradleVersionUtil.isGradleAtLeast(gradleVersion, "4.8") &&
GradleVersionUtil.isGradleOlderThan(gradleVersion, "5.1")
) {
"The exact output is too unstable to assert reliably:" +
" Gradle 4.8–5.x introduced Task Configuration Avoidance and a series of error-message reforms in a short window."
}
}
@Test
fun `test sync reports success`(): Unit = runBlocking {
val projectInfo = gradleProjectInfo(gradleVersion) {
gradleWrapper()
buildFile { }
}
val projectRoot = projectInfo.initProject(testRoot)
gradle.linkProject(project, projectRoot)
buildView.assertSyncViewTree {
assertNode("finished") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
}
}
}
@Test
fun `test sync reports task initialization failure`(): Unit = runBlocking {
val projectInfo = gradleProjectInfo(gradleVersion) {
@@ -60,12 +103,114 @@ class GradleSyncOutputFailureTest(private val gradleVersion: GradleVersion) {
gradle.linkProject(project, projectRoot)
buildView.assertSyncViewTree {
assertNode("failed" or "finished") {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertNode("build.gradle.kts") {
assertNode("Could not create task ':brokenIdeaProjectTask'.")
when {
!isTaskConfigurationAvoidanceSupported(gradleVersion) -> {
assertNode("Could not create task of type 'BrokenIdeaProjectTask'")
}
!isResilientSyncEnabled(gradleVersion) -> {
assertNode("Could not create task ':brokenIdeaProjectTask'.")
}
else -> {
if (showSuppressedFailures) {
assertNode("build.gradle.kts") {
assertNode("Could not create task ':brokenIdeaProjectTask'.")
}
}
}
}
}
}
}
@Test
fun `test sync reports build script compilation failure`(): Unit = runBlocking {
val projectInfo = gradleProjectInfo(gradleVersion) {
gradleWrapper()
buildFile {
addPostfix("""
|dependencies {
""".trimMargin())
}
}
val projectRoot = projectInfo.initProject(testRoot)
gradle.linkProject(project, projectRoot)
buildView.assertSyncViewTree {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertNode("build.gradle.kts") {
if (isResilientSyncEnabled(gradleVersion) && showSuppressedFailures) {
assertNode("A problem occurred configuring root project 'project'.")
}
assertNode("Expecting '}'")
}
}
}
}
@Test
fun `test sync reports dependency resolution failure`(): Unit = runBlocking {
val projectInfo = gradleProjectInfo(gradleVersion) {
gradleWrapper()
buildFile {
withJavaPlugin()
addImplementationDependency("abc:abc:123")
}
}
val projectRoot = projectInfo.initProject(testRoot)
gradle.linkProject(project, projectRoot)
buildView.assertSyncViewTree {
assertNode("finished") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertNode("Could Not Resolve abc:abc:123 for project:main")
assertNode("Could Not Resolve abc:abc:123 for project:test")
}
}
}
@Test
fun `test sync reports model builder failure`(): Unit = runBlocking {
project.registerProjectResolverExtension(TestProjectResolverExtension::class.java, asDisposable()) {
addProjectModelClass(FailingTestModelBuilder.Model::class.java)
addToolingExtensionClass(FailingTestModelBuilder::class.java)
}
val projectInfo = gradleProjectInfo(gradleVersion) {
gradleWrapper()
buildFile { }
}
val projectRoot = projectInfo.initProject(testRoot)
gradle.linkProject(project, projectRoot)
buildView.assertSyncViewTree {
assertNode("failed") {
assertNodeWithDeprecatedGradleWarning(gradleVersion)
assertNode("root project 'project': Test import errors")
}
}
buildView.assertSyncViewNode("root project 'project': Test import errors") {
assertThat(it).startsWith("""
|Unable to import Test model
|
|java.lang.RuntimeException: Boom! '"{}}${'\n'}${'\t'}
|${'\t'}at org.jetbrains.plugins.gradle.tooling.builder.FailingTestModelBuilder.buildAll(FailingTestModelBuilder.java:
""".trimMargin())
}
}
companion object {
private fun isResilientSyncEnabled(gradleVersion: GradleVersion): Boolean =
GradleVersionUtil.isGradleAtLeast(gradleVersion, "9.3")
}
}
@@ -2,6 +2,7 @@
package org.jetbrains.plugins.gradle.importing.syncAction
import com.intellij.gradle.toolingExtension.modelAction.GradleModelFetchPhase
import com.intellij.gradle.toolingExtension.modelProvider.GradleClassProjectModelProvider
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.ComponentManager
import com.intellij.platform.workspace.storage.ImmutableEntityStorage
@@ -14,7 +15,6 @@ import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncContributor
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncListener
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncPhase
import java.util.concurrent.CopyOnWriteArrayList
fun whenSyncPhaseCompleted(
parentDisposable: Disposable,
@@ -128,21 +128,38 @@ abstract class AbstractTestProjectResolverExtension : AbstractProjectResolverExt
override fun getModelProviders(): List<ProjectImportModelProvider> {
return getService().getModelProviders()
}
override fun getToolingExtensionsClasses(): Set<Class<*>> {
return getService().getToolingExtensionsClasses()
}
}
abstract class AbstractTestProjectResolverService {
private val modelProviders = CopyOnWriteArrayList<ProjectImportModelProvider>()
private val modelProviders = ArrayList<ProjectImportModelProvider>()
private val toolingExtensionClasses = LinkedHashSet<Class<*>>()
fun getModelProviders(): List<ProjectImportModelProvider> {
return modelProviders
}
fun getToolingExtensionsClasses(): Set<Class<*>> {
return toolingExtensionClasses
}
fun addProjectModelClass(modelClass: Class<*>) {
modelProviders.add(GradleClassProjectModelProvider(modelClass))
}
fun addModelProviders(vararg modelProviders: ProjectImportModelProvider) {
addModelProviders(modelProviders.toList())
this.modelProviders.addAll(modelProviders)
}
fun addModelProviders(modelProviders: Collection<ProjectImportModelProvider>) {
this.modelProviders.addAll(modelProviders)
}
fun addToolingExtensionClass(toolingExtensionClass: Class<*>) {
toolingExtensionClasses.add(toolingExtensionClass)
}
}