[Gradle, MPP] Test sources publication as gradle variants

^KT-36943
This commit is contained in:
Anton Lakotka
2022-11-29 14:56:58 +01:00
committed by teamcity
parent 3a418bfe66
commit fd7ceabab6
6 changed files with 396 additions and 2 deletions
@@ -797,6 +797,63 @@ class HierarchicalMppIT : KGPBaseTest() {
actualSourcePublicationLayoutBySourcesFile.stringifyForBeautifulDiff()
)
}
project(
"mpp-sources-publication/consumer",
gradleVersion = gradleVersion,
localRepoDir = tempDir
) {
buildGradleKts.appendText("""
testResolutionToSourcesVariant(
"common",
KotlinPlatformType.common,
includeDisambiguation = false
)
testResolutionToSourcesVariant(
"jvm",
KotlinPlatformType.jvm
)
testResolutionToSourcesVariant(
"jvm2",
KotlinPlatformType.jvm
)
testResolutionToSourcesVariant(
"linuxX64",
KotlinPlatformType.native,
nativePlatform = "linux_x64"
)
""".trimIndent())
val expectedReports = mapOf(
"common" to SourcesVariantResolutionReport(
files = listOf("lib-kotlin-1.0-sources.jar"),
dependencyToVariant = mapOf("test:lib:1.0" to "metadataSourcesElements")
),
"jvm" to SourcesVariantResolutionReport(
files = listOf("lib-jvm-1.0-sources.jar"),
dependencyToVariant = mapOf("test:lib:1.0" to "jvmSourcesElements-published",
"test:lib-jvm:1.0" to "jvmSourcesElements-published")
),
"jvm2" to SourcesVariantResolutionReport(
files = listOf("lib-jvm2-1.0-sources.jar"),
dependencyToVariant = mapOf("test:lib:1.0" to "jvm2SourcesElements-published",
"test:lib-jvm2:1.0" to "jvm2SourcesElements-published")
),
"linuxX64" to SourcesVariantResolutionReport(
files = listOf("lib-linuxx64-1.0-sources.jar"),
dependencyToVariant = mapOf("test:lib:1.0" to "linuxX64SourcesElements-published",
"test:lib-linuxx64:1.0" to "linuxX64SourcesElements-published")
),
)
build("help") { // evaluate only
val actualReports = SourcesVariantResolutionReport.parse(output, expectedReports.keys)
assertEquals(expectedReports, actualReports)
}
}
}
@GradleTest
@@ -1083,4 +1140,43 @@ class HierarchicalMppIT : KGPBaseTest() {
}
}
}
private data class SourcesVariantResolutionReport(
val files: List<String>,
val dependencyToVariant: Map<String, String>
) {
companion object {
fun parse(output: String, targetNames: Iterable<String>): Map<String, SourcesVariantResolutionReport> {
val lines = output.lines()
return targetNames.associateWith { targetName -> lines.parseForTarget(targetName) }
}
private fun List<String>.parseForTarget(targetName: String) = SourcesVariantResolutionReport(
files = parseFiles(targetName),
dependencyToVariant = parseResolvedDependencies(targetName)
)
private fun List<String>.betweenMarkers(
start: String,
end: String
): List<String> {
val startPos = indexOf(start)
val endPos = indexOf(end)
return subList(startPos + 1, endPos)
}
private fun List<String>.parseFiles(targetName: String): List<String> =
betweenMarkers(
"<RESOLVED SOURCES FILE $targetName>",
"</RESOLVED SOURCES FILE $targetName>"
)
private fun List<String>.parseResolvedDependencies(targetName: String): Map<String, String> =
betweenMarkers(
"<RESOLVED DEPENDENCIES OF $targetName>",
"</RESOLVED DEPENDENCIES OF $targetName>"
).associate { it.split(" => ").let { it[0] to it[1] } }
}
}
}
@@ -196,6 +196,19 @@ class KotlinAndroidMppIT : KGPBaseTest() {
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
val androidSourcesElementsAttributes = arrayOf(
"org.gradle.category" to "documentation",
"org.gradle.dependency.bundling" to "external",
"org.gradle.docstype" to "sources",
"org.gradle.libraryelements" to "jar",
"org.gradle.usage" to "java-runtime",
"org.jetbrains.kotlin.platform.type" to "androidJvm",
// user-specific attributes that added manually in build script
"com.example.compilation" to "release",
"com.example.target" to "androidLib",
)
project(
"new-mpp-android",
gradleVersion,
@@ -222,6 +235,12 @@ class KotlinAndroidMppIT : KGPBaseTest() {
build("publish") {
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
assertGradleVariant(
groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.module"),
"releaseSourcesElements-published"
) {
assertAttributesEquals(*androidSourcesElementsAttributes)
}
assertFileInProjectNotExists("$groupDir/lib-androidlib-debug")
}
groupDir.deleteRecursively()
@@ -237,8 +256,24 @@ class KotlinAndroidMppIT : KGPBaseTest() {
build("publish") {
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
assertGradleVariant(
groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.module"),
"releaseSourcesElements-published"
) {
assertAttributesEquals(*androidSourcesElementsAttributes)
}
assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0-sources.jar"))
assertGradleVariant(
groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.module"),
"debugSourcesElements-published"
) {
assertAttributesEquals(
*androidSourcesElementsAttributes,
"com.example.compilation" to "debug",
"com.android.build.api.attributes.BuildTypeAttr" to "debug"
)
}
}
groupDir.deleteRecursively()
@@ -255,6 +290,22 @@ class KotlinAndroidMppIT : KGPBaseTest() {
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug.aar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug-sources.jar"))
assertGradleVariant(
groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.module"),
"releaseSourcesElements-published"
) {
assertAttributesEquals(*androidSourcesElementsAttributes)
}
assertGradleVariant(
groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.module"),
"debugSourcesElements-published"
) {
assertAttributesEquals(
*androidSourcesElementsAttributes,
"com.example.compilation" to "debug",
"com.android.build.api.attributes.BuildTypeAttr" to "debug"
)
}
}
groupDir.deleteRecursively()
@@ -267,11 +318,45 @@ class KotlinAndroidMppIT : KGPBaseTest() {
""".trimIndent()
)
build("publish") {
listOf("foobar", "foobaz").forEach { flavor ->
listOf("fooBar", "fooBaz").forEach { flavorName ->
val flavor = flavorName.lowercase()
val flavorAttributes = if (AGPVersion.fromString(agpVersion) >= AGPVersion.v7_0_0) {
arrayOf(
"foo" to flavorName,
"com.android.build.api.attributes.ProductFlavor:foo" to flavorName
)
} else {
arrayOf(
"foo" to flavorName
)
}
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-sources.jar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug-sources.jar"))
assertGradleVariant(
groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.module"),
"${flavorName}ReleaseSourcesElements-published"
) {
assertAttributesEquals(
*androidSourcesElementsAttributes,
*flavorAttributes,
"com.example.compilation" to "${flavorName}Release",
)
}
assertGradleVariant(
groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.module"),
"${flavorName}DebugSourcesElements-published"
) {
assertAttributesEquals(
*androidSourcesElementsAttributes,
*flavorAttributes,
"com.example.compilation" to "${flavorName}Debug",
"com.android.build.api.attributes.BuildTypeAttr" to "debug"
)
}
}
}
groupDir.deleteRecursively()
@@ -285,11 +370,47 @@ class KotlinAndroidMppIT : KGPBaseTest() {
""".trimIndent()
)
build("publish") {
listOf("foobar", "foobaz").forEach { flavor ->
listOf("fooBar", "fooBaz").forEach { flavorName ->
val flavor = flavorName.lowercase()
val flavorAttributes = if (AGPVersion.fromString(agpVersion) >= AGPVersion.v7_0_0) {
arrayOf(
"foo" to flavorName,
"com.android.build.api.attributes.ProductFlavor:foo" to flavorName
)
} else {
arrayOf(
"foo" to flavorName
)
}
listOf("-debug", "").forEach { buildType ->
assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0-sources.jar"))
}
assertGradleVariant(
groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.module"),
"${flavorName}ReleaseSourcesElements-published"
) {
assertAttributesEquals(
*androidSourcesElementsAttributes,
*flavorAttributes,
"com.example.compilation" to "${flavorName}Release",
)
}
assertGradleVariant(
groupDir.resolve("lib-androidlib-$flavor-debug/1.0/lib-androidlib-$flavor-debug-1.0.module"),
"${flavorName}DebugSourcesElements-published"
) {
assertAttributesEquals(
*androidSourcesElementsAttributes,
*flavorAttributes,
"com.example.compilation" to "${flavorName}Debug",
"com.android.build.api.attributes.BuildTypeAttr" to "debug"
)
}
}
}
}
@@ -5,11 +5,14 @@
package org.jetbrains.kotlin.gradle.testbase
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.readText
import kotlin.test.assertEquals
import kotlin.test.asserter
/**
@@ -210,3 +213,33 @@ fun assertContainsFiles(expected: Iterable<Path>, actual: Iterable<Path>, messag
"Actual set: ${actualSet.sorted().joinToString(", ")}\n"
}, actualSet.containsAll(expectedSet))
}
class GradleVariantAssertions(
val variantJson: JsonObject
) {
fun assertAttributesEquals(expected: Map<String, String>) {
val attributesJson = variantJson.getAsJsonObject("attributes")
val actual = attributesJson.keySet().associateWith { attributesJson.get(it).asString }
assertEquals(expected.toSortedStringWithLines(), actual.toSortedStringWithLines())
}
fun assertAttributesEquals(vararg expected: Pair<String, String>) = assertAttributesEquals(expected.toMap())
}
private fun Map<String, Any?>.toSortedStringWithLines() = entries
.sortedBy { it.key }
.joinToString("\n") { (key, value) -> "'$key' => '$value'" }
fun assertGradleVariant(gradleModuleFile: Path, variantName: String, code: GradleVariantAssertions.() -> Unit) {
val moduleJson = JsonParser.parseString(gradleModuleFile.readText()).asJsonObject
val variants = moduleJson.getAsJsonArray("variants")
val variantJson = variants.find { it.asJsonObject.get("name").asString == variantName }
if (variantJson == null) {
val existingVariants = variants.map { it.asJsonObject.get("name").asString }
throw AssertionError("Variant with name '$variantName' doesn't exist; Existing variants: $existingVariants")
}
GradleVariantAssertions(variantJson.asJsonObject).apply(code)
}
@@ -0,0 +1,63 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform") apply false
`maven-publish`
}
repositories {
mavenLocal()
maven("<localRepo>")
}
val disambiguationAttribute = Attribute.of("disambiguationAttribute", String::class.java)
fun testResolutionToSourcesVariant(
targetName: String,
platformType: KotlinPlatformType,
nativePlatform: String? = null,
includeDisambiguation: Boolean = true
) {
val configuration = configurations.create("${targetName}Sources") {
isCanBeResolved = true
isCanBeConsumed = false
if (platformType == KotlinPlatformType.jvm) {
attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named("java-runtime"))
} else {
attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named("kotlin-runtime"))
}
attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
attributes.attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
attributes.attribute(KotlinPlatformType.attribute, platformType)
if (nativePlatform != null) {
attributes.attribute(KotlinNativeTarget.konanTargetAttribute, nativePlatform)
}
if (includeDisambiguation) {
attributes.attribute(disambiguationAttribute, targetName)
}
project.dependencies.add(name, "test:lib:1.0")
}
val files = configuration.resolve()
println("<RESOLVED SOURCES FILE $targetName>")
println(files.joinToString("\n") { it.name })
println("</RESOLVED SOURCES FILE $targetName>")
println("<RESOLVED DEPENDENCIES OF $targetName>")
val resolvedVariants = configuration
.incoming
.resolutionResult
.allDependencies
.filterIsInstance<ResolvedDependencyResult>()
.map { it.requested.displayName to it.resolvedVariant.displayName }
.joinToString("\n") { "${it.first} => ${it.second}" }
println(resolvedVariants)
println("</RESOLVED DEPENDENCIES OF $targetName>")
}
@@ -7,12 +7,19 @@
package org.jetbrains.kotlin.gradle
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.component.ComponentWithVariants
import org.gradle.api.component.SoftwareComponent
import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
@@ -56,4 +63,78 @@ class MppPublicationTest {
assertTrue(sources.isNotEmpty(), "Expected at least one sources artifact for ${publication.name}")
}
}
@Test
fun `jvm sources elements has same attributes as java sources elements from default java gradle plugin`() {
val javaProject = ProjectBuilder.builder().build().run {
// Given
plugins.apply("java")
plugins.apply("maven-publish")
extensions.getByType(JavaPluginExtension::class.java).run {
withSourcesJar()
}
// When
this as ProjectInternal
this.evaluate()
}
val javaSourcesElementsAttributes = javaProject
.components
.getByName("java")
.attributesOfUsageContext("sourcesElements")
project.evaluate()
val kotlinComponent = project.components.getByName("kotlin") as ComponentWithVariants
val jvmComponent = kotlinComponent.variants.first { it.name == "jvm" }
val jvmSourcesElementsAttributes = jvmComponent.attributesOfUsageContext("jvmSourcesElements-published")
val extraExpectedAttributes = mapOf(
"org.jetbrains.kotlin.platform.type" to "jvm", // target disambiguation attribute
"org.gradle.libraryelements" to "jar" // is set by default by kgp, when it sets usage as `java-runtime-jars`
// see [KotlinUsages.producerApiUsage]
)
val expectedAttributes = javaSourcesElementsAttributes.toMapOfStrings() + extraExpectedAttributes
val actualAttributes = jvmSourcesElementsAttributes.toMapOfStrings()
assertEquals(expectedAttributes, actualAttributes)
}
@Test
fun `sources elements includes user-defined attributes`() {
val userAttribute = Attribute.of("userAttribute", String::class.java)
kotlin.run {
targets.all { target -> target.attributes { attribute(userAttribute, target.name) } }
js(IR)
linuxX64()
iosX64()
}
project.evaluate()
val kotlinComponent = project.components.getByName("kotlin") as ComponentWithVariants
val sourcesElements = listOf("jvm", "js", "linuxX64", "iosX64")
.map { targetName ->
targetName to kotlinComponent.variants
.first { it.name == targetName }
.attributesOfUsageContext("${targetName}SourcesElements-published")
}
for ( (targetName, sourceElements) in sourcesElements) {
assertTrue(
message = "Sources Elements of target $targetName doesn't have 'userAttribute'"
) { sourceElements.attributes.toMapOfStrings().containsKey("userAttribute") }
}
}
private fun SoftwareComponent.attributesOfUsageContext(usageContextName: String): AttributeContainer {
this as SoftwareComponentInternal
return usages.first { it.name == usageContextName }.attributes
}
private fun AttributeContainer.toMapOfStrings(): Map<String, String> = keySet()
.associate { key -> key.name to getAttribute(key).toString() }
}