[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:
committed by
Space
parent
e4827efb4a
commit
29fb74d27d
Generated
+1
-1
@@ -15,4 +15,4 @@
|
||||
<component name="KotlinCompilerSettings">
|
||||
<option name="additionalArguments" value="-version -Xallow-kotlin-package -Xskip-metadata-version-check" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
+24
@@ -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 {
|
||||
HostManager.hostIsLinux -> "linuxX64"
|
||||
HostManager.hostIsMingw -> "mingwX64"
|
||||
|
||||
+20
@@ -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")
|
||||
+10
@@ -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 {
|
||||
compileOnly(gradleKotlinDsl())
|
||||
api(project(":kotlin-gradle-plugin-api"))
|
||||
api(project(":kotlin-gradle-plugin-model"))
|
||||
compileOnly(project(":compiler"))
|
||||
|
||||
+3
-2
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.gradle.testing.internal.KotlinTestsRegistry
|
||||
import org.jetbrains.kotlin.gradle.tooling.buildKotlinToolingMetadataTask
|
||||
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
|
||||
import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources
|
||||
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
|
||||
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
|
||||
import javax.inject.Inject
|
||||
import kotlin.reflect.KClass
|
||||
@@ -201,7 +202,7 @@ open class KotlinJsPluginWrapper : KotlinBasePluginWrapper() {
|
||||
override val projectExtensionClass: KClass<out KotlinJsProjectExtension>
|
||||
get() = KotlinJsProjectExtension::class
|
||||
|
||||
override fun whenBuildEvaluated(project: Project) {
|
||||
override fun whenBuildEvaluated(project: Project) = project.runProjectConfigurationHealthCheck {
|
||||
val isJsTargetUninitialized = (project.kotlinExtension as KotlinJsProjectExtension)
|
||||
._target == null
|
||||
|
||||
@@ -232,7 +233,7 @@ open class KotlinMultiplatformPluginWrapper : KotlinBasePluginWrapper() {
|
||||
override val projectExtensionClass: KClass<out KotlinMultiplatformExtension>
|
||||
get() = KotlinMultiplatformExtension::class
|
||||
|
||||
override fun whenBuildEvaluated(project: Project) {
|
||||
override fun whenBuildEvaluated(project: Project) = project.runProjectConfigurationHealthCheck {
|
||||
val isNoTargetsInitialized = (project.kotlinExtension as KotlinMultiplatformExtension)
|
||||
.targets
|
||||
.none { it !is KotlinMetadataTarget }
|
||||
|
||||
+4
-6
@@ -26,18 +26,16 @@ import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
|
||||
import org.jetbrains.kotlin.gradle.plugin.*
|
||||
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.sources.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.CleanupStaleSourceSetMetadataEntriesService
|
||||
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.scripting.internal.ScriptingGradleSubplugin
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset
|
||||
import org.jetbrains.kotlin.gradle.tasks.locateTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.registerTask
|
||||
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.KonanTarget.*
|
||||
import org.jetbrains.kotlin.konan.target.presetName
|
||||
@@ -229,7 +227,7 @@ class KotlinMultiplatformPlugin : Plugin<Project> {
|
||||
|
||||
UnusedSourceSetsChecker.checkSourceSets(project)
|
||||
|
||||
project.whenEvaluated {
|
||||
project.runPostEvaluationProjectConfigurationHealthCheck {
|
||||
checkSourceSetVisibilityRequirements(project)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -11,7 +11,8 @@ import org.gradle.api.Project
|
||||
import org.gradle.api.file.SourceDirectorySet
|
||||
import org.gradle.api.provider.Provider
|
||||
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.toModuleDependency
|
||||
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.utils.addExtendsFromRelation
|
||||
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.KotlinModuleFragment
|
||||
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.whenEvaluated {
|
||||
project.runPostEvaluationProjectConfigurationHealthCheck {
|
||||
kotlinGradleFragmentConsistencyChecker.runAllChecks(this@KotlinGradleFragmentInternal, other.get())
|
||||
}
|
||||
}
|
||||
@@ -117,4 +119,4 @@ internal val kotlinGradleFragmentConsistencyChecker =
|
||||
unitName = "fragment",
|
||||
languageSettings = { languageSettings }
|
||||
).allChecks
|
||||
)
|
||||
)
|
||||
|
||||
+3
-2
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
|
||||
import org.jetbrains.kotlin.gradle.utils.*
|
||||
import java.io.File
|
||||
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.
|
||||
checkForCircularDependencies()
|
||||
|
||||
project.whenEvaluated { defaultSourceSetLanguageSettingsChecker.runAllChecks(this@DefaultKotlinSourceSet, other) }
|
||||
project.runPostEvaluationProjectConfigurationHealthCheck {
|
||||
defaultSourceSetLanguageSettingsChecker.runAllChecks(this@DefaultKotlinSourceSet, other)
|
||||
}
|
||||
}
|
||||
|
||||
private val dependsOnSourceSetsImpl = mutableSetOf<KotlinSourceSet>()
|
||||
|
||||
+1
-2
@@ -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.mpp.KotlinJsSingleTargetPreset
|
||||
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.utils.*
|
||||
|
||||
@@ -47,7 +46,7 @@ open class KotlinJsPlugin(
|
||||
defaultJsCompilerType = PropertiesProvider(project).jsCompiler
|
||||
}
|
||||
|
||||
project.whenEvaluated {
|
||||
project.runPostEvaluationProjectConfigurationHealthCheck {
|
||||
if (kotlinExtension._target == null) {
|
||||
project.logger.warn(
|
||||
"""
|
||||
|
||||
+2
-1
@@ -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.ir.KotlinJsIrTargetPreset
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import org.jetbrains.kotlin.gradle.utils.runPostEvaluationProjectConfigurationHealthCheck
|
||||
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
|
||||
|
||||
open class KotlinJsTargetPreset(
|
||||
@@ -54,7 +55,7 @@ open class KotlinJsTargetPreset(
|
||||
}
|
||||
this.isMpp = this@KotlinJsTargetPreset.isMpp
|
||||
|
||||
project.whenEvaluated {
|
||||
project.runPostEvaluationProjectConfigurationHealthCheck {
|
||||
if (!isBrowserConfigured && !isNodejsConfigured) {
|
||||
project.logger.warn(
|
||||
"""
|
||||
|
||||
+2
-1
@@ -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.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import org.jetbrains.kotlin.gradle.utils.runPostEvaluationProjectConfigurationHealthCheck
|
||||
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
|
||||
|
||||
open class KotlinJsIrTargetPreset(
|
||||
@@ -31,7 +32,7 @@ open class KotlinJsIrTargetPreset(
|
||||
return project.objects.newInstance(KotlinJsIrTarget::class.java, project, platformType, mixedMode).apply {
|
||||
this.isMpp = this@KotlinJsIrTargetPreset.isMpp
|
||||
if (!mixedMode) {
|
||||
project.whenEvaluated {
|
||||
project.runPostEvaluationProjectConfigurationHealthCheck {
|
||||
if (!isBrowserConfigured && !isNodejsConfigured) {
|
||||
project.logger.warn(
|
||||
"""
|
||||
|
||||
+81
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user