Fix visibility for MPP dependencies from non-root source sets, KT-32204

Fix incorrect source sets visibility when a dependency is added to a
non-root source set and its visible source sets are mistakenly treated
as already visible by the dependsOn parents and are thus not extracted
from the metadata package.

To simplify the logic, reuse the GranularMetadataTransformation
instances from the dependsOn parent source sets: they can provide the
requested dependencies sets for the parents and their transformation
results, so that excluding the dependency source sets which are seen
the parents gets simpler.

The issue is then fixed by naturally excluding only the source sets
which are mentioned in the parents' transformation results.

Issue #KT-32204 Fixed
This commit is contained in:
Sergey Igushkin
2019-06-25 19:19:16 +03:00
parent 0da55e8284
commit dbc8007c63
5 changed files with 243 additions and 89 deletions
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.gradle.internals.MULTIPLATFORM_PROJECT_METADATA_FILE
import org.jetbrains.kotlin.gradle.internals.parseKotlinSourceSetMetadataFromXml import org.jetbrains.kotlin.gradle.internals.parseKotlinSourceSetMetadataFromXml
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.ModuleDependencyIdentifier import org.jetbrains.kotlin.gradle.plugin.mpp.ModuleDependencyIdentifier
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
import java.io.File import java.io.File
import java.util.zip.ZipFile import java.util.zip.ZipFile
@@ -24,13 +25,7 @@ class HierarchicalMppIT : BaseGradleIT() {
@Test @Test
fun testPublishedModules() { fun testPublishedModules() {
Project("third-party-lib", gradleVersion, "hierarchical-mpp-published-modules").run { publishThirdPartyLib(withGranularMetadata = false)
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
build("publish") {
assertSuccessful()
}
}
Project("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run { Project("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run {
setupWorkingDir() setupWorkingDir()
@@ -59,13 +54,7 @@ class HierarchicalMppIT : BaseGradleIT() {
@Test @Test
fun testProjectDependencies() { fun testProjectDependencies() {
Project("third-party-lib", gradleVersion, "hierarchical-mpp-project-dependency").run { publishThirdPartyLib(withGranularMetadata = false)
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
build("publish") {
assertSuccessful()
}
}
with(Project("hierarchical-mpp-project-dependency", gradleVersion)) { with(Project("hierarchical-mpp-project-dependency", gradleVersion)) {
setupWorkingDir() setupWorkingDir()
@@ -79,6 +68,20 @@ class HierarchicalMppIT : BaseGradleIT() {
} }
} }
private fun publishThirdPartyLib(withGranularMetadata: Boolean): Project =
Project("third-party-lib", gradleVersion, "hierarchical-mpp-published-modules").apply {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
if (withGranularMetadata) {
projectDir.resolve("gradle.properties").appendText("kotlin.mpp.enableGranularSourceSetsMetadata=true")
}
build("publish") {
assertSuccessful()
}
}
private fun checkMyLibFoo(compiledProject: CompiledProject, subprojectPrefix: String? = null) = with(compiledProject) { private fun checkMyLibFoo(compiledProject: CompiledProject, subprojectPrefix: String? = null) = with(compiledProject) {
assertSuccessful() assertSuccessful()
assertTasksExecuted(expectedTasks(subprojectPrefix)) assertTasksExecuted(expectedTasks(subprojectPrefix))
@@ -273,7 +276,7 @@ class HierarchicalMppIT : BaseGradleIT() {
"compileKotlinMetadata", "compileKotlinMetadata",
"compileJvmAndJsMainKotlinMetadata", "compileJvmAndJsMainKotlinMetadata",
"compileLinuxAndJsMainKotlinMetadata" "compileLinuxAndJsMainKotlinMetadata"
).map { subprojectPrefix?.let { ":$it" }.orEmpty() + ":" + it } ).map { task -> subprojectPrefix?.let { ":$it" }.orEmpty() + ":" + task }
// the projects used in these tests are similar and only the dependencies differ: // the projects used in these tests are similar and only the dependencies differ:
private fun expectedProjectStructureMetadata( private fun expectedProjectStructureMetadata(
@@ -317,4 +320,130 @@ class HierarchicalMppIT : BaseGradleIT() {
.use { inputStream -> documentBuilder.parse(inputStream) } .use { inputStream -> documentBuilder.parse(inputStream) }
return checkNotNull(parseKotlinSourceSetMetadataFromXml(document)) return checkNotNull(parseKotlinSourceSetMetadataFromXml(document))
} }
@Test
fun testProcessingDependencyDeclaredInNonRootSourceSet() {
publishThirdPartyLib(withGranularMetadata = true)
Project("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
val intermediateMetadataCompileTask = ":compileJvmAndJsMainKotlinMetadata"
build(intermediateMetadataCompileTask) {
assertSuccessful()
checkNamesOnCompileClasspath(
intermediateMetadataCompileTask,
shouldInclude = listOf(
"third-party-lib" to "commonMain"
)
)
}
}
}
@Test
fun testDependenciesInNonPublishedSourceSets() {
publishThirdPartyLib(withGranularMetadata = true)
Project("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
testDependencyTransformations { reports ->
reports.single {
it.sourceSetName == "jvmAndJsMain" && it.scope == "api" && it.groupAndModule.startsWith("com.example")
}.let {
assertEquals(setOf("commonMain"), it.allVisibleSourceSets)
assertEquals(setOf("commonMain"), it.newVisibleSourceSets)
}
}
}
}
private fun Project.testDependencyTransformations(
subproject: String? = null,
check: CompiledProject.(reports: Iterable<DependencyTransformationReport>) -> Unit
) {
setupWorkingDir()
val buildGradleKts = gradleBuildScript(subproject)
assert(buildGradleKts.extension == "kts") { "Only Kotlin scripts are supported." }
val testTaskName = "reportDependencyTransformationsForTest"
if (testTaskName !in buildGradleKts.readText()) {
buildGradleKts.modify {
"import ${DefaultKotlinSourceSet::class.qualifiedName}\n" + it + "\n" + """
val $testTaskName by tasks.creating {
doFirst {
for (scope in listOf("api", "implementation", "compileOnly", "runtimeOnly")) {
println("========\n${'$'}scope\n")
kotlin.sourceSets.withType<DefaultKotlinSourceSet>().forEach { sourceSet ->
println("--------\n${'$'}{sourceSet.name}")
sourceSet
.getDependenciesTransformation(
"${'$'}{sourceSet.name}${'$'}{scope.capitalize()}DependenciesMetadata"
).forEach {
val line = listOf(
"${DependencyTransformationReport.TEST_OUTPUT_MARKER}",
sourceSet.name,
scope,
it.groupId + ":" + it.moduleName,
it.allVisibleSourceSets.joinToString(","),
it.useFilesForSourceSets.keys.joinToString(",")
)
println(" " + line.joinToString(" :: "))
}
println()
}
println()
}
}
}
""".trimIndent()
}
}
build(":${subproject?.plus(":").orEmpty()}$testTaskName") {
assertSuccessful()
val reports = output.lines()
.filter { DependencyTransformationReport.TEST_OUTPUT_MARKER in it }
.map { DependencyTransformationReport.parseTestOutputLine(it) }
check(this, reports)
}
}
private data class DependencyTransformationReport(
val sourceSetName: String,
val scope: String,
val groupAndModule: String,
val allVisibleSourceSets: Set<String>,
val newVisibleSourceSets: Set<String> // those which the dependsOn parents don't see
) {
val isExcluded: Boolean get() = allVisibleSourceSets.isEmpty()
companion object {
const val TEST_OUTPUT_MARKER = "###transformation"
const val TEST_OUTPUT_COMPONENT_SEPARATOR = " :: "
const val TEST_OUTPUT_ITEMS_SEPARATOR = ","
fun parseTestOutputLine(line: String): DependencyTransformationReport {
val tail = line.substringAfter(TEST_OUTPUT_MARKER + TEST_OUTPUT_COMPONENT_SEPARATOR)
val (sourceSetName, scope, groupAndModule, allVisibleSourceSets, newVisibleSourceSets) =
tail.split(TEST_OUTPUT_COMPONENT_SEPARATOR)
return DependencyTransformationReport(
sourceSetName, scope, groupAndModule,
allVisibleSourceSets.split(TEST_OUTPUT_ITEMS_SEPARATOR).filter { it.isNotEmpty() }.toSet(),
newVisibleSourceSets.split(TEST_OUTPUT_ITEMS_SEPARATOR).filter { it.isNotEmpty() }.toSet()
)
}
}
}
} }
@@ -11,6 +11,8 @@ import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_TEST_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
@@ -71,6 +73,9 @@ private typealias ModuleId = Pair<String?, String> // group ID, artifact ID
private val ResolvedDependency.moduleId: ModuleId private val ResolvedDependency.moduleId: ModuleId
get() = moduleGroup to moduleName get() = moduleGroup to moduleName
private val Dependency.moduleId: ModuleId
get() = group to name
internal class GranularMetadataTransformation( internal class GranularMetadataTransformation(
val project: Project, val project: Project,
val kotlinSourceSet: KotlinSourceSet, val kotlinSourceSet: KotlinSourceSet,
@@ -78,6 +83,7 @@ internal class GranularMetadataTransformation(
val sourceSetRequestedScopes: List<KotlinDependencyScope>, val sourceSetRequestedScopes: List<KotlinDependencyScope>,
/** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */ /** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */
val allSourceSetsConfigurations: Iterable<Configuration> val allSourceSetsConfigurations: Iterable<Configuration>
val parentTransformations: Lazy<Iterable<GranularMetadataTransformation>>
) { ) {
val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> by lazy { doTransform() } val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> by lazy { doTransform() }
@@ -115,48 +121,50 @@ internal class GranularMetadataTransformation(
return result return result
} }
private fun getRequestedDependencies(kotlinSourceSet: KotlinSourceSet): Set<Dependency> {
val hierarchy = kotlinSourceSet.getSourceSetHierarchy().toMutableSet()
// This is an ad-hoc mechanism for exposing the commonMain dependencies to test source sets as well: private val requestedDependencies: Iterable<Dependency> by lazy {
// TODO once a general production-test visibility mechanism is implemented, replace this workaround with the general solution fun collectScopedDependenciesFromSourceSet(sourceSet: KotlinSourceSet): Set<Dependency> =
if (hierarchy.any { it.name == KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME }) { sourceSetRequestedScopes.flatMapTo(mutableSetOf()) { scope ->
hierarchy += project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME).getSourceSetHierarchy()
}
return hierarchy.flatMapTo(mutableSetOf()) { sourceSet ->
sourceSetRequestedScopes.flatMap { scope ->
project.sourceSetDependencyConfigurationByScope(sourceSet, scope).allDependencies project.sourceSetDependencyConfigurationByScope(sourceSet, scope).allDependencies
} }
}
val ownDependencies = collectScopedDependenciesFromSourceSet(kotlinSourceSet)
val parentDependencies = parentTransformations.value.flatMapTo(mutableSetOf<Dependency>()) { it.requestedDependencies }
ownDependencies + parentDependencies
}
private val resolvedConfigurations: Iterable<LenientConfiguration> by lazy {
allSourceSetsConfigurations.map { it.resolvedConfiguration.lenientConfiguration }
} }
private fun doTransform(): Iterable<MetadataDependencyResolution> { private fun doTransform(): Iterable<MetadataDependencyResolution> {
val result = mutableListOf<MetadataDependencyResolution>() val result = mutableListOf<MetadataDependencyResolution>()
val resolvedDependenciesFromAllSourceSets = allSourceSetsConfigurations.map { it.resolvedConfiguration.lenientConfiguration } val parentResolutions =
parentTransformations.value.flatMap { it.metadataDependencyResolutions }.groupBy { it.dependency.moduleId }
val visitedDependencies = mutableSetOf<ResolvedDependency>() val allRequestedDependencies = requestedDependencies
val directRequestedDependencies = getRequestedDependencies(kotlinSourceSet) val allModuleDependencies = resolvedConfigurations.flatMap { it.allModuleDependencies }
val directRequestedModules: Set<ModuleId> = directRequestedDependencies.mapTo(mutableSetOf()) { it.group to it.name }
val allModuleDependencies = resolvedDependenciesFromAllSourceSets.flatMapTo(mutableSetOf()) { it.allModuleDependencies }
val knownProjectDependencies = collectProjectDependencies( val knownProjectDependencies = collectProjectDependencies(
directRequestedDependencies.filterIsInstance<ProjectDependency>(), allRequestedDependencies.filterIsInstance<ProjectDependency>(),
allModuleDependencies allModuleDependencies
) )
val resolvedDependencyQueue: Queue<ResolvedDependencyWithParent> = ArrayDeque<ResolvedDependencyWithParent>().apply { val resolvedDependencyQueue: Queue<ResolvedDependencyWithParent> = ArrayDeque<ResolvedDependencyWithParent>().apply {
val requestedModules: Set<ModuleId> = allRequestedDependencies.mapTo(mutableSetOf()) { it.moduleId }
addAll( addAll(
resolvedDependenciesFromAllSourceSets.flatMap { it.firstLevelModuleDependencies } resolvedConfigurations.flatMap { it.firstLevelModuleDependencies }
.filter { it.moduleId in directRequestedModules } .filter { it.moduleId in requestedModules }
.map { ResolvedDependencyWithParent(it, null) } .map { ResolvedDependencyWithParent(it, null) }
) )
} }
val visitedDependencies = mutableSetOf<ResolvedDependency>()
while (resolvedDependencyQueue.isNotEmpty()) { while (resolvedDependencyQueue.isNotEmpty()) {
val (resolvedDependency, parent: ResolvedDependency?) = resolvedDependencyQueue.poll() val (resolvedDependency, parent: ResolvedDependency?) = resolvedDependencyQueue.poll()
@@ -164,7 +172,13 @@ internal class GranularMetadataTransformation(
visitedDependencies.add(resolvedDependency) visitedDependencies.add(resolvedDependency)
val dependencyResult = processDependency(resolvedDependency, parent, projectDependency) val dependencyResult = processDependency(
resolvedDependency,
parentResolutions[resolvedDependency.moduleId].orEmpty(),
parent,
projectDependency
)
result.add(dependencyResult) result.add(dependencyResult)
val transitiveDependenciesToVisit = when (dependencyResult) { val transitiveDependenciesToVisit = when (dependencyResult) {
@@ -209,6 +223,7 @@ internal class GranularMetadataTransformation(
*/ */
private fun processDependency( private fun processDependency(
module: ResolvedDependency, module: ResolvedDependency,
parentResolutionsForModule: Iterable<MetadataDependencyResolution>,
parent: ResolvedDependency?, parent: ResolvedDependency?,
projectDependency: ProjectDependency? projectDependency: ProjectDependency?
): MetadataDependencyResolution { ): MetadataDependencyResolution {
@@ -220,13 +235,10 @@ internal class GranularMetadataTransformation(
} }
val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata() val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata()
?: return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency)
if (projectStructureMetadata == null) { val allVisibleSourceSets =
return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency) SourceSetVisibilityProvider(project).getVisibleSourceSetNames(
}
val (allVisibleSourceSets, visibleByParents) =
SourceSetVisibilityProvider(project).getVisibleSourceSets(
kotlinSourceSet, kotlinSourceSet,
sourceSetRequestedScopes, sourceSetRequestedScopes,
parent ?: module, parent ?: module,
@@ -234,6 +246,10 @@ internal class GranularMetadataTransformation(
projectDependency?.dependencyProject projectDependency?.dependencyProject
) )
val sourceSetsVisibleInParents = parentResolutionsForModule
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames }
// Keep only the transitive dependencies requested by the visible source sets: // Keep only the transitive dependencies requested by the visible source sets:
// Visit the transitive dependencies visible by parents, too (i.e. allVisibleSourceSets), as this source set might get a more // Visit the transitive dependencies visible by parents, too (i.e. allVisibleSourceSets), as this source set might get a more
// concrete view on them: // concrete view on them:
@@ -250,7 +266,7 @@ internal class GranularMetadataTransformation(
(it.moduleId) in requestedTransitiveDependencies (it.moduleId) in requestedTransitiveDependencies
} }
val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in visibleByParents } val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in sourceSetsVisibleInParents }
return object : MetadataDependencyResolution.ChooseVisibleSourceSets( return object : MetadataDependencyResolution.ChooseVisibleSourceSets(
module, module,
@@ -15,43 +15,29 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations
internal data class DependencySourceSetVisibilityResult(
val sourceSetsVisibleByThisSourceSet: Set<String>,
val sourceSetsVisibleThroughDependsOn: Set<String>
)
internal class SourceSetVisibilityProvider( internal class SourceSetVisibilityProvider(
private val project: Project private val project: Project
) { ) {
fun getVisibleSourceSets( /**
* Determine which source sets of the [resolvedMppDependency] are visible in the [visibleFrom] source set.
*
* This requires resolving dependencies of the compilations which [visibleFrom] takes part in, in order to find which variants the
* [resolvedMppDependency] got resolved to for those compilations. The [resolvedMppDependency] should therefore be the dependency
* on the 'root' module of the MPP (such as 'com.example:lib-foo', not 'com.example:lib-foo-metadata').
*
* Once the variants are known, they are checked against the [dependencyProjectStructureMetadata], and the
* source sets of the dependency are determined that are compiled for all those variants and thus should be visible here.
*
* If the [resolvedMppDependency] is a project dependency, its project should be passed as [resolvedToOtherProject], as
* the Gradle API for dependency variants behaves differently for project dependencies and published ones.
*/
@Suppress("UnstableApiUsage")
fun getVisibleSourceSetNames(
visibleFrom: KotlinSourceSet, visibleFrom: KotlinSourceSet,
dependencyScopes: Iterable<KotlinDependencyScope>, dependencyScopes: Iterable<KotlinDependencyScope>,
resolvedMppDependency: ResolvedDependency, resolvedMppDependency: ResolvedDependency,
dependencyProjectStructureMetadata: KotlinProjectStructureMetadata, dependencyProjectStructureMetadata: KotlinProjectStructureMetadata,
resolvedToOtherProject: Project? resolvedToOtherProject: Project?
): DependencySourceSetVisibilityResult {
val visibleByThisSourceSet =
getVisibleSourceSetsImpl(
visibleFrom, dependencyScopes, resolvedMppDependency, dependencyProjectStructureMetadata, resolvedToOtherProject
)
val visibleByParents = visibleFrom.dependsOn
.flatMapTo(mutableSetOf()) {
getVisibleSourceSetsImpl(
it, dependencyScopes, resolvedMppDependency, dependencyProjectStructureMetadata, resolvedToOtherProject
)
}
return DependencySourceSetVisibilityResult(visibleByThisSourceSet, visibleByParents)
}
@Suppress("UnstableApiUsage")
private fun getVisibleSourceSetsImpl(
visibleFrom: KotlinSourceSet,
dependencyScopes: Iterable<KotlinDependencyScope>,
mppDependency: ResolvedDependency,
dependencyProjectMetadata: KotlinProjectStructureMetadata,
otherProject: Project?
): Set<String> { ): Set<String> {
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(visibleFrom) val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(visibleFrom)
@@ -66,7 +52,8 @@ internal class SourceSetVisibilityProvider(
// Resolve the configuration but don't trigger artifacts download, only download component metadata: // Resolve the configuration but don't trigger artifacts download, only download component metadata:
configuration.incoming.resolutionResult.allComponents configuration.incoming.resolutionResult.allComponents
.find { .find {
it.moduleVersion?.group == mppDependency.moduleGroup && it.moduleVersion?.name == mppDependency.moduleName it.moduleVersion?.group == resolvedMppDependency.moduleGroup &&
it.moduleVersion?.name == resolvedMppDependency.moduleName
} }
?.variant?.displayName ?.variant?.displayName
} }
@@ -76,15 +63,15 @@ internal class SourceSetVisibilityProvider(
return emptySet() return emptySet()
} }
if (otherProject != null) { if (resolvedToOtherProject != null) {
val publishedVariants = getPublishedPlatformCompilations(otherProject).keys val publishedVariants = getPublishedPlatformCompilations(resolvedToOtherProject).keys
visiblePlatformVariantNames = visiblePlatformVariantNames.mapTo(mutableSetOf()) { configurationName -> visiblePlatformVariantNames = visiblePlatformVariantNames.mapTo(mutableSetOf()) { configurationName ->
publishedVariants.first { it.dependencyConfigurationName == configurationName }.name publishedVariants.first { it.dependencyConfigurationName == configurationName }.name
} }
} }
return dependencyProjectMetadata.sourceSetNamesByVariantName return dependencyProjectStructureMetadata.sourceSetNamesByVariantName
.filterKeys { it in visiblePlatformVariantNames } .filterKeys { it in visiblePlatformVariantNames }
.values.let { if (it.isEmpty()) emptySet() else it.reduce { acc, item -> acc intersect item } } .values.let { if (it.isEmpty()) emptySet() else it.reduce { acc, item -> acc intersect item } }
} }
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.API_SCOP
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.IMPLEMENTATION_SCOPE import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.IMPLEMENTATION_SCOPE
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetConfigurator
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@@ -64,13 +65,22 @@ open class TransformKotlinGranularMetadata
.allDependencies.map { listOf(it.group, it.name, it.version) }.toSet() .allDependencies.map { listOf(it.group, it.name, it.version) }.toSet()
} }
private val transformation = @get:Internal
internal val transformation: GranularMetadataTransformation by lazy {
GranularMetadataTransformation( GranularMetadataTransformation(
project, project,
kotlinSourceSet, kotlinSourceSet,
listOf(API_SCOPE, IMPLEMENTATION_SCOPE), listOf(API_SCOPE, IMPLEMENTATION_SCOPE),
listOf(project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)) listOf(project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)),
lazy {
KotlinMetadataTargetConfigurator.dependsOnWithInterCompilationDependencies(project, kotlinSourceSet).map {
project.tasks.withType(TransformKotlinGranularMetadata::class.java)
.getByName(KotlinMetadataTargetConfigurator.transformGranularMetadataTaskName(it))
.transformation
}
}
) )
}
@get:Internal @get:Internal
internal val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> internal val metadataDependencyResolutions: Iterable<MetadataDependencyResolution>
@@ -43,6 +43,17 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
companion object { companion object {
internal const val ALL_METADATA_JAR_NAME = "allMetadataJar" internal const val ALL_METADATA_JAR_NAME = "allMetadataJar"
internal fun transformGranularMetadataTaskName(sourceSet: KotlinSourceSet) =
lowerCamelCaseName("transform", sourceSet.name, "DependenciesMetadata")
// TODO generalize once a general production-test and other kinds of inter-compilation visibility are supported
// Currently, this is a temporary ad-hoc mechanism for exposing the commonMain dependencies to the test source sets
internal fun dependsOnWithInterCompilationDependencies(project: Project, sourceSet: KotlinSourceSet): Set<KotlinSourceSet> =
sourceSet.dependsOn.toMutableSet().apply {
if (sourceSet.name == KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
add(project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME))
}
} }
private val KotlinOnlyTarget<KotlinCommonCompilation>.apiElementsConfiguration: Configuration private val KotlinOnlyTarget<KotlinCommonCompilation>.apiElementsConfiguration: Configuration
@@ -97,9 +108,6 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
} }
} }
private fun transformGranularMetadataTaskName(sourceSet: KotlinSourceSet) =
lowerCamelCaseName("transform", sourceSet.name, "DependenciesMetadata")
private fun setupDependencyTransformationForCommonSourceSets(target: KotlinMetadataTarget) { private fun setupDependencyTransformationForCommonSourceSets(target: KotlinMetadataTarget) {
target.project.whenEvaluated { target.project.whenEvaluated {
val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project) val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project)
@@ -201,18 +209,22 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
isSourceSetPublished: Boolean isSourceSetPublished: Boolean
) { ) {
KotlinDependencyScope.values().forEach { scope -> KotlinDependencyScope.values().forEach { scope ->
val allMetadataConfigurations = mutableListOf<Configuration>().apply { val allMetadataConfiguration = project.configurations.getByName(
if (scope != KotlinDependencyScope.COMPILE_ONLY_SCOPE) when (scope) {
add(project.configurations.getByName(ALL_RUNTIME_METADATA_CONFIGURATION_NAME)) KotlinDependencyScope.RUNTIME_ONLY_SCOPE -> ALL_RUNTIME_METADATA_CONFIGURATION_NAME
if (scope != KotlinDependencyScope.RUNTIME_ONLY_SCOPE) else -> ALL_COMPILE_METADATA_CONFIGURATION_NAME
add(project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)) }
} )
val granularMetadataTransformation = GranularMetadataTransformation( val granularMetadataTransformation = GranularMetadataTransformation(
project, project,
sourceSet, sourceSet,
listOf(scope), listOf(scope),
allMetadataConfigurations allMetadataConfiguration,
lazy {
dependsOnWithInterCompilationDependencies(project, sourceSet).filterIsInstance<DefaultKotlinSourceSet>()
.map { checkNotNull(it.dependencyTransformations[scope]) }
}
) )
(sourceSet as DefaultKotlinSourceSet).dependencyTransformations[scope] = granularMetadataTransformation (sourceSet as DefaultKotlinSourceSet).dependencyTransformations[scope] = granularMetadataTransformation