[Gradle] Don't run post-evaluation-checks on projects that failed to configure

When in 'lenient' or 'classpath' mode, Gradle will catch exceptions
during project evaluation and collect them into the
'ClassPathModeExceptionCollector'.

Running checks on the project after evaluation should only happen
if the project is properly configured (aka no such exceptions collected)
to prevent false positive error reports (that will then fail
the Gradle process and swallow the real cause)

^KT-48823 Verification Pending
This commit is contained in:
sebastian.sellmair
2021-09-27 10:41:37 +02:00
committed by Space
parent e4827efb4a
commit 29fb74d27d
13 changed files with 157 additions and 18 deletions
@@ -1848,6 +1848,30 @@ class NewMultiplatformIT : BaseGradleIT() {
} }
} }
@Test
fun testErrorInClasspathMode() {
val classpathModeOptions = defaultBuildOptions().copy(
freeCommandLineArgs = listOf("-Dorg.gradle.kotlin.dsl.provider.mode=classpath")
)
with(Project("kotlin-mpp-classpathMode")) {
build("tasks") {
assertFailed()
assertContains("ERROR DURING CONFIGURATION PHASE")
}
build("tasks", options = classpathModeOptions) {
assertSuccessful()
}
build("listCollectedErrors", options = classpathModeOptions) {
assertSuccessful()
assertContains("Collected 1 exception(s)")
assertContains("ERROR DURING CONFIGURATION PHASE")
}
}
}
private fun detectNativeEnabledCompilation(): String = when { private fun detectNativeEnabledCompilation(): String = when {
HostManager.hostIsLinux -> "linuxX64" HostManager.hostIsLinux -> "linuxX64"
HostManager.hostIsMingw -> "mingwX64" HostManager.hostIsMingw -> "mingwX64"
@@ -0,0 +1,20 @@
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.konan.target.HostManager
import org.gradle.kotlin.dsl.support.serviceOf
import org.gradle.kotlin.dsl.provider.ClassPathModeExceptionCollector
plugins {
kotlin("multiplatform")
}
tasks.create("listCollectedErrors") {
doFirst {
val exceptionCollector = serviceOf<ClassPathModeExceptionCollector>()
logger.quiet("Collected ${exceptionCollector.exceptions.size} exception(s)")
exceptionCollector.exceptions.forEach { e ->
logger.log(LogLevel.ERROR, "Exception: ${e.message}", e)
}
}
}
error("ERROR DURING CONFIGURATION PHASE")
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
val kotlin_version: String by settings
plugins {
kotlin("multiplatform").version(kotlin_version)
}
}
@@ -26,6 +26,7 @@ pill {
} }
dependencies { dependencies {
compileOnly(gradleKotlinDsl())
api(project(":kotlin-gradle-plugin-api")) api(project(":kotlin-gradle-plugin-api"))
api(project(":kotlin-gradle-plugin-model")) api(project(":kotlin-gradle-plugin-model"))
compileOnly(project(":compiler")) compileOnly(project(":compiler"))
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.gradle.testing.internal.KotlinTestsRegistry
import org.jetbrains.kotlin.gradle.tooling.buildKotlinToolingMetadataTask import org.jetbrains.kotlin.gradle.tooling.buildKotlinToolingMetadataTask
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
import org.jetbrains.kotlin.statistics.metrics.StringMetrics import org.jetbrains.kotlin.statistics.metrics.StringMetrics
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -201,7 +202,7 @@ open class KotlinJsPluginWrapper : KotlinBasePluginWrapper() {
override val projectExtensionClass: KClass<out KotlinJsProjectExtension> override val projectExtensionClass: KClass<out KotlinJsProjectExtension>
get() = KotlinJsProjectExtension::class get() = KotlinJsProjectExtension::class
override fun whenBuildEvaluated(project: Project) { override fun whenBuildEvaluated(project: Project) = project.runProjectConfigurationHealthCheck {
val isJsTargetUninitialized = (project.kotlinExtension as KotlinJsProjectExtension) val isJsTargetUninitialized = (project.kotlinExtension as KotlinJsProjectExtension)
._target == null ._target == null
@@ -232,7 +233,7 @@ open class KotlinMultiplatformPluginWrapper : KotlinBasePluginWrapper() {
override val projectExtensionClass: KClass<out KotlinMultiplatformExtension> override val projectExtensionClass: KClass<out KotlinMultiplatformExtension>
get() = KotlinMultiplatformExtension::class get() = KotlinMultiplatformExtension::class
override fun whenBuildEvaluated(project: Project) { override fun whenBuildEvaluated(project: Project) = project.runProjectConfigurationHealthCheck {
val isNoTargetsInitialized = (project.kotlinExtension as KotlinMultiplatformExtension) val isNoTargetsInitialized = (project.kotlinExtension as KotlinMultiplatformExtension)
.targets .targets
.none { it !is KotlinMetadataTarget } .none { it !is KotlinMetadataTarget }
@@ -26,18 +26,16 @@ import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companion.sourceSetFreeCompilerArgsPropertyName import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companion.sourceSetFreeCompilerArgsPropertyName
import org.jetbrains.kotlin.gradle.plugin.mpp.internal.handleHierarchicalStructureFlagsMigration import org.jetbrains.kotlin.gradle.plugin.mpp.internal.handleHierarchicalStructureFlagsMigration
import org.jetbrains.kotlin.gradle.plugin.sources.* import org.jetbrains.kotlin.gradle.plugin.sources.CleanupStaleSourceSetMetadataEntriesService
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.sources.SourceSetMetadataStorageForIde
import org.jetbrains.kotlin.gradle.plugin.sources.checkSourceSetVisibilityRequirements
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset
import org.jetbrains.kotlin.gradle.tasks.locateTask import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.SingleActionPerBuild
import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget.* import org.jetbrains.kotlin.konan.target.KonanTarget.*
import org.jetbrains.kotlin.konan.target.presetName import org.jetbrains.kotlin.konan.target.presetName
@@ -229,7 +227,7 @@ class KotlinMultiplatformPlugin : Plugin<Project> {
UnusedSourceSetsChecker.checkSourceSets(project) UnusedSourceSetsChecker.checkSourceSets(project)
project.whenEvaluated { project.runPostEvaluationProjectConfigurationHealthCheck {
checkSourceSetVisibilityRequirements(project) checkSourceSetVisibilityRequirements(project)
} }
} }
@@ -11,7 +11,8 @@ import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.util.ConfigureUtil import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.mpp.toModuleDependency import org.jetbrains.kotlin.gradle.plugin.mpp.toModuleDependency
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
@@ -19,6 +20,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.FragmentConsistencyChecker
import org.jetbrains.kotlin.gradle.plugin.sources.FragmentConsistencyChecks import org.jetbrains.kotlin.gradle.plugin.sources.FragmentConsistencyChecks
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.runPostEvaluationProjectConfigurationHealthCheck
import org.jetbrains.kotlin.project.model.KotlinModuleDependency import org.jetbrains.kotlin.project.model.KotlinModuleDependency
import org.jetbrains.kotlin.project.model.KotlinModuleFragment import org.jetbrains.kotlin.project.model.KotlinModuleFragment
import org.jetbrains.kotlin.project.model.refinesClosure import org.jetbrains.kotlin.project.model.refinesClosure
@@ -54,7 +56,7 @@ open class KotlinGradleFragmentInternal @Inject constructor(
project.addExtendsFromRelation(getConfiguration(this), getConfiguration(other.get())) // todo eager instantiation; fix? project.addExtendsFromRelation(getConfiguration(this), getConfiguration(other.get())) // todo eager instantiation; fix?
} }
project.whenEvaluated { project.runPostEvaluationProjectConfigurationHealthCheck {
kotlinGradleFragmentConsistencyChecker.runAllChecks(this@KotlinGradleFragmentInternal, other.get()) kotlinGradleFragmentConsistencyChecker.runAllChecks(this@KotlinGradleFragmentInternal, other.get())
} }
} }
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
import java.io.File import java.io.File
import java.util.* import java.util.*
@@ -93,7 +92,9 @@ class DefaultKotlinSourceSet(
// Fail-fast approach: check on each new added edge and report a circular dependency at once when the edge is added. // Fail-fast approach: check on each new added edge and report a circular dependency at once when the edge is added.
checkForCircularDependencies() checkForCircularDependencies()
project.whenEvaluated { defaultSourceSetLanguageSettingsChecker.runAllChecks(this@DefaultKotlinSourceSet, other) } project.runPostEvaluationProjectConfigurationHealthCheck {
defaultSourceSetLanguageSettingsChecker.runAllChecks(this@DefaultKotlinSourceSet, other)
}
} }
private val dependsOnSourceSetsImpl = mutableSetOf<KotlinSourceSet>() private val dependsOnSourceSetsImpl = mutableSetOf<KotlinSourceSet>()
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPI
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsSingleTargetPreset import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsSingleTargetPreset
import org.jetbrains.kotlin.gradle.plugin.mpp.setupGeneralKotlinExtensionParameters import org.jetbrains.kotlin.gradle.plugin.mpp.setupGeneralKotlinExtensionParameters
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrSingleTargetPreset import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrSingleTargetPreset
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
@@ -47,7 +46,7 @@ open class KotlinJsPlugin(
defaultJsCompilerType = PropertiesProvider(project).jsCompiler defaultJsCompilerType = PropertiesProvider(project).jsCompiler
} }
project.whenEvaluated { project.runPostEvaluationProjectConfigurationHealthCheck {
if (kotlinExtension._target == null) { if (kotlinExtension._target == null) {
project.logger.warn( project.logger.warn(
""" """
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTargetConfigurator import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTargetConfigurator
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.runPostEvaluationProjectConfigurationHealthCheck
import org.jetbrains.kotlin.statistics.metrics.StringMetrics import org.jetbrains.kotlin.statistics.metrics.StringMetrics
open class KotlinJsTargetPreset( open class KotlinJsTargetPreset(
@@ -54,7 +55,7 @@ open class KotlinJsTargetPreset(
} }
this.isMpp = this@KotlinJsTargetPreset.isMpp this.isMpp = this@KotlinJsTargetPreset.isMpp
project.whenEvaluated { project.runPostEvaluationProjectConfigurationHealthCheck {
if (!isBrowserConfigured && !isNodejsConfigured) { if (!isBrowserConfigured && !isNodejsConfigured) {
project.logger.warn( project.logger.warn(
""" """
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTargetPreset import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTargetPreset
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.runPostEvaluationProjectConfigurationHealthCheck
import org.jetbrains.kotlin.statistics.metrics.StringMetrics import org.jetbrains.kotlin.statistics.metrics.StringMetrics
open class KotlinJsIrTargetPreset( open class KotlinJsIrTargetPreset(
@@ -31,7 +32,7 @@ open class KotlinJsIrTargetPreset(
return project.objects.newInstance(KotlinJsIrTarget::class.java, project, platformType, mixedMode).apply { return project.objects.newInstance(KotlinJsIrTarget::class.java, project, platformType, mixedMode).apply {
this.isMpp = this@KotlinJsIrTargetPreset.isMpp this.isMpp = this@KotlinJsIrTargetPreset.isMpp
if (!mixedMode) { if (!mixedMode) {
project.whenEvaluated { project.runPostEvaluationProjectConfigurationHealthCheck {
if (!isBrowserConfigured && !isNodejsConfigured) { if (!isBrowserConfigured && !isNodejsConfigured) {
project.logger.warn( project.logger.warn(
""" """
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.Project
import org.gradle.kotlin.dsl.provider.ClassPathModeExceptionCollector
import org.gradle.kotlin.dsl.provider.inLenientMode
import org.gradle.kotlin.dsl.support.serviceOf
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
/**
* Function used to wrap any checks/assertions done on the current project configuration / project model.
*
* Runs the given [check] only on projects that are considered "healthy".
* A "healthy" project did evaluate correctly which means "without exceptions/errors".
*
* This function has to be used over "just running the check", because running project configuration checks
* on projects that failed to configure will lead to false positive error reporting.
* In most cases (when called in 'afterEvaluate') such false positive error message will even swallow the real root cause
* of configuration failure.
*
* Note:
* During Gradle/IDEA sync (import), Gradle will be set into `lenientMode` and will catch all exceptions
* during evaluation of the build script. Those exceptions will be put into the [ClassPathModeExceptionCollector].
* Any project that contains caught and collected exceptions in this 'collector' should be considered failed
* and running project model checks is undesirable. In this mode, throwing exceptions in `afterEvaluate` will even fail the process
* which would swallow the previously collected exceptions.
*
* Example:
* We have a post-evaluation check that will report users an error if no Kotlin target
* was registered.
*
* Consider the following build script:
*
* ```kotlin
* plugins {
* kotlin("multiplatform")
* }
*
* error("Something went wrong during the configuration phase")
*
* kotlin {
* jvm() // <- * Note: jvm target registered
* js() // <- * Note: js target registered
* }
* ```
*
* In this example, the exception is thrown before the configuration of Kotlin targets.
* During IDEA import, this exception will be caught and put into the [ClassPathModeExceptionCollector].
* When running the assertion just plainly (*without this wrapper function*), the user
* will not see the real cause of failure, but a rather bizarre:
* "Please initialize at least one Kotlin target"
* error message. Which is not helpful at all.
*
*/
internal inline fun Project.runProjectConfigurationHealthCheck(check: Project.() -> Unit) {
/* Running configuration checks on a failed project will only lead to false positive error messages */
if (state.failure != null || (inLenientMode() && serviceOf<ClassPathModeExceptionCollector>().exceptions.isNotEmpty())) {
return
}
check()
}
/**
* Convenience function for
* ```kotlin
* whenEvaluated {
* runProjectConfigurationCheck(action)
* }
* ```
* @see runProjectConfigurationHealthCheck
*/
internal inline fun Project.runPostEvaluationProjectConfigurationHealthCheck(crossinline action: Project.() -> Unit) {
whenEvaluated {
runProjectConfigurationHealthCheck(action)
}
}