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()
)
}
}
}
}