[Gradle][MPP] Include cinterop metadata directory in kotlin-project-structure-metadata.json

- Also increase the KotlinProjectStructureMetadata's format
version to 0.3.2

- Move new default location into /cinterop/{sourceSetName}

^KT-49596 Verification Pending
This commit is contained in:
sebastian.sellmair
2021-11-08 15:29:35 +01:00
committed by Space
parent 7b017fad90
commit bd8aa8ae3d
9 changed files with 405 additions and 25 deletions
@@ -499,6 +499,7 @@ class HierarchicalMppIT : BaseGradleIT() {
ModuleDependencyIdentifier(it.first, it.second)
}.toSet()
},
sourceSetCInteropMetadataDirectory = mapOf(),
hostSpecificSourceSets = emptySet(),
sourceSetBinaryLayout = sourceSetModuleDependencies.mapValues { SourceSetMetadataLayout.KLIB },
isPublishedAsRoot = true
@@ -9,6 +9,7 @@ project.extensions.getByType<KotlinJvmProjectExtension>().target.compilations {
description = "Runs functional tests"
testClassesDirs = output.classesDirs
classpath = sourceSets["functionalTest"].runtimeClasspath
workingDir = projectDir
}
tasks.named("check") {
dependsOn(functionalTest)
@@ -37,7 +37,34 @@ class CompositeMetadataJarTest {
val metadataJar = CompositeMetadataJar(
moduleIdentifier = "test-id",
projectStructureMetadata = createProjectStructureMetadata(
sourceSetBinaryLayout = mapOf("testSourceSetName" to KLIB)
sourceSetBinaryLayout = mapOf("testSourceSetName" to KLIB),
sourceSetCInteropMetadataDirectory = mapOf("testSourceSetName" to "cinterop/testSourceSetName")
),
primaryArtifactFile = primaryArtifactFile,
hostSpecificArtifactsBySourceSet = emptyMap()
)
val metadataOutputDirectory = temporaryFolder.newFolder()
val metadataFile = metadataJar.getSourceSetCompiledMetadata("testSourceSetName", metadataOutputDirectory, true)
assertEquals(
File("test-id/test-id-testSourceSetName.klib"),
metadataFile.relativeTo(metadataOutputDirectory)
)
}
@Test
fun `empty jar - no cinterop metadata directory - get metadata - returns file`() {
val primaryArtifactContent = temporaryFolder.newFolder()
val primaryArtifactFile = temporaryFolder.newFile("metadata.jar")
zipTo(primaryArtifactFile, primaryArtifactContent)
assertTrue(primaryArtifactFile.isFile, "Expected primaryArtifactFile.isFile")
val metadataJar = CompositeMetadataJar(
moduleIdentifier = "test-id",
projectStructureMetadata = createProjectStructureMetadata(
sourceSetBinaryLayout = mapOf("testSourceSetName" to KLIB),
sourceSetCInteropMetadataDirectory = emptyMap(),
),
primaryArtifactFile = primaryArtifactFile,
hostSpecificArtifactsBySourceSet = emptyMap()
@@ -155,7 +182,7 @@ class CompositeMetadataJarTest {
.withParentDirectoriesCreated()
.writeText("stub2 content")
primaryArtifactContent.resolve("sourceSetB-cinterop/interopB0/stub3")
primaryArtifactContent.resolve("nested/sourceSetB/interops/interopB0/stub3")
.withParentDirectoriesCreated()
.writeText("stub3 content")
@@ -165,7 +192,12 @@ class CompositeMetadataJarTest {
val metadataJar = CompositeMetadataJar(
moduleIdentifier = "test-id",
projectStructureMetadata = createProjectStructureMetadata(),
projectStructureMetadata = createProjectStructureMetadata(
sourceSetCInteropMetadataDirectory = mapOf(
"sourceSetA" to "sourceSetA-cinterop",
"sourceSetB" to "nested/sourceSetB/interops/"
)
),
primaryArtifactFile = primaryArtifactFile,
hostSpecificArtifactsBySourceSet = emptyMap()
)
@@ -209,7 +241,7 @@ class CompositeMetadataJarTest {
?: fail("Failed to find 'interopB0.klib'")
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetB-cinterop/interopB0"), interopB0MetadataFile,
primaryArtifactContent.resolve("nested/sourceSetB/interops/interopB0"), interopB0MetadataFile,
"Expected correct content for extracted 'interopB0'"
)
}
@@ -219,6 +251,7 @@ class CompositeMetadataJarTest {
private fun createProjectStructureMetadata(
sourceSetNamesByVariantName: Map<String, Set<String>> = emptyMap(),
sourceSetsDependsOnRelation: Map<String, Set<String>> = emptyMap(),
sourceSetCInteropMetadataDirectory: Map<String, String> = emptyMap(),
sourceSetBinaryLayout: Map<String, SourceSetMetadataLayout> = emptyMap(),
sourceSetModuleDependencies: Map<String, Set<ModuleDependencyIdentifier>> = emptyMap(),
hostSpecificSourceSets: Set<String> = emptySet(),
@@ -228,6 +261,7 @@ private fun createProjectStructureMetadata(
sourceSetNamesByVariantName = sourceSetNamesByVariantName,
sourceSetsDependsOnRelation = sourceSetsDependsOnRelation,
sourceSetBinaryLayout = sourceSetBinaryLayout,
sourceSetCInteropMetadataDirectory = sourceSetCInteropMetadataDirectory,
sourceSetModuleDependencies = sourceSetModuleDependencies,
hostSpecificSourceSets = hostSpecificSourceSets,
isPublishedAsRoot = isPublishedAsRoot
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.SourceSetMetadataLayout.KLIB
import org.jetbrains.kotlin.gradle.plugin.mpp.SourceSetMetadataLayout.METADATA
import java.io.File
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class KotlinProjectStructureMetadataSerializationTest {
private val sampleMetadata = KotlinProjectStructureMetadata(
sourceSetNamesByVariantName = mapOf(
"sourceSetA" to setOf("value1", "value2"), "sourceSetB" to setOf("value3", "value4"), "sourceSetC" to setOf("value5")
),
sourceSetsDependsOnRelation = mapOf(
"sourceSetA" to setOf("commonMain"),
"sourceSetB" to setOf("commonMain", "sourceSetA"),
"sourceSetC" to setOf("commonMain", "sourceSetB")
),
sourceSetBinaryLayout = mapOf("sourceSetA" to METADATA, "sourceSetB" to KLIB, "sourceSetC" to KLIB),
sourceSetModuleDependencies = mapOf(
"sourceSetA" to setOf(ModuleDependencyIdentifier("aa", "bb")),
"sourceSetB" to setOf(ModuleDependencyIdentifier("cc", "dd"), ModuleDependencyIdentifier("ee", "ff")),
"sourceSetC" to emptySet()
),
sourceSetCInteropMetadataDirectory = mapOf("sourceSetB" to "xx/cinterop/", "sourceSetC" to "cinterops/C"),
hostSpecificSourceSets = setOf("sourceSetC"),
isPublishedAsRoot = true
)
@Test
fun `serialize and deserialize - json`() {
val json = sampleMetadata.toJson()
val deserialized = parseKotlinSourceSetMetadataFromJson(json)
assertEquals(sampleMetadata, deserialized)
}
@Test
fun `serialize and deserialize - xml`() {
val xml = sampleMetadata.toXmlDocument()
val deserialized = parseKotlinSourceSetMetadataFromXml(xml)
assertEquals(sampleMetadata, deserialized)
}
@Test
fun `deserialize 0_3_1 format version built from coroutines`() {
val json = File("src/functionalTest/resources/coroutines-kotlin-project-structure-metadata.0_3_1.json").absoluteFile.readText()
val deserialized = assertNotNull(parseKotlinSourceSetMetadataFromJson(json))
assertEquals(KotlinProjectStructureMetadata.FORMAT_VERSION_0_3_1, deserialized.formatVersion)
assertTrue(deserialized.isPublishedAsRoot)
assertEquals(setOf("commonMain", "concurrentMain"), deserialized.sourceSetsDependsOnRelation["nativeMain"])
}
}
@@ -0,0 +1,243 @@
{
"projectStructure": {
"formatVersion": "0.3.1",
"isPublishedAsRoot": "true",
"variants": [
{
"name": "iosArm32ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "iosArm64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "iosSimulatorArm64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "iosX64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "jsLegacyApiElements",
"sourceSet": [
"commonMain"
]
},
{
"name": "jsLegacyRuntimeElements",
"sourceSet": [
"commonMain"
]
},
{
"name": "jsIrApiElements",
"sourceSet": [
"commonMain"
]
},
{
"name": "jsIrRuntimeElements",
"sourceSet": [
"commonMain"
]
},
{
"name": "jvmApiElements",
"sourceSet": [
"commonMain",
"concurrentMain"
]
},
{
"name": "jvmRuntimeElements",
"sourceSet": [
"commonMain",
"concurrentMain"
]
},
{
"name": "linuxX64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeOtherMain",
"concurrentMain"
]
},
{
"name": "macosArm64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "macosX64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "mingwX64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeOtherMain",
"concurrentMain"
]
},
{
"name": "tvosArm64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "tvosSimulatorArm64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "tvosX64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "watchosArm32ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "watchosArm64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "watchosSimulatorArm64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "watchosX64ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
},
{
"name": "watchosX86ApiElements",
"sourceSet": [
"commonMain",
"nativeMain",
"nativeDarwinMain",
"concurrentMain"
]
}
],
"sourceSets": [
{
"name": "commonMain",
"dependsOn": [],
"moduleDependency": [
"org.jetbrains.kotlin:kotlin-stdlib-common"
],
"binaryLayout": "klib"
},
{
"name": "concurrentMain",
"dependsOn": [
"commonMain"
],
"moduleDependency": [],
"binaryLayout": "klib"
},
{
"name": "nativeDarwinMain",
"dependsOn": [
"nativeMain"
],
"moduleDependency": [
"org.jetbrains.kotlin:kotlin-stdlib-common",
"org.jetbrains.kotlinx:atomicfu"
],
"binaryLayout": "klib",
"hostSpecific": "true"
},
{
"name": "nativeMain",
"dependsOn": [
"commonMain",
"concurrentMain"
],
"moduleDependency": [
"org.jetbrains.kotlin:kotlin-stdlib-common",
"org.jetbrains.kotlinx:atomicfu"
],
"binaryLayout": "klib"
},
{
"name": "nativeOtherMain",
"dependsOn": [
"nativeMain"
],
"moduleDependency": [
"org.jetbrains.kotlin:kotlin-stdlib-common",
"org.jetbrains.kotlinx:atomicfu"
],
"binaryLayout": "klib"
}
]
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.plugin.mpp
import org.jetbrains.kotlin.gradle.utils.copyZipFilePartially
import org.jetbrains.kotlin.gradle.utils.ensureValidZipDirectoryPath
import org.jetbrains.kotlin.gradle.utils.listDescendants
import java.io.File
import java.util.zip.ZipFile
@@ -72,16 +73,17 @@ private class CompositeMetadataJarImpl(
val moduleOutputDirectory = outputDirectory.resolve(moduleIdentifier).also(File::mkdirs)
ZipFile(getArtifactFile(sourceSetName)).use { artifactZipFile ->
val cinteropRootPath = "$sourceSetName-cinterop/"
val cinteropEntries = artifactZipFile.listDescendants(cinteropRootPath)
val cinteropMetadataDirectory = projectStructureMetadata.sourceSetCInteropMetadataDirectory[sourceSetName] ?: return emptySet()
val cinteropMetadataDirectoryPath = ensureValidZipDirectoryPath(cinteropMetadataDirectory)
val cinteropEntries = artifactZipFile.listDescendants(cinteropMetadataDirectoryPath)
val cinteropNames = cinteropEntries.map { entry ->
entry.name.removePrefix(cinteropRootPath).split("/", limit = 2).first()
entry.name.removePrefix(cinteropMetadataDirectoryPath).split("/", limit = 2).first()
}.toSet()
val cinteropsByOutputFile = cinteropNames.associateBy { cinteropName -> moduleOutputDirectory.resolve("$cinteropName.klib") }
if (materializeFiles) {
cinteropsByOutputFile.forEach { (cinteropOutputFile, cinteropName) ->
copyZipFilePartially(artifactFile, cinteropOutputFile, "$cinteropRootPath$cinteropName/")
copyZipFilePartially(artifactFile, cinteropOutputFile, "$cinteropMetadataDirectoryPath$cinteropName/")
}
}
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurati
import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies
import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations
import org.jetbrains.kotlin.gradle.targets.metadata.isSharedNativeSourceSet
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerCompositeMetadataJarBundling.cinteropMetadataDirectoryPath
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
@@ -105,6 +106,9 @@ data class KotlinProjectStructureMetadata(
@Internal
val sourceSetModuleDependencies: Map<String, Set<ModuleDependencyIdentifier>>,
@Input
val sourceSetCInteropMetadataDirectory: Map<String, String>,
@Input
val hostSpecificSourceSets: Set<String>,
@@ -112,7 +116,7 @@ data class KotlinProjectStructureMetadata(
val isPublishedAsRoot: Boolean,
@Input
val formatVersion: String = FORMAT_VERSION_0_3_1
val formatVersion: String = FORMAT_VERSION_0_3_2
) : Serializable {
@Suppress("UNUSED") // Gradle input
@get:Input
@@ -130,6 +134,9 @@ data class KotlinProjectStructureMetadata(
// + 'isPublishedInRootModule' top-level flag
internal const val FORMAT_VERSION_0_3_1 = "0.3.1"
// + 'sourceSetCInteropMetadataDirectory' map
internal const val FORMAT_VERSION_0_3_2 = "0.3.2"
}
}
@@ -173,6 +180,9 @@ internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjec
}
sourceSet.name to sourceSetExportedDependencies.map { ModuleIds.fromDependency(it) }.toSet()
},
sourceSetCInteropMetadataDirectory = sourceSetsWithMetadataCompilations.keys
.filter { isSharedNativeSourceSet(project, it) }
.associate { sourceSet -> sourceSet.name to cinteropMetadataDirectoryPath(sourceSet.name) },
hostSpecificSourceSets = getHostSpecificSourceSets(project)
.filter { it in sourceSetsWithMetadataCompilations }.map { it.name }
.toSet(),
@@ -206,11 +216,12 @@ internal fun buildProjectStructureMetadata(module: KotlinGradleModule): KotlinPr
}
return KotlinProjectStructureMetadata(
expandVariantKeys(kotlinFragmentsPerKotlinVariant),
fragmentRefinesRelation,
module.fragments.associate { it.name to SourceSetMetadataLayout.KLIB },
fragmentDependencies,
getHostSpecificFragments(module).mapTo(mutableSetOf()) { it.name },
sourceSetNamesByVariantName = expandVariantKeys(kotlinFragmentsPerKotlinVariant),
sourceSetsDependsOnRelation = fragmentRefinesRelation,
sourceSetBinaryLayout = module.fragments.associate { it.name to SourceSetMetadataLayout.KLIB },
sourceSetModuleDependencies = fragmentDependencies,
sourceSetCInteropMetadataDirectory = emptyMap(), // Not supported yet
hostSpecificSourceSets = getHostSpecificFragments(module).mapTo(mutableSetOf()) { it.name },
isPublishedAsRoot = true
)
}
@@ -246,6 +257,9 @@ internal fun <Serializer> KotlinProjectStructureMetadata.serialize(
multiValue(MODULE_DEPENDENCY_NODE_NAME, sourceSetModuleDependencies[sourceSet].orEmpty().map { moduleDependency ->
moduleDependency.groupId + ":" + moduleDependency.moduleId
})
sourceSetCInteropMetadataDirectory[sourceSet]?.let { cinteropMetadataDirectory ->
value(SOURCE_SET_CINTEROP_METADATA_NODE_NAME, cinteropMetadataDirectory)
}
sourceSetBinaryLayout[sourceSet]?.let { binaryLayout ->
value(BINARY_LAYOUT_NODE_NAME, binaryLayout.name)
}
@@ -335,6 +349,7 @@ internal fun <ParsingContext> parseKotlinSourceSetMetadata(
val sourceSetDependsOnRelation = mutableMapOf<String, Set<String>>()
val sourceSetModuleDependencies = mutableMapOf<String, Set<ModuleDependencyIdentifier>>()
val sourceSetBinaryLayout = mutableMapOf<String, SourceSetMetadataLayout>()
val sourceSetCInteropMetadataDirectory = mutableMapOf<String, String>()
val hostSpecificSourceSets = mutableSetOf<String>()
val sourceSetsNode = projectStructureNode.multiObjects(SOURCE_SETS_NODE_NAME)
@@ -348,6 +363,10 @@ internal fun <ParsingContext> parseKotlinSourceSetMetadata(
ModuleDependencyIdentifier(groupId, moduleId)
}
sourceSetNode.valueNamed(SOURCE_SET_CINTEROP_METADATA_NODE_NAME)?.let { cinteropMetadataDirectory ->
sourceSetCInteropMetadataDirectory[sourceSetName] = cinteropMetadataDirectory
}
sourceSetNode.valueNamed(HOST_SPECIFIC_NODE_NAME)
?.let { if (it.toBoolean()) hostSpecificSourceSets.add(sourceSetName) }
@@ -360,13 +379,14 @@ internal fun <ParsingContext> parseKotlinSourceSetMetadata(
}
return KotlinProjectStructureMetadata(
sourceSetsByVariant,
sourceSetDependsOnRelation,
sourceSetBinaryLayout,
sourceSetModuleDependencies,
hostSpecificSourceSets,
isPublishedAsRoot,
formatVersion
sourceSetNamesByVariantName = sourceSetsByVariant,
sourceSetsDependsOnRelation = sourceSetDependsOnRelation,
sourceSetBinaryLayout = sourceSetBinaryLayout,
sourceSetModuleDependencies = sourceSetModuleDependencies,
sourceSetCInteropMetadataDirectory = sourceSetCInteropMetadataDirectory,
hostSpecificSourceSets = hostSpecificSourceSets,
isPublishedAsRoot = isPublishedAsRoot,
formatVersion = formatVersion
)
}
@@ -403,6 +423,7 @@ private const val VARIANT_NODE_NAME = "variant"
private const val NAME_NODE_NAME = "name"
private const val SOURCE_SETS_NODE_NAME = "sourceSets"
private const val SOURCE_SET_NODE_NAME = "sourceSet"
private const val SOURCE_SET_CINTEROP_METADATA_NODE_NAME = "sourceSetCInteropMetadataDirectory"
private const val DEPENDS_ON_NODE_NAME = "dependsOn"
private const val MODULE_DEPENDENCY_NODE_NAME = "moduleDependency"
private const val BINARY_LAYOUT_NODE_NAME = "binaryLayout"
@@ -9,6 +9,7 @@ import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerCompositeMetadataJarBundling.cinteropMetadataDirectoryPath
internal fun Project.includeCommonizedCInteropMetadata(
metadataKlib: TaskProvider<out Zip>, compilation: KotlinSharedNativeCompilation
@@ -22,6 +23,12 @@ internal fun Project.includeCommonizedCInteropMetadata(metadataKlib: Zip, compil
val outputDirectory = commonizerTask.commonizedOutputDirectory(commonizerDependencyToken) ?: return
metadataKlib.from(outputDirectory) { spec ->
spec.into(compilation.defaultSourceSet.name + "-cinterop")
spec.into(cinteropMetadataDirectoryPath(compilation.defaultSourceSetName))
}
}
internal object CInteropCommonizerCompositeMetadataJarBundling {
fun cinteropMetadataDirectoryPath(sourceSetName: String): String {
return "cinterop/$sourceSetName/"
}
}
@@ -11,7 +11,7 @@ import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
internal fun copyZipFilePartially(sourceZipFile: File, destinationZipFile: File, path: String) {
requireValidZipPath(path)
requireValidZipDirectoryPath(path)
ZipFile(sourceZipFile).use { zip ->
val entries = zip.listDescendants(path).toList()
@@ -35,12 +35,19 @@ internal fun copyZipFilePartially(sourceZipFile: File, destinationZipFile: File,
}
internal fun ZipFile.listDescendants(path: String): Sequence<ZipEntry> {
requireValidZipPath(path)
requireValidZipDirectoryPath(path)
return entries().asSequence().filter { entry ->
entry.name != path && entry.name.startsWith(path)
}
}
private fun requireValidZipPath(path: String) = require(path.isEmpty() || path.endsWith("/")) {
internal fun ensureValidZipDirectoryPath(path: String): String {
if (isValidZipDirectoryPath(path)) return path
return "$path/".also(::requireValidZipDirectoryPath)
}
private fun requireValidZipDirectoryPath(path: String) = require(isValidZipDirectoryPath(path)) {
"Expected path to end with '/', found '$path'"
}
private fun isValidZipDirectoryPath(path: String) = path.isEmpty() || path.endsWith("/")