[Gradle] Setup Consistent metadata dependencies resolution
Configure consistent metadata resolution only for source sets that participate in metadata compilation. i.e. test or any other extra compilations should be excluded. ^KT-65954 Verification Pending ^KT-66047 Verification Pending
This commit is contained in:
committed by
Space Team
parent
c027ba642f
commit
3e5afcaaba
+2
-1
@@ -1282,7 +1282,8 @@ open class HierarchicalMppIT : KGPBaseTest() {
|
||||
gradleVersion,
|
||||
localRepoDir = tempDir,
|
||||
).run {
|
||||
// add a dependency from commonTest on 2.0 version
|
||||
// add a dependency from jvmTest on 2.0 version
|
||||
// and assert that this dependency isn't leaking to the main source sets, including metadata compilation
|
||||
buildGradleKts.appendText(
|
||||
"""
|
||||
|
||||
|
||||
+3
-1
@@ -355,4 +355,6 @@ internal fun <T : KotlinTarget> KotlinTargetsContainerWithPresets.configureOrCre
|
||||
}
|
||||
}
|
||||
|
||||
internal val KotlinMultiplatformExtension.metadataTarget get() = metadata() as KotlinMetadataTarget
|
||||
internal val KotlinMultiplatformExtension.metadataTarget get() = metadata() as KotlinMetadataTarget
|
||||
|
||||
internal val Collection<KotlinTarget>.platformTargets: List<KotlinTarget> get() = filter { it !is KotlinMetadataTarget }
|
||||
+64
-38
@@ -10,14 +10,14 @@ import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.attributes.Category
|
||||
import org.gradle.api.attributes.Usage
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.categoryByName
|
||||
import org.jetbrains.kotlin.gradle.plugin.hierarchy.orNull
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.InternalKotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.disambiguateName
|
||||
import org.jetbrains.kotlin.gradle.plugin.usageByName
|
||||
import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf
|
||||
import org.jetbrains.kotlin.gradle.utils.*
|
||||
import org.jetbrains.kotlin.gradle.utils.listProperty
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
@@ -35,12 +35,13 @@ internal val InternalKotlinSourceSet.resolvableMetadataConfigurationName: String
|
||||
*/
|
||||
internal val InternalKotlinSourceSet.resolvableMetadataConfiguration: Configuration by extrasStoredProperty {
|
||||
assert(resolvableMetadataConfigurationName !in project.configurations.names)
|
||||
val configuration = project.configurations.maybeCreateResolvable(resolvableMetadataConfigurationName)
|
||||
val configuration = project.configurations
|
||||
.maybeCreateResolvable(resolvableMetadataConfigurationName)
|
||||
.configureMetadataDependenciesAttribute(project)
|
||||
|
||||
withDependsOnClosure.forAll { sourceSet ->
|
||||
configuration.extendsFrom(project.configurations.getByName(sourceSet.apiConfigurationName))
|
||||
configuration.extendsFrom(project.configurations.getByName(sourceSet.implementationConfigurationName))
|
||||
configuration.extendsFrom(project.configurations.getByName(sourceSet.compileOnlyConfigurationName))
|
||||
val extenders = sourceSet.internal.compileDependenciesConfigurations
|
||||
configuration.extendsFrom(*extenders.toTypedArray())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,26 +51,23 @@ internal val InternalKotlinSourceSet.resolvableMetadataConfiguration: Configurat
|
||||
*/
|
||||
configuration.dependencies.addAllLater(project.listProvider {
|
||||
getVisibleSourceSetsFromAssociateCompilations(this).flatMap { sourceSet ->
|
||||
project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies +
|
||||
project.configurations.getByName(sourceSet.implementationConfigurationName).allDependencies +
|
||||
project.configurations.getByName(sourceSet.compileOnlyConfigurationName).allDependencies
|
||||
sourceSet.internal.compileDependenciesConfigurations.flatMap { it.allDependencies }
|
||||
}
|
||||
})
|
||||
|
||||
val allCompileMetadataConfiguration = project.allCompileMetadataConfiguration
|
||||
|
||||
/* Ensure consistent dependency resolution result within the whole module */
|
||||
configuration.shouldResolveConsistentlyWith(allCompileMetadataConfiguration)
|
||||
allCompileMetadataConfiguration.copyAttributesTo(
|
||||
project,
|
||||
dest = configuration
|
||||
)
|
||||
|
||||
configureMetadataDependenciesConfigurations(configuration)
|
||||
// needed for old IDEs
|
||||
configureLegacyMetadataDependenciesConfigurations(configuration)
|
||||
|
||||
configuration
|
||||
}
|
||||
|
||||
private val InternalKotlinSourceSet.compileDependenciesConfigurations: List<Configuration>
|
||||
get() = listOf(
|
||||
project.configurations.getByName(apiConfigurationName),
|
||||
project.configurations.getByName(implementationConfigurationName),
|
||||
project.configurations.getByName(compileOnlyConfigurationName),
|
||||
)
|
||||
|
||||
/**
|
||||
Older IDEs still rely on resolving the metadata configurations explicitly.
|
||||
Dependencies will be coming from extending the newer 'resolvableMetadataConfiguration'.
|
||||
@@ -77,7 +75,7 @@ Dependencies will be coming from extending the newer 'resolvableMetadataConfigur
|
||||
the intransitiveMetadataConfigurationName will not extend this mechanism, since it only
|
||||
relies on dependencies being added explicitly by the Kotlin Gradle Plugin
|
||||
*/
|
||||
private fun InternalKotlinSourceSet.configureMetadataDependenciesConfigurations(resolvableMetadataConfiguration: Configuration) {
|
||||
private fun InternalKotlinSourceSet.configureLegacyMetadataDependenciesConfigurations(resolvableMetadataConfiguration: Configuration) {
|
||||
@Suppress("DEPRECATION")
|
||||
listOf(
|
||||
apiMetadataConfigurationName,
|
||||
@@ -90,27 +88,55 @@ private fun InternalKotlinSourceSet.configureMetadataDependenciesConfigurations(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration containing all compile dependencies from *all* source sets.
|
||||
* This configuration is used to provide a dependency 'consistency scope' for
|
||||
* the [InternalKotlinSourceSet.resolvableMetadataConfiguration]
|
||||
*/
|
||||
private val Project.allCompileMetadataConfiguration
|
||||
get(): Configuration = configurations.findResolvable("allSourceSetsCompileDependenciesMetadata")
|
||||
?: configurations.createResolvable("allSourceSetsCompileDependenciesMetadata").also { configuration ->
|
||||
configuration.usesPlatformOf(multiplatformExtension.metadata())
|
||||
configuration.attributes.setAttribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA))
|
||||
configuration.attributes.setAttribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY))
|
||||
|
||||
kotlinExtension.sourceSets.all { sourceSet ->
|
||||
configuration.extendsFrom(configurations.getByName(sourceSet.apiConfigurationName))
|
||||
configuration.extendsFrom(configurations.getByName(sourceSet.implementationConfigurationName))
|
||||
configuration.extendsFrom(configurations.getByName(sourceSet.compileOnlyConfigurationName))
|
||||
}
|
||||
}
|
||||
private fun Configuration.configureMetadataDependenciesAttribute(project: Project): Configuration = apply {
|
||||
usesPlatformOf(project.multiplatformExtension.metadata())
|
||||
attributes.setAttribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA))
|
||||
attributes.setAttribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY))
|
||||
}
|
||||
|
||||
private inline fun <reified T> Project.listProvider(noinline provider: () -> List<T>): Provider<List<T>> {
|
||||
return project.objects.listProperty<T>().apply {
|
||||
set(project.provider(provider))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a consistent dependencies resolution result between common source sets and actual
|
||||
* See [ResolvableMetadataConfigurationTest] for the cases where dependencies should resolve consistently
|
||||
*/
|
||||
internal val SetupConsistentMetadataDependenciesResolution = KotlinProjectSetupCoroutine {
|
||||
KotlinPluginLifecycle.Stage.AfterFinaliseRefinesEdges.await()
|
||||
|
||||
val sourceSets = multiplatformExtension.awaitSourceSets()
|
||||
val sourceSetsBySourceSetTree = mutableMapOf<KotlinSourceSetTree?, MutableSet<KotlinSourceSet>>()
|
||||
for (sourceSet in sourceSets) {
|
||||
val trees = sourceSet.internal.compilations.map { KotlinSourceSetTree.orNull(it) }
|
||||
trees.forEach { tree -> sourceSetsBySourceSetTree.getOrPut(tree) { mutableSetOf() }.add(sourceSet) }
|
||||
}
|
||||
|
||||
for ((sourceSetTree, sourceSetsOfTree) in sourceSetsBySourceSetTree) {
|
||||
val configurationName = when(sourceSetTree) {
|
||||
null -> continue // for unknown trees there should be no relation between source sets, so just skip
|
||||
KotlinSourceSetTree.main -> "allSourceSetsCompileDependenciesMetadata"
|
||||
else -> lowerCamelCaseName("all", sourceSetTree.name, "SourceSetsCompileDependenciesMetadata")
|
||||
}
|
||||
|
||||
configureConsistentDependencyResolution(sourceSetsOfTree, configurationName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.configureConsistentDependencyResolution(groupOfSourceSets: Collection<KotlinSourceSet>, configurationName: String) {
|
||||
if (groupOfSourceSets.isEmpty()) return
|
||||
val configuration = configurations.createResolvable(configurationName)
|
||||
configuration.configureMetadataDependenciesAttribute(project)
|
||||
val allVisibleSourceSets = groupOfSourceSets + groupOfSourceSets.flatMap { getVisibleSourceSetsFromAssociateCompilations(it) }
|
||||
val extenders = allVisibleSourceSets.flatMap { it.internal.compileDependenciesConfigurations }
|
||||
configuration.extendsFrom(*extenders.toTypedArray())
|
||||
groupOfSourceSets.forEach { it.internal.resolvableMetadataConfiguration.shouldResolveConsistentlyWith(configuration) }
|
||||
|
||||
// Make actual compilation classpaths/libraries configurations to have the same consistent dependencies
|
||||
groupOfSourceSets
|
||||
.flatMap { it.internal.compilations }
|
||||
.toSet()
|
||||
.forEach { project.configurations.getByName(it.compileDependencyConfigurationName).shouldResolveConsistentlyWith(configuration) }
|
||||
}
|
||||
+1
@@ -77,6 +77,7 @@ internal fun Project.registerKotlinPluginExtensions() {
|
||||
register(project, IdeMultiplatformImportActionSetupAction)
|
||||
register(project, KotlinLLDBScriptSetupAction)
|
||||
register(project, ExcludeDefaultPlatformDependenciesFromKotlinNativeCompileTasks)
|
||||
register(project, SetupConsistentMetadataDependenciesResolution)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -31,4 +31,4 @@ internal interface InternalKotlinSourceSet : KotlinSourceSet {
|
||||
internal suspend fun InternalKotlinSourceSet.awaitPlatformCompilations(): Set<KotlinCompilation<*>> {
|
||||
KotlinPluginLifecycle.Stage.AfterFinaliseRefinesEdges.await()
|
||||
return compilations.filter { it !is KotlinMetadataCompilation }.toSet()
|
||||
}
|
||||
}
|
||||
+91
-5
@@ -7,6 +7,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.dependencyResolutionTests
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||
import org.gradle.api.artifacts.result.ResolvedDependencyResult
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
@@ -17,15 +18,12 @@ import org.jetbrains.kotlin.gradle.plugin.ide.kotlinIdeMultiplatformImport
|
||||
import org.jetbrains.kotlin.gradle.plugin.kotlinToolingVersion
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.internal
|
||||
import org.jetbrains.kotlin.gradle.util.applyMultiplatformPlugin
|
||||
import org.jetbrains.kotlin.gradle.util.buildProject
|
||||
import org.jetbrains.kotlin.gradle.util.enableDefaultStdlibDependency
|
||||
import org.jetbrains.kotlin.gradle.util.enableDependencyVerification
|
||||
import org.jetbrains.kotlin.gradle.util.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
class ResolvableMetadataConfigurationTest {
|
||||
class ResolvableMetadataConfigurationTest : SourceSetDependenciesResolution() {
|
||||
|
||||
@Test
|
||||
fun `test - resolves consistent in project`() {
|
||||
@@ -97,4 +95,92 @@ class ResolvableMetadataConfigurationTest {
|
||||
binaryCoordinates(Regex("com.arkivanov.mvikotlin:mvikotlin(-*)?:.*:3.0.2")),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun commonMainWithHigherVersion() {
|
||||
assertSourceSetDependenciesResolution("commonMainWithHigherVersion.txt") { project ->
|
||||
project.defaultTargets()
|
||||
project.kotlin { linuxArm64() }
|
||||
|
||||
// commonMain depends on `lib:2.0`, so during dependency resolution,
|
||||
// this version should win over jvmMain and linuxMain that depend on 1.0
|
||||
api("jvmMain", "lib", "1.0")
|
||||
api("linuxMain", "lib", "1.0")
|
||||
api("commonMain", "lib", "2.0")
|
||||
// for test source sets, it should work the same due to transitivity.
|
||||
// because test code depends on the main with all its transitive dependencies.
|
||||
// this is the general logic of `associatedWith` compilations.
|
||||
api("commonTest", "lib", "1.0")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun jvmMainWithHigherVersion() {
|
||||
assertSourceSetDependenciesResolution("leafSourceSetWithHigherVersion.txt") { project ->
|
||||
project.defaultTargets()
|
||||
|
||||
// jvmMain depends on 3.0.
|
||||
// commonMain code is included in jvmMain compilation, so it should see the same version as jvmMain (3.0 wins here).
|
||||
// commonMain code is included in jsMain compilation, so it should see the same version as commonMain (3.0 wins here).
|
||||
// linuxX64Main should receive 3.0 version from commonMain transitively
|
||||
api("jvmMain", "lib", "3.0")
|
||||
api("jsMain", "lib", "2.0")
|
||||
api("commonMain", "lib", "1.0")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nativeMainWithHigherVersion() {
|
||||
assertSourceSetDependenciesResolution("leafSourceSetWithHigherVersion.txt") { project ->
|
||||
project.defaultTargets()
|
||||
|
||||
/** Same as for [jvmMainWithHigherVersion] but for linuxX64 */
|
||||
api("linuxX64Main", "lib", "3.0")
|
||||
api("jsMain", "lib", "2.0")
|
||||
api("commonMain", "lib", "1.0")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun jsMainWithHigherVersion() {
|
||||
assertSourceSetDependenciesResolution("leafSourceSetWithHigherVersion.txt") { project ->
|
||||
project.defaultTargets()
|
||||
|
||||
/** Same as for [jvmMainWithHigherVersion] but for js */
|
||||
api("jsMain", "lib", "3.0")
|
||||
api("nativeMain", "lib", "2.0")
|
||||
api("commonMain", "lib", "1.0")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun commonTestShouldNotAffectMainSourceSets() {
|
||||
assertSourceSetDependenciesResolution("commonTestShouldNotAffectMainSourceSets.txt") { project ->
|
||||
project.defaultTargets()
|
||||
|
||||
/** Test source sets are not compiled together with the main code, but just depend on it.
|
||||
Thus, by default, there is no need for the main code to receive the same dependency versions as tests.
|
||||
However, the other way around works in the opposite. See, for example, test [commonMainWithHigherVersion] */
|
||||
api("commonMain", "lib", "1.0")
|
||||
api("commonTest", "lib", "2.0")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun leafSourceSetsDependsOnDifferentVersionsAndCommonCodeDoesNot() {
|
||||
assertSourceSetDependenciesResolution("leafSourceSetsDependsOnDifferentVersionsAndCommonCodeDoesNot.txt") { project ->
|
||||
project.defaultTargets()
|
||||
|
||||
/** Even though commonMain has no dependency on lib, thus there is no connection between
|
||||
* jvmMain and linuxX64Main their dependencies should be still resolved consistently.
|
||||
* This is by design!
|
||||
* It complies with the desire of having global dependencies for the whole project */
|
||||
api("jvmMain", "lib", "1.0")
|
||||
api("linuxX64Main", "lib", "2.0")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.defaultTargets() {
|
||||
kotlin { jvm(); linuxX64(); js(); applyDefaultHierarchyTemplate() }
|
||||
}
|
||||
}
|
||||
|
||||
-12
@@ -261,18 +261,6 @@ class ConfigurationsTest : MultiplatformExtensionTest() {
|
||||
|
||||
project.evaluate()
|
||||
|
||||
fun HasKotlinDependencies.allDependenciesConfigurationNames() = listOfNotNull(
|
||||
apiConfigurationName,
|
||||
implementationConfigurationName,
|
||||
compileOnlyConfigurationName,
|
||||
runtimeOnlyConfigurationName
|
||||
)
|
||||
|
||||
fun KotlinCompilation<*>.allCompilationDependenciesConfigurationNames() = allDependenciesConfigurationNames() + listOfNotNull(
|
||||
compileDependencyConfigurationName,
|
||||
runtimeDependencyConfigurationName,
|
||||
)
|
||||
|
||||
project.kotlinExtension.targets.flatMap { it.compilations }.forEach { compilation ->
|
||||
val compilationSourceSets = compilation.allKotlinSourceSets
|
||||
val compilationConfigurationNames = compilation.allCompilationDependenciesConfigurationNames()
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2024 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.util
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
|
||||
fun HasKotlinDependencies.allDependenciesConfigurationNames() = listOfNotNull(
|
||||
apiConfigurationName,
|
||||
implementationConfigurationName,
|
||||
compileOnlyConfigurationName,
|
||||
runtimeOnlyConfigurationName
|
||||
)
|
||||
|
||||
fun KotlinCompilation<*>.allCompilationDependenciesConfigurationNames() = allDependenciesConfigurationNames() + listOfNotNull(
|
||||
compileDependencyConfigurationName,
|
||||
runtimeDependencyConfigurationName,
|
||||
)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
commonMain
|
||||
test:lib:2.0
|
||||
commonTest
|
||||
test:lib:2.0
|
||||
jsMain
|
||||
test:lib:2.0
|
||||
jsTest
|
||||
test:lib:2.0
|
||||
jvmMain
|
||||
test:lib:2.0
|
||||
jvmTest
|
||||
test:lib:2.0
|
||||
linuxArm64Main
|
||||
test:lib:2.0
|
||||
linuxArm64Test
|
||||
test:lib:2.0
|
||||
linuxMain
|
||||
test:lib:2.0
|
||||
linuxTest
|
||||
test:lib:2.0
|
||||
linuxX64Main
|
||||
test:lib:2.0
|
||||
linuxX64Test
|
||||
test:lib:2.0
|
||||
nativeMain
|
||||
test:lib:2.0
|
||||
nativeTest
|
||||
test:lib:2.0
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
commonMain
|
||||
test:lib:1.0
|
||||
commonTest
|
||||
test:lib:2.0
|
||||
jsMain
|
||||
test:lib:1.0
|
||||
jsTest
|
||||
test:lib:2.0
|
||||
jvmMain
|
||||
test:lib:1.0
|
||||
jvmTest
|
||||
test:lib:2.0
|
||||
linuxMain
|
||||
test:lib:1.0
|
||||
linuxTest
|
||||
test:lib:2.0
|
||||
linuxX64Main
|
||||
test:lib:1.0
|
||||
linuxX64Test
|
||||
test:lib:2.0
|
||||
nativeMain
|
||||
test:lib:1.0
|
||||
nativeTest
|
||||
test:lib:2.0
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
commonMain
|
||||
test:lib:3.0
|
||||
commonTest
|
||||
test:lib:3.0
|
||||
jsMain
|
||||
test:lib:3.0
|
||||
jsTest
|
||||
test:lib:3.0
|
||||
jvmMain
|
||||
test:lib:3.0
|
||||
jvmTest
|
||||
test:lib:3.0
|
||||
linuxMain
|
||||
test:lib:3.0
|
||||
linuxTest
|
||||
test:lib:3.0
|
||||
linuxX64Main
|
||||
test:lib:3.0
|
||||
linuxX64Test
|
||||
test:lib:3.0
|
||||
nativeMain
|
||||
test:lib:3.0
|
||||
nativeTest
|
||||
test:lib:3.0
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
commonMain
|
||||
|
||||
commonTest
|
||||
|
||||
jsMain
|
||||
|
||||
jsTest
|
||||
|
||||
jvmMain
|
||||
test:lib:2.0
|
||||
jvmTest
|
||||
test:lib:2.0
|
||||
linuxMain
|
||||
|
||||
linuxTest
|
||||
test:lib:2.0
|
||||
linuxX64Main
|
||||
test:lib:2.0
|
||||
linuxX64Test
|
||||
test:lib:2.0
|
||||
nativeMain
|
||||
|
||||
nativeTest
|
||||
test:lib:2.0
|
||||
Reference in New Issue
Block a user