[Gradle, MPP] Test sources publication as gradle variants
^KT-36943
This commit is contained in:
+96
@@ -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] } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+123
-2
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -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)
|
||||
}
|
||||
+63
@@ -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>")
|
||||
}
|
||||
+81
@@ -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() }
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user