Fix stdlib-by-default failures when configuration has been resolved
Issue #KT-38221
This commit is contained in:
+20
@@ -257,6 +257,26 @@ class KotlinSpecificDependenciesIT : BaseGradleIT() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoFailureIfConfigurationIsObserved() = with(jvmProject()) {
|
||||
lateinit var originalScript: String
|
||||
try {
|
||||
gradleBuildScript().modify {
|
||||
originalScript = it
|
||||
"""
|
||||
configurations.create("api")
|
||||
dependencies {
|
||||
api("org.jetbrains.kotlin:kotlin-reflect")
|
||||
}
|
||||
println(configurations.api.incoming.dependencies.toList())
|
||||
""".trimIndent() + "\n" + it
|
||||
}
|
||||
checkTaskCompileClasspath("compileKotlin", listOf("kotlin-reflect"))
|
||||
} finally {
|
||||
gradleBuildScript().writeText(originalScript)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.checkConfigurationContent(
|
||||
configurationName: String,
|
||||
subproject: String? = null,
|
||||
|
||||
+88
-51
@@ -8,9 +8,12 @@ package org.jetbrains.kotlin.gradle.internal
|
||||
import com.android.build.gradle.TestedExtension
|
||||
import com.android.build.gradle.api.TestVariant
|
||||
import com.android.build.gradle.api.UnitTestVariant
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.artifacts.DependencySet
|
||||
import org.gradle.api.artifacts.ExternalDependency
|
||||
import org.gradle.api.tasks.testing.AbstractTestTask
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
@@ -54,66 +57,82 @@ private fun configureDefaultVersionsResolutionStrategy(project: Project) {
|
||||
}
|
||||
|
||||
//region stdlib
|
||||
internal fun configureStdlibDefaultDependency(project: Project) = project.whenEvaluated {
|
||||
internal fun configureStdlibDefaultDependency(project: Project) = with(project) {
|
||||
if (!PropertiesProvider(project).stdlibDefaultDependency)
|
||||
return@whenEvaluated
|
||||
return
|
||||
|
||||
val scopesToHandleConfigurations = listOf(KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE)
|
||||
|
||||
project.kotlinExtension.sourceSets.all { kotlinSourceSet ->
|
||||
val scope = if (isRelatedToAndroidTestSourceSet(project, kotlinSourceSet)) // AGP deprecates API configurations in test source sets
|
||||
KotlinDependencyScope.IMPLEMENTATION_SCOPE
|
||||
else KotlinDependencyScope.API_SCOPE
|
||||
scopesToHandleConfigurations.forEach { scope ->
|
||||
val scopeConfiguration = project.sourceSetDependencyConfigurationByScope(kotlinSourceSet, scope)
|
||||
|
||||
val scopeConfiguration = project.sourceSetDependencyConfigurationByScope(kotlinSourceSet, scope)
|
||||
project.tryWithDependenciesIfUnresolved(scopeConfiguration) { dependencies ->
|
||||
val scopeToAddStdlibDependency =
|
||||
if (isRelatedToAndroidTestSourceSet(project, kotlinSourceSet)) // AGP deprecates API configurations in test source sets
|
||||
KotlinDependencyScope.IMPLEMENTATION_SCOPE
|
||||
else KotlinDependencyScope.API_SCOPE
|
||||
|
||||
scopeConfiguration.withDependencies { dependencies ->
|
||||
val sourceSetDependencyConfigurations =
|
||||
KotlinDependencyScope.values().map { project.sourceSetDependencyConfigurationByScope(kotlinSourceSet, it) }
|
||||
if (scope != scopeToAddStdlibDependency)
|
||||
return@tryWithDependenciesIfUnresolved
|
||||
|
||||
val hierarchySourceSetsDependencyConfigurations = kotlinSourceSet.getSourceSetHierarchy().flatMap { hierarchySourceSet ->
|
||||
if (hierarchySourceSet === kotlinSourceSet) emptyList() else
|
||||
KotlinDependencyScope.values().map { scope ->
|
||||
project.sourceSetDependencyConfigurationByScope(hierarchySourceSet, scope)
|
||||
}
|
||||
}
|
||||
|
||||
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(kotlinSourceSet)
|
||||
.filter { it.target !is KotlinMetadataTarget }
|
||||
val platformTypes = compilations.mapTo(mutableSetOf()) { it.platformType }
|
||||
|
||||
val sourceSetStdlibPlatformType = when {
|
||||
platformTypes.isEmpty() -> KotlinPlatformType.common
|
||||
platformTypes.size == 1 -> platformTypes.single()
|
||||
setOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm).containsAll(platformTypes) -> KotlinPlatformType.jvm
|
||||
else -> KotlinPlatformType.common
|
||||
}
|
||||
|
||||
val isStdlibAddedByUser = sourceSetDependencyConfigurations
|
||||
.flatMap { it.allDependencies }
|
||||
.any { dependency -> dependency.group == KOTLIN_MODULE_GROUP && dependency.name in stdlibModules }
|
||||
|
||||
if (!isStdlibAddedByUser) {
|
||||
val stdlibModuleName = when (sourceSetStdlibPlatformType) {
|
||||
KotlinPlatformType.jvm -> stdlibModuleForJvmCompilations(compilations)
|
||||
KotlinPlatformType.androidJvm ->
|
||||
if (kotlinSourceSet.name == androidMainSourceSetName(project)) stdlibModuleForJvmCompilations(compilations) else null
|
||||
KotlinPlatformType.js -> "kotlin-stdlib-js"
|
||||
KotlinPlatformType.native -> null
|
||||
KotlinPlatformType.common -> // there's no platform compilation that the source set is default for
|
||||
"kotlin-stdlib-common"
|
||||
}
|
||||
|
||||
// If the exact same module is added in the source sets hierarchy, possibly even with a different scope, we don't add it
|
||||
val moduleAddedInHierarchy = hierarchySourceSetsDependencyConfigurations.any {
|
||||
it.allDependencies.any { dependency -> dependency.group == KOTLIN_MODULE_GROUP && dependency.name == stdlibModuleName }
|
||||
}
|
||||
|
||||
if (stdlibModuleName != null && !moduleAddedInHierarchy)
|
||||
dependencies.add(project.kotlinDependency(stdlibModuleName, project.kotlinExtension.coreLibrariesVersion))
|
||||
chooseAndAddStdlibDependency(project, kotlinSourceSet, dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseAndAddStdlibDependency(
|
||||
project: Project,
|
||||
kotlinSourceSet: KotlinSourceSet,
|
||||
dependencies: DependencySet
|
||||
) {
|
||||
val sourceSetDependencyConfigurations =
|
||||
KotlinDependencyScope.values().map { project.sourceSetDependencyConfigurationByScope(kotlinSourceSet, it) }
|
||||
|
||||
val hierarchySourceSetsDependencyConfigurations = kotlinSourceSet.getSourceSetHierarchy().flatMap { hierarchySourceSet ->
|
||||
if (hierarchySourceSet === kotlinSourceSet) emptyList() else
|
||||
KotlinDependencyScope.values().map { scope ->
|
||||
project.sourceSetDependencyConfigurationByScope(hierarchySourceSet, scope)
|
||||
}
|
||||
}
|
||||
|
||||
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(kotlinSourceSet)
|
||||
.filter { it.target !is KotlinMetadataTarget }
|
||||
val platformTypes = compilations.mapTo(mutableSetOf()) { it.platformType }
|
||||
|
||||
val sourceSetStdlibPlatformType = when {
|
||||
platformTypes.isEmpty() -> KotlinPlatformType.common
|
||||
platformTypes.size == 1 -> platformTypes.single()
|
||||
setOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm).containsAll(platformTypes) -> KotlinPlatformType.jvm
|
||||
else -> KotlinPlatformType.common
|
||||
}
|
||||
|
||||
val isStdlibAddedByUser = sourceSetDependencyConfigurations
|
||||
.flatMap { it.allDependencies }
|
||||
.any { dependency -> dependency.group == KOTLIN_MODULE_GROUP && dependency.name in stdlibModules }
|
||||
|
||||
if (!isStdlibAddedByUser) {
|
||||
val stdlibModuleName = when (sourceSetStdlibPlatformType) {
|
||||
KotlinPlatformType.jvm -> stdlibModuleForJvmCompilations(compilations)
|
||||
KotlinPlatformType.androidJvm ->
|
||||
if (kotlinSourceSet.name == androidMainSourceSetName(project)) stdlibModuleForJvmCompilations(compilations) else null
|
||||
KotlinPlatformType.js -> "kotlin-stdlib-js"
|
||||
KotlinPlatformType.native -> null
|
||||
KotlinPlatformType.common -> // there's no platform compilation that the source set is default for
|
||||
"kotlin-stdlib-common"
|
||||
}
|
||||
|
||||
// If the exact same module is added in the source sets hierarchy, possibly even with a different scope, we don't add it
|
||||
val moduleAddedInHierarchy = hierarchySourceSetsDependencyConfigurations.any {
|
||||
it.allDependencies.any { dependency -> dependency.group == KOTLIN_MODULE_GROUP && dependency.name == stdlibModuleName }
|
||||
}
|
||||
|
||||
if (stdlibModuleName != null && !moduleAddedInHierarchy)
|
||||
dependencies.add(project.kotlinDependency(stdlibModuleName, project.kotlinExtension.coreLibrariesVersion))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.findAndroidTarget(): KotlinAndroidTarget? {
|
||||
val kotlinExtension = project.kotlinExtension
|
||||
return when (kotlinExtension) {
|
||||
@@ -178,7 +197,7 @@ internal fun configureKotlinTestDependencies(project: Project) {
|
||||
}
|
||||
}
|
||||
|
||||
configuration.withDependencies { dependencies ->
|
||||
project.tryWithDependenciesIfUnresolved(configuration) { dependencies ->
|
||||
val parentOrOwnVersions: List<String?> =
|
||||
kotlinSourceSet.getSourceSetHierarchy().filter(versionOrNullBySourceSet::contains).map(versionOrNullBySourceSet::get)
|
||||
|
||||
@@ -283,4 +302,22 @@ private fun KotlinTargetWithTests<*, *>.findTestRunsByCompilation(byCompilation:
|
||||
}
|
||||
return testRuns.filter { it.executionSource.isProducedFromTheCompilation() }.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
//endregion
|
||||
//endregion
|
||||
|
||||
private fun Project.tryWithDependenciesIfUnresolved(configuration: Configuration, action: (DependencySet) -> Unit) {
|
||||
fun reportAlreadyResolved() {
|
||||
logger.info("Could not setup Kotlin-specific dependencies for $configuration as it is already resolved")
|
||||
}
|
||||
|
||||
if (configuration.state != Configuration.State.UNRESOLVED)
|
||||
return reportAlreadyResolved()
|
||||
|
||||
// Gradle doesn't provide any public API to check if it's safe to modify a configuration's dependencies.
|
||||
// The state of the configuration may still be UNRESOLVED but it might have 'participated' in dependency resolution and its dependencies
|
||||
// may now be frozen.
|
||||
try {
|
||||
configuration.withDependencies(action)
|
||||
} catch (e: InvalidUserDataException) {
|
||||
reportAlreadyResolved()
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -99,11 +99,7 @@ abstract class AbstractKotlinTarget(
|
||||
?: project.configurations.create(kotlinUsageContext.name).also { configuration ->
|
||||
configuration.isCanBeConsumed = false
|
||||
configuration.isCanBeResolved = false
|
||||
/** Add dependencies lazily to avoid freezing the content of the configuration held by [kotlinUsageContext] */
|
||||
configuration.withDependencies { dependencies ->
|
||||
dependencies.addAll(kotlinUsageContext.dependencies)
|
||||
}
|
||||
configuration.dependencyConstraints.addAll(kotlinUsageContext.dependencyConstraints)
|
||||
configuration.extendsFrom(project.configurations.getByName(kotlinUsageContext.dependencyConfigurationName))
|
||||
configuration.artifacts.addAll(kotlinUsageContext.artifacts)
|
||||
|
||||
val attributes = kotlinUsageContext.attributes
|
||||
|
||||
Reference in New Issue
Block a user