From 29fb74d27d85bc3cea0865289a4b693c64eb4a44 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Mon, 27 Sep 2021 10:41:37 +0200 Subject: [PATCH] [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 --- .idea/kotlinc.xml | 2 +- .../kotlin/gradle/NewMultiplatformIT.kt | 24 ++++++ .../kotlin-mpp-classpathMode/build.gradle.kts | 20 +++++ .../settings.gradle.kts | 10 +++ .../kotlin-gradle-plugin/build.gradle.kts | 1 + .../gradle/plugin/KotlinPluginWrapper.kt | 5 +- .../plugin/mpp/KotlinMultiplatformPlugin.kt | 10 +-- .../plugin/mpp/pm20/KotlinGradleFragment.kt | 8 +- .../plugin/sources/DefaultKotlinSourceSet.kt | 5 +- .../gradle/targets/js/KotlinJsPlugin.kt | 3 +- .../gradle/targets/js/KotlinJsTargetPreset.kt | 3 +- .../targets/js/ir/KotlinJsIrTargetPreset.kt | 3 +- .../utils/runProjectConfigurationCheck.kt | 81 +++++++++++++++++++ 13 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/runProjectConfigurationCheck.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 1010bfbd321..136791891a9 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index 806f596c50c..dfe2f44a24b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -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" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/build.gradle.kts new file mode 100644 index 00000000000..544b9e229cc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/build.gradle.kts @@ -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() + 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") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/settings.gradle.kts new file mode 100644 index 00000000000..ecdece6ecfe --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-mpp-classpathMode/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } + val kotlin_version: String by settings + plugins { + kotlin("multiplatform").version(kotlin_version) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index aac5ac66ccf..2f660868bfc 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -26,6 +26,7 @@ pill { } dependencies { + compileOnly(gradleKotlinDsl()) api(project(":kotlin-gradle-plugin-api")) api(project(":kotlin-gradle-plugin-model")) compileOnly(project(":compiler")) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 63ac7b13ff0..a3d01eec927 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -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 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 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 } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 57b8d5000a0..c89a84000c0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -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 { UnusedSourceSetsChecker.checkSourceSets(project) - project.whenEvaluated { + project.runPostEvaluationProjectConfigurationHealthCheck { checkSourceSetVisibilityRequirements(project) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt index 19e6ae35e4b..6028e62ccba 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt @@ -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 - ) \ No newline at end of file + ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt index ddff31440e0..8b0e048839b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt @@ -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() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsPlugin.kt index 97675ecd6b7..2204ac30c3f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsPlugin.kt @@ -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( """ diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetPreset.kt index b9b1b24b277..26c16cba7a9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetPreset.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetPreset.kt @@ -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( """ diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetPreset.kt index b5223ab4291..159eb8b9b7b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetPreset.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTargetPreset.kt @@ -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( """ diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/runProjectConfigurationCheck.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/runProjectConfigurationCheck.kt new file mode 100644 index 00000000000..afabf4b33aa --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/runProjectConfigurationCheck.kt @@ -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().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) + } +}