diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/PreHmppDependenciesDeprecationIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/PreHmppDependenciesDeprecationIT.kt index 06fc157b094..26618ac8ad7 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/PreHmppDependenciesDeprecationIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/PreHmppDependenciesDeprecationIT.kt @@ -1,7 +1,7 @@ package org.jetbrains.kotlin.gradle.mpp import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.gradle.plugin.mpp.internal.DEPRECATED_PRE_HMPP_LIBRARIES_DETECTED_MESSAGE +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.replaceText import org.junit.jupiter.api.io.TempDir @@ -90,13 +90,11 @@ class PreHmppDependenciesDeprecationIT : KGPBaseTest() { preBuildAction() build(taskToCall) { if (expectReportForDependency != null) { - assertOutputContainsExactlyTimes( - DEPRECATED_PRE_HMPP_LIBRARIES_DETECTED_MESSAGE.replace("{0}", ".*$expectReportForDependency.*").toRegex() + output.assertHasDiagnostic( + KotlinToolingDiagnostics.PreHmppDependenciesUsedInBuild ) } else { - assertOutputDoesNotContain( - DEPRECATED_PRE_HMPP_LIBRARIES_DETECTED_MESSAGE.replace("{0}", ".*").toRegex() - ) + output.assertNoDiagnostic(KotlinToolingDiagnostics.PreHmppDependenciesUsedInBuild) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt index 443e51d7d52..fa01573c203 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt @@ -104,7 +104,8 @@ internal interface KotlinGradleProjectChecker { NoKotlinTargetsDeclaredChecker, DisabledCinteropCommonizationInHmppProjectChecker, DisabledNativeTargetsChecker, - JsEnvironmentChecker + JsEnvironmentChecker, + PreHmppDependenciesUsageChecker ) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt index 120f426c18c..6e4db4f6c2a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt @@ -312,4 +312,13 @@ object KotlinToolingDiagnostics { """.trimMargin() ) } + + object PreHmppDependenciesUsedInBuild : ToolingDiagnosticFactory(WARNING) { + operator fun invoke(dependencyName: String) = build( + """ + The dependency '$dependencyName' was published in the legacy mode. Support for such dependencies will be removed in the future. + See https://kotl.in/0b5kn8 for details. + """.trimIndent() + ) + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/checkers/PreHmppDependenciesUsageChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/checkers/PreHmppDependenciesUsageChecker.kt new file mode 100644 index 00000000000..a79d5f340ba --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/checkers/PreHmppDependenciesUsageChecker.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2023 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.plugin.diagnostics.checkers + +import org.gradle.api.artifacts.component.ComponentIdentifier +import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.artifacts.result.ResolvedDependencyResult +import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.Usage +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider +import org.jetbrains.kotlin.gradle.plugin.await +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinGradleProjectChecker +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinGradleProjectCheckerContext +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnosticsCollector +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages +import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration +import org.jetbrains.kotlin.gradle.plugin.sources.internal +import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +internal object PreHmppDependenciesUsageChecker : KotlinGradleProjectChecker { + override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + KotlinPluginLifecycle.Stage.ReadyForExecution.await() + + if (!project.isKotlinGranularMetadataEnabled || project.kotlinPropertiesProvider.allowLegacyMppDependencies) return + + val metadataTarget = project.multiplatformExtensionOrNull?.awaitTargets() + ?.matching { it is KotlinMetadataTarget } + ?.singleOrNull() + ?: return + + // Note that we do not inspect dependencies of compilations, because adding dependencies into compilation is esoteric enough on its own, + // and x2 esoteric for KotlinMetadataTarget + val sourceSetsInMetadataCompilations = metadataTarget.compilations.flatMapTo(mutableSetOf()) { it.allKotlinSourceSets } + val configurationsToInspect = sourceSetsInMetadataCompilations.map { it.internal.resolvableMetadataConfiguration } + + // Resolution of configurations can happen concurrently, so need to use thread-safe primitives + val reportedDependencies: MutableSet = Collections.newSetFromMap(ConcurrentHashMap()) + val processedDependencies: MutableSet = Collections.newSetFromMap(ConcurrentHashMap()) + + for (configuration in configurationsToInspect) { + configuration.incoming.afterResolve { resolvableDependencies -> + val resolvedDependencies = resolvableDependencies.resolutionResult.root.dependencies + .filterIsInstance() + // We don't want to report deprecation on transitive dependencies. Gradle will add them into list of 'dependencies', + // but will mark them as 'isConstraint' + .filter { it.selected.id is ModuleComponentIdentifier && !it.isConstraint } + + for (dependency in resolvedDependencies) { + val dependencyId = dependency.selected.id as ModuleComponentIdentifier + if (!processedDependencies.add(dependencyId)) continue + + if (isPreHmppDependency(dependency) && reportedDependencies.add(dependencyId)) { + collector.reportOncePerGradleBuild( + project, + KotlinToolingDiagnostics.PreHmppDependenciesUsedInBuild(dependencyId.displayName), + key = dependencyId.displayName + ) + } + } + } + } + } + + private fun isPreHmppDependency(dependency: ResolvedDependencyResult): Boolean { + val attributes = dependency.resolvedVariant.attributes + val kotlinPlatformAttribute = attributes.getAttribute(Attribute.of(KotlinPlatformType.attribute.name, String::class.java)) + ?: return false + val usageAttribute = attributes.getAttribute(Attribute.of(Usage.USAGE_ATTRIBUTE.name, String::class.java)) ?: return false + + return kotlinPlatformAttribute == KotlinPlatformType.common.name && usageAttribute != KotlinUsages.KOTLIN_METADATA + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/deprecationDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/deprecationDiagnostics.kt index 7d07b0f2311..a85bd44ebc3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/deprecationDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/deprecationDiagnostics.kt @@ -20,10 +20,7 @@ import org.jetbrains.kotlin.tooling.core.UnsafeApi internal fun runDeprecationDiagnostics(project: Project) { checkAndReportDeprecatedMppProperties(project) handleHierarchicalStructureFlagsMigration(project) - project.runProjectConfigurationHealthCheckWhenEvaluated { - checkAndReportPreHmppDependenciesUsage(project) - } -} + } /** * Declared properties have to be captured during plugin application phase before the HMPP migration util sets them. diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/reportDeprecatedPreHmppDependencies.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/reportDeprecatedPreHmppDependencies.kt deleted file mode 100644 index 1f9c8b539c3..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/reportDeprecatedPreHmppDependencies.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2010-2023 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.plugin.mpp.internal - -import org.gradle.api.Project -import org.gradle.api.artifacts.component.ComponentIdentifier -import org.gradle.api.artifacts.component.ModuleComponentIdentifier -import org.gradle.api.artifacts.result.ResolvedDependencyResult -import org.gradle.api.attributes.Attribute -import org.gradle.api.attributes.Usage -import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi -import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull -import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages -import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration -import org.jetbrains.kotlin.gradle.plugin.sources.internal -import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled -import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild -import java.util.* -import java.util.concurrent.ConcurrentHashMap - -@Suppress("NAME_SHADOWING") -internal fun checkAndReportPreHmppDependenciesUsage( - project: Project, - reportWarning: (Project, String) -> Unit = DEFAULT_DIAGNOSTIC_REPORTER, -) { - if (!project.isKotlinGranularMetadataEnabled || project.kotlinPropertiesProvider.allowLegacyMppDependencies) return - - val metadataTarget = project.multiplatformExtensionOrNull?.targets?.matching { it is KotlinMetadataTarget }?.singleOrNull() - ?: return - - // Note that we do not inspect dependencies of compilations, because adding dependencies into compilation is esoteric enough on its own, - // and x2 esoteric for KotlinMetadataTarget - val sourceSetsInMetadataCompilations = metadataTarget.compilations.flatMapTo(mutableSetOf()) { it.allKotlinSourceSets } - val configurationsToInspect = sourceSetsInMetadataCompilations.map { it.internal.resolvableMetadataConfiguration } - - // Resolution of configurations can happen concurrently, so need to use thread-safe primitives - val reportedDependencies: MutableSet = Collections.newSetFromMap(ConcurrentHashMap()) - val processedDependencies: MutableSet = Collections.newSetFromMap(ConcurrentHashMap()) - - for (configuration in configurationsToInspect) { - configuration.incoming.afterResolve { configuration -> - val resolvedDependencies = configuration.resolutionResult.root.dependencies - .filterIsInstance() - // We don't want to report deprecation on transitive dependencies. Gradle will add them into list of 'dependencies', - // but will mark them as 'isConstraint' - .filter { it.selected.id is ModuleComponentIdentifier && !it.isConstraint } - - for (dependency in resolvedDependencies) { - val dependencyId = dependency.selected.id as ModuleComponentIdentifier - if (!processedDependencies.add(dependencyId)) continue - - if (isPreHmppDependency(dependency) && reportedDependencies.add(dependencyId)) { - reportWarning( - project, - DEPRECATED_PRE_HMPP_LIBRARIES_DETECTED_MESSAGE.replace("{0}", dependencyId.displayName) - ) - } - } - } - } -} - -private fun isPreHmppDependency(dependency: ResolvedDependencyResult): Boolean { - val attributes = dependency.resolvedVariant.attributes - val kotlinPlatformAttribute = attributes.getAttribute(Attribute.of(KotlinPlatformType.attribute.name, String::class.java)) - ?: return false - val usageAttribute = attributes.getAttribute(Attribute.of(Usage.USAGE_ATTRIBUTE.name, String::class.java)) ?: return false - - return kotlinPlatformAttribute == KotlinPlatformType.common.name && usageAttribute != KotlinUsages.KOTLIN_METADATA -} - -private val DEFAULT_DIAGNOSTIC_REPORTER = { project: Project, message: String -> - SingleWarningPerBuild.show(project, message) -} - -@InternalKotlinGradlePluginApi -val DEPRECATED_PRE_HMPP_LIBRARIES_DETECTED_MESSAGE = - "w: The dependency {0} was published in the legacy mode. Support for such dependencies will be removed in the future. " + - "See https://kotl.in/0b5kn8 for details."