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.plugin.mpp.KotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.ModuleDependencyIdentifier
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.util.modify
import java.io.File
import java.util.zip.ZipFile
@@ -24,13 +25,7 @@ class HierarchicalMppIT : BaseGradleIT() {
@Test
fun testPublishedModules() {
Project("third-party-lib", gradleVersion, "hierarchical-mpp-published-modules").run {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
build("publish") {
assertSuccessful()
}
}
publishThirdPartyLib(withGranularMetadata = false)
Project("my-lib-foo", gradleVersion, "hierarchical-mpp-published-modules").run {
setupWorkingDir()
@@ -59,13 +54,7 @@ class HierarchicalMppIT : BaseGradleIT() {
@Test
fun testProjectDependencies() {
Project("third-party-lib", gradleVersion, "hierarchical-mpp-project-dependency").run {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
build("publish") {
assertSuccessful()
}
}
publishThirdPartyLib(withGranularMetadata = false)
with(Project("hierarchical-mpp-project-dependency", gradleVersion)) {
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) {
assertSuccessful()
assertTasksExecuted(expectedTasks(subprojectPrefix))
@@ -273,7 +276,7 @@ class HierarchicalMppIT : BaseGradleIT() {
"compileKotlinMetadata",
"compileJvmAndJsMainKotlinMetadata",
"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:
private fun expectedProjectStructureMetadata(
@@ -317,4 +320,130 @@ class HierarchicalMppIT : BaseGradleIT() {
.use { inputStream -> documentBuilder.parse(inputStream) }
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.multiplatformExtension
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.getSourceSetHierarchy
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
get() = moduleGroup to moduleName
private val Dependency.moduleId: ModuleId
get() = group to name
internal class GranularMetadataTransformation(
val project: Project,
val kotlinSourceSet: KotlinSourceSet,
@@ -78,6 +83,7 @@ internal class GranularMetadataTransformation(
val sourceSetRequestedScopes: List<KotlinDependencyScope>,
/** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */
val allSourceSetsConfigurations: Iterable<Configuration>
val parentTransformations: Lazy<Iterable<GranularMetadataTransformation>>
) {
val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> by lazy { doTransform() }
@@ -115,48 +121,50 @@ internal class GranularMetadataTransformation(
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:
// TODO once a general production-test visibility mechanism is implemented, replace this workaround with the general solution
if (hierarchy.any { it.name == KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME }) {
hierarchy += project.kotlinExtension.sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME).getSourceSetHierarchy()
}
return hierarchy.flatMapTo(mutableSetOf()) { sourceSet ->
sourceSetRequestedScopes.flatMap { scope ->
private val requestedDependencies: Iterable<Dependency> by lazy {
fun collectScopedDependenciesFromSourceSet(sourceSet: KotlinSourceSet): Set<Dependency> =
sourceSetRequestedScopes.flatMapTo(mutableSetOf()) { scope ->
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> {
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 directRequestedModules: Set<ModuleId> = directRequestedDependencies.mapTo(mutableSetOf()) { it.group to it.name }
val allModuleDependencies = resolvedDependenciesFromAllSourceSets.flatMapTo(mutableSetOf()) { it.allModuleDependencies }
val allModuleDependencies = resolvedConfigurations.flatMap { it.allModuleDependencies }
val knownProjectDependencies = collectProjectDependencies(
directRequestedDependencies.filterIsInstance<ProjectDependency>(),
allRequestedDependencies.filterIsInstance<ProjectDependency>(),
allModuleDependencies
)
val resolvedDependencyQueue: Queue<ResolvedDependencyWithParent> = ArrayDeque<ResolvedDependencyWithParent>().apply {
val requestedModules: Set<ModuleId> = allRequestedDependencies.mapTo(mutableSetOf()) { it.moduleId }
addAll(
resolvedDependenciesFromAllSourceSets.flatMap { it.firstLevelModuleDependencies }
.filter { it.moduleId in directRequestedModules }
resolvedConfigurations.flatMap { it.firstLevelModuleDependencies }
.filter { it.moduleId in requestedModules }
.map { ResolvedDependencyWithParent(it, null) }
)
}
val visitedDependencies = mutableSetOf<ResolvedDependency>()
while (resolvedDependencyQueue.isNotEmpty()) {
val (resolvedDependency, parent: ResolvedDependency?) = resolvedDependencyQueue.poll()
@@ -164,7 +172,13 @@ internal class GranularMetadataTransformation(
visitedDependencies.add(resolvedDependency)
val dependencyResult = processDependency(resolvedDependency, parent, projectDependency)
val dependencyResult = processDependency(
resolvedDependency,
parentResolutions[resolvedDependency.moduleId].orEmpty(),
parent,
projectDependency
)
result.add(dependencyResult)
val transitiveDependenciesToVisit = when (dependencyResult) {
@@ -209,6 +223,7 @@ internal class GranularMetadataTransformation(
*/
private fun processDependency(
module: ResolvedDependency,
parentResolutionsForModule: Iterable<MetadataDependencyResolution>,
parent: ResolvedDependency?,
projectDependency: ProjectDependency?
): MetadataDependencyResolution {
@@ -220,13 +235,10 @@ internal class GranularMetadataTransformation(
}
val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata()
?: return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency)
if (projectStructureMetadata == null) {
return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency)
}
val (allVisibleSourceSets, visibleByParents) =
SourceSetVisibilityProvider(project).getVisibleSourceSets(
val allVisibleSourceSets =
SourceSetVisibilityProvider(project).getVisibleSourceSetNames(
kotlinSourceSet,
sourceSetRequestedScopes,
parent ?: module,
@@ -234,6 +246,10 @@ internal class GranularMetadataTransformation(
projectDependency?.dependencyProject
)
val sourceSetsVisibleInParents = parentResolutionsForModule
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames }
// 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
// concrete view on them:
@@ -250,7 +266,7 @@ internal class GranularMetadataTransformation(
(it.moduleId) in requestedTransitiveDependencies
}
val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in visibleByParents }
val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in sourceSetsVisibleInParents }
return object : MetadataDependencyResolution.ChooseVisibleSourceSets(
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.targets.metadata.getPublishedPlatformCompilations
internal data class DependencySourceSetVisibilityResult(
val sourceSetsVisibleByThisSourceSet: Set<String>,
val sourceSetsVisibleThroughDependsOn: Set<String>
)
internal class SourceSetVisibilityProvider(
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,
dependencyScopes: Iterable<KotlinDependencyScope>,
resolvedMppDependency: ResolvedDependency,
dependencyProjectStructureMetadata: KotlinProjectStructureMetadata,
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> {
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:
configuration.incoming.resolutionResult.allComponents
.find {
it.moduleVersion?.group == mppDependency.moduleGroup && it.moduleVersion?.name == mppDependency.moduleName
it.moduleVersion?.group == resolvedMppDependency.moduleGroup &&
it.moduleVersion?.name == resolvedMppDependency.moduleName
}
?.variant?.displayName
}
@@ -76,15 +63,15 @@ internal class SourceSetVisibilityProvider(
return emptySet()
}
if (otherProject != null) {
val publishedVariants = getPublishedPlatformCompilations(otherProject).keys
if (resolvedToOtherProject != null) {
val publishedVariants = getPublishedPlatformCompilations(resolvedToOtherProject).keys
visiblePlatformVariantNames = visiblePlatformVariantNames.mapTo(mutableSetOf()) { configurationName ->
publishedVariants.first { it.dependencyConfigurationName == configurationName }.name
}
}
return dependencyProjectMetadata.sourceSetNamesByVariantName
return dependencyProjectStructureMetadata.sourceSetNamesByVariantName
.filterKeys { it in visiblePlatformVariantNames }
.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.getSourceSetHierarchy
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 javax.inject.Inject
@@ -64,13 +65,22 @@ open class TransformKotlinGranularMetadata
.allDependencies.map { listOf(it.group, it.name, it.version) }.toSet()
}
private val transformation =
@get:Internal
internal val transformation: GranularMetadataTransformation by lazy {
GranularMetadataTransformation(
project,
kotlinSourceSet,
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
internal val metadataDependencyResolutions: Iterable<MetadataDependencyResolution>
@@ -43,6 +43,17 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
companion object {
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
@@ -97,9 +108,6 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
}
}
private fun transformGranularMetadataTaskName(sourceSet: KotlinSourceSet) =
lowerCamelCaseName("transform", sourceSet.name, "DependenciesMetadata")
private fun setupDependencyTransformationForCommonSourceSets(target: KotlinMetadataTarget) {
target.project.whenEvaluated {
val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project)
@@ -201,18 +209,22 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
isSourceSetPublished: Boolean
) {
KotlinDependencyScope.values().forEach { scope ->
val allMetadataConfigurations = mutableListOf<Configuration>().apply {
if (scope != KotlinDependencyScope.COMPILE_ONLY_SCOPE)
add(project.configurations.getByName(ALL_RUNTIME_METADATA_CONFIGURATION_NAME))
if (scope != KotlinDependencyScope.RUNTIME_ONLY_SCOPE)
add(project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME))
}
val allMetadataConfiguration = project.configurations.getByName(
when (scope) {
KotlinDependencyScope.RUNTIME_ONLY_SCOPE -> ALL_RUNTIME_METADATA_CONFIGURATION_NAME
else -> ALL_COMPILE_METADATA_CONFIGURATION_NAME
}
)
val granularMetadataTransformation = GranularMetadataTransformation(
project,
sourceSet,
listOf(scope),
allMetadataConfigurations
allMetadataConfiguration,
lazy {
dependsOnWithInterCompilationDependencies(project, sourceSet).filterIsInstance<DefaultKotlinSourceSet>()
.map { checkNotNull(it.dependencyTransformations[scope]) }
}
)
(sourceSet as DefaultKotlinSourceSet).dependencyTransformations[scope] = granularMetadataTransformation