[Gradle][MPP] Flatten transformed metadata library directories

The main purpose of this commit is to fix KT-48135 and to
de-duplicate libraries for the IDE whilst also minimizing
zip operations during IDE import.

However, with this commit the metadata library directories layout
also gets changed for CLI builds. The overall layout
is now more shared with cinterops and the IDE.

^KT-48135 Verification Pending
This commit is contained in:
Sebastian Sellmair
2022-09-14 12:26:22 +02:00
committed by Space
parent 7a1f4304d0
commit 3cee2ca409
15 changed files with 270 additions and 565 deletions
@@ -10,8 +10,8 @@ import org.jetbrains.kotlin.konan.target.HostManager
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.zip.ZipFile import java.util.zip.ZipFile
import kotlin.test.Test
import kotlin.test.Ignore import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
@@ -92,10 +92,10 @@ class KlibBasedMppIT : BaseGradleIT() {
checkTaskCompileClasspath( checkTaskCompileClasspath(
"compile${hostSpecificSourceSet.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}KotlinMetadata", "compile${hostSpecificSourceSet.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}KotlinMetadata",
listOf( listOf(
"published-producer-$hostSpecificSourceSet.klib", "published-producer-1.0-$hostSpecificSourceSet.klib",
"published-producer-commonMain.klib", "published-producer-1.0-commonMain.klib",
"published-dependency-$hostSpecificSourceSet.klib", "published-dependency-1.0-$hostSpecificSourceSet.klib",
"published-dependency-commonMain.klib" "published-dependency-1.0-commonMain.klib"
) )
) )
} }
@@ -357,6 +357,7 @@ class KlibBasedMppIT : BaseGradleIT() {
build("${subproject?.prependIndent(":").orEmpty()}:$printingTaskName") { build("${subproject?.prependIndent(":").orEmpty()}:$printingTaskName") {
assertSuccessful() assertSuccessful()
val itemsLine = output.lines().single { "###$printingTaskName" in it }.substringAfter(printingTaskName) val itemsLine = output.lines().single { "###$printingTaskName" in it }.substringAfter(printingTaskName)
// NOTE: This does not work for commonized libraries, they may contain the ',' naturally
val items = itemsLine.removeSurrounding("[", "]").split(", ").toSet() val items = itemsLine.removeSurrounding("[", "]").split(", ").toSet()
checkAnyItemsContains.forEach { pattern -> assertTrue { items.any { pattern in it } } } checkAnyItemsContains.forEach { pattern -> assertTrue { items.any { pattern in it } } }
checkNoItemContains.forEach { pattern -> assertFalse { items.any { pattern in it } } } checkNoItemContains.forEach { pattern -> assertFalse { items.any { pattern in it } } }
@@ -9,11 +9,11 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmFragmentGranularMetadataResolverFactory import org.jetbrains.kotlin.gradle.plugin.mpp.kotlinTransformedMetadataLibraryDirectoryForIde
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmFragment import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmFragmentGranularMetadataResolverFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmModule.Companion.moduleName import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmModule.Companion.moduleName
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toKpmModuleDependency import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toKpmModuleDependency
import org.jetbrains.kotlin.gradle.utils.withTemporaryDirectory
internal class IdeaKpmMetadataBinaryDependencyResolver( internal class IdeaKpmMetadataBinaryDependencyResolver(
private val fragmentGranularMetadataResolverFactory: GradleKpmFragmentGranularMetadataResolverFactory private val fragmentGranularMetadataResolverFactory: GradleKpmFragmentGranularMetadataResolverFactory
@@ -34,39 +34,28 @@ internal class IdeaKpmMetadataBinaryDependencyResolver(
is ProjectMetadataProvider -> return emptySet() is ProjectMetadataProvider -> return emptySet()
is JarMetadataProvider -> resolution.metadataProvider is JarMetadataProvider -> resolution.metadataProvider
} }
return resolution.allVisibleSourceSetNames.mapNotNull { visibleFragmentName ->
val binaryFile = withTemporaryDirectory("metadataBinaryDependencyResolver") { temporaryDirectory -> return metadataProvider.read { artifactHandle ->
val sourceBinaryFile = metadataProvider.getSourceSetCompiledMetadata( resolution.allVisibleSourceSetNames.mapNotNull { visibleFragmentName ->
sourceSetName = visibleFragmentName, val sourceSet = artifactHandle.findSourceSet(visibleFragmentName) ?: return@mapNotNull null
outputDirectory = temporaryDirectory, val metadataLibrary = sourceSet.metadataLibrary ?: return@mapNotNull null
materializeFile = true
val libraryFile = fragment.project.kotlinTransformedMetadataLibraryDirectoryForIde
.resolve(metadataLibrary.relativeFile)
.apply { if (!isFile) metadataLibrary.copyTo(this) }
IdeaKpmResolvedBinaryDependencyImpl(
binaryType = IdeaKpmDependency.CLASSPATH_BINARY_TYPE,
binaryFile = libraryFile,
coordinates = IdeaKpmBinaryCoordinatesImpl(
group = gradleModuleIdentifier.group,
module = gradleModuleIdentifier.module,
version = gradleModuleIdentifier.version,
kotlinModuleName = kotlinModuleIdentifier.moduleName,
kotlinFragmentName = visibleFragmentName
)
) )
if (!sourceBinaryFile.isFile)
return@mapNotNull null
fragment.project.rootDir
.resolve(".gradle").resolve("kotlin").resolve("transformedKotlinMetadata")
.resolve(gradleModuleIdentifier.group)
.resolve(gradleModuleIdentifier.module)
.resolve(gradleModuleIdentifier.version)
.resolve(kotlinModuleIdentifier.moduleName)
.resolve(visibleFragmentName)
.resolve(sourceBinaryFile.name)
.apply { if (!isFile) sourceBinaryFile.copyTo(this) }
} }
IdeaKpmResolvedBinaryDependencyImpl(
binaryType = IdeaKpmDependency.CLASSPATH_BINARY_TYPE,
binaryFile = binaryFile,
coordinates = IdeaKpmBinaryCoordinatesImpl(
group = gradleModuleIdentifier.group,
module = gradleModuleIdentifier.module,
version = gradleModuleIdentifier.version,
kotlinModuleName = kotlinModuleIdentifier.moduleName,
kotlinFragmentName = visibleFragmentName
)
)
} }
} }
} }
@@ -1,97 +0,0 @@
/*
* 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.
*/
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
internal fun CompositeMetadataJar(
moduleIdentifier: String,
projectStructureMetadata: KotlinProjectStructureMetadata,
primaryArtifactFile: File,
hostSpecificArtifactsBySourceSet: Map<String, File>,
): CompositeMetadataJar = CompositeMetadataJarImpl(
moduleIdentifier, projectStructureMetadata, primaryArtifactFile, hostSpecificArtifactsBySourceSet
)
internal interface CompositeMetadataJar {
fun getSourceSetCompiledMetadata(
sourceSetName: String, outputDirectory: File, materializeFile: Boolean
): File
fun getSourceSetCInteropMetadata(
sourceSetName: String, outputDirectory: File, materializeFiles: Boolean
): Set<File>
}
private class CompositeMetadataJarImpl(
private val moduleIdentifier: String,
private val projectStructureMetadata: KotlinProjectStructureMetadata,
private val primaryArtifactFile: File,
private val hostSpecificArtifactsBySourceSet: Map<String, File>,
) : CompositeMetadataJar {
override fun getSourceSetCompiledMetadata(
sourceSetName: String, outputDirectory: File, materializeFile: Boolean
): File {
val artifactFile = getArtifactFile(sourceSetName)
val moduleOutputDirectory = outputDirectory.resolve(moduleIdentifier).also { it.mkdirs() }
val extension = projectStructureMetadata.sourceSetBinaryLayout[sourceSetName]?.archiveExtension
?: SourceSetMetadataLayout.METADATA.archiveExtension
val metadataOutputFile = moduleOutputDirectory.resolve("$moduleIdentifier-$sourceSetName.$extension")
/** In composite builds, we don't really need tro process the file in IDE import, so ignore it if it's missing */
// refactor: allow only included builds to provide no artifacts, and allow this only in IDE import
if (!artifactFile.isFile) {
return metadataOutputFile
}
if (!materializeFile) {
return metadataOutputFile
}
if (metadataOutputFile.exists()) {
metadataOutputFile.delete()
}
copyZipFilePartially(artifactFile, metadataOutputFile, "$sourceSetName/")
return metadataOutputFile
}
override fun getSourceSetCInteropMetadata(
sourceSetName: String, outputDirectory: File, materializeFiles: Boolean
): Set<File> {
val artifactFile = getArtifactFile(sourceSetName)
val moduleOutputDirectory = outputDirectory.resolve(moduleIdentifier).also(File::mkdirs)
ZipFile(getArtifactFile(sourceSetName)).use { artifactZipFile ->
val cinteropMetadataDirectory = projectStructureMetadata.sourceSetCInteropMetadataDirectory[sourceSetName] ?: return emptySet()
val cinteropMetadataDirectoryPath = ensureValidZipDirectoryPath(cinteropMetadataDirectory)
val cinteropEntries = artifactZipFile.listDescendants(cinteropMetadataDirectoryPath)
val cinteropNames = cinteropEntries.map { entry ->
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, "$cinteropMetadataDirectoryPath$cinteropName/")
}
}
return cinteropsByOutputFile.keys
}
}
private fun getArtifactFile(sourceSetName: String): File =
hostSpecificArtifactsBySourceSet[sourceSetName] ?: primaryArtifactFile
}
@@ -1,47 +0,0 @@
/*
* 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.
*/
package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider
import java.io.File
/**
* @param outputDirectoryWhenMaterialised: Root output directory to put files if [materializeFilesIfNecessary] is set
* and the artifact is indeed a [CompositeMetadataJar]
*
* @param materializeFilesIfNecessary: If the artifact is a [CompositeMetadataJar] then this flag will tell if
* compiled source set metadata should be extracted from this jar file into the given [outputDirectoryWhenMaterialised]
*/
internal fun ChooseVisibleSourceSets.getAllCompiledSourceSetMetadata(
project: Project,
outputDirectoryWhenMaterialised: File,
materializeFilesIfNecessary: Boolean
): ConfigurableFileCollection = project.files(
visibleSourceSetNamesExcludingDependsOn.map { visibleSourceSetName ->
metadataProvider.getSourceSetCompiledMetadata(
project, visibleSourceSetName, outputDirectoryWhenMaterialised, materializeFilesIfNecessary
)
}
)
internal fun ChooseVisibleSourceSets.MetadataProvider.getSourceSetCompiledMetadata(
project: Project,
sourceSetName: String,
outputDirectoryWhenMaterialised: File,
materializeFilesIfNecessary: Boolean
): FileCollection = when (this) {
is ProjectMetadataProvider -> getSourceSetCompiledMetadata(sourceSetName)
is JarMetadataProvider -> project.files(
getSourceSetCompiledMetadata(
sourceSetName, outputDirectoryWhenMaterialised, materializeFilesIfNecessary
)
)
}
@@ -16,7 +16,7 @@ import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.Companion.asMetadataProvider import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
@@ -80,8 +80,8 @@ internal sealed class MetadataDependencyResolution(
) : MetadataDependencyResolution(dependency, projectDependency) { ) : MetadataDependencyResolution(dependency, projectDependency) {
internal sealed class MetadataProvider { internal sealed class MetadataProvider {
class JarMetadataProvider(private val compositeMetadataArtifact: CompositeMetadataJar) : class JarMetadataProvider(private val compositeMetadataArtifact: CompositeMetadataArtifact) :
MetadataProvider(), CompositeMetadataJar by compositeMetadataArtifact MetadataProvider(), CompositeMetadataArtifact by compositeMetadataArtifact
abstract class ProjectMetadataProvider : MetadataProvider() { abstract class ProjectMetadataProvider : MetadataProvider() {
enum class MetadataConsumer { Ide, Cli } enum class MetadataConsumer { Ide, Cli }
@@ -89,10 +89,6 @@ internal sealed class MetadataDependencyResolution(
abstract fun getSourceSetCompiledMetadata(sourceSetName: String): FileCollection abstract fun getSourceSetCompiledMetadata(sourceSetName: String): FileCollection
abstract fun getSourceSetCInteropMetadata(sourceSetName: String, consumer: MetadataConsumer): FileCollection abstract fun getSourceSetCInteropMetadata(sourceSetName: String, consumer: MetadataConsumer): FileCollection
} }
companion object {
fun CompositeMetadataJar.asMetadataProvider() = JarMetadataProvider(this)
}
} }
override fun toString(): String = override fun toString(): String =
@@ -178,6 +174,7 @@ internal class GranularMetadataTransformation(
val transitiveDependenciesToVisit = when (dependencyResult) { val transitiveDependenciesToVisit = when (dependencyResult) {
is MetadataDependencyResolution.KeepOriginalDependency -> is MetadataDependencyResolution.KeepOriginalDependency ->
resolvedDependency.dependencies.filterIsInstance<ResolvedDependencyResult>() resolvedDependency.dependencies.filterIsInstance<ResolvedDependencyResult>()
is MetadataDependencyResolution.ChooseVisibleSourceSets -> dependencyResult.visibleTransitiveDependencies is MetadataDependencyResolution.ChooseVisibleSourceSets -> dependencyResult.visibleTransitiveDependencies
is MetadataDependencyResolution.Exclude.PublishedPlatformSourceSetDependency -> dependencyResult.visibleTransitiveDependencies is MetadataDependencyResolution.Exclude.PublishedPlatformSourceSetDependency -> dependencyResult.visibleTransitiveDependencies
is MetadataDependencyResolution.Exclude.Unrequested -> error("a visited dependency is erroneously considered unrequested") is MetadataDependencyResolution.Exclude.Unrequested -> error("a visited dependency is erroneously considered unrequested")
@@ -277,12 +274,15 @@ internal class GranularMetadataTransformation(
moduleIdentifier = mppDependencyMetadataExtractor.moduleIdentifier moduleIdentifier = mppDependencyMetadataExtractor.moduleIdentifier
) )
is JarMppDependencyProjectStructureMetadataExtractor -> CompositeMetadataJar( is JarMppDependencyProjectStructureMetadataExtractor -> JarMetadataProvider(
moduleIdentifier = ModuleIds.fromComponent(project, module).toString(), CompositeMetadataArtifactImpl(
projectStructureMetadata = projectStructureMetadata, moduleDependencyIdentifier = ModuleIds.fromComponent(project, module),
primaryArtifactFile = mppDependencyMetadataExtractor.primaryArtifactFile, moduleDependencyVersion = module.moduleVersion?.version ?: "unspecified",
hostSpecificArtifactsBySourceSet = sourceSetVisibility.hostSpecificMetadataArtifactBySourceSet, kotlinProjectStructureMetadata = projectStructureMetadata,
).asMetadataProvider() primaryArtifactFile = mppDependencyMetadataExtractor.primaryArtifactFile,
hostSpecificArtifactFilesBySourceSetName = sourceSetVisibility.hostSpecificMetadataArtifactBySourceSet
)
)
} }
return MetadataDependencyResolution.ChooseVisibleSourceSets( return MetadataDependencyResolution.ChooseVisibleSourceSets(
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2022 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.
*/
package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Project
import java.io.File
private const val kotlinTransformedMetadataLibraries = "kotlinTransformedMetadataLibraries"
private const val kotlinTransformedCInteropMetadataLibraries = "kotlinTransformedCInteropMetadataLibraries"
internal fun Project.kotlinTransformedMetadataLibraryDirectoryForBuild(sourceSetName: String): File =
buildDir.resolve(kotlinTransformedMetadataLibraries).resolve(sourceSetName)
internal val Project.kotlinTransformedMetadataLibraryDirectoryForIde: File
get() = rootDir.resolve(".gradle").resolve("kotlin").resolve(kotlinTransformedMetadataLibraries)
internal fun Project.kotlinTransformedCInteropMetadataLibraryDirectoryForBuild(sourceSetName: String): File =
buildDir.resolve(kotlinTransformedCInteropMetadataLibraries).resolve(sourceSetName)
internal val Project.kotlinTransformedCInteropMetadataLibraryDirectoryForIde: File
get() = rootDir.resolve(".gradle").resolve("kotlin").resolve(kotlinTransformedCInteropMetadataLibraries)
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.* import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.*
import org.jetbrains.kotlin.gradle.plugin.sources.internal import org.jetbrains.kotlin.gradle.plugin.sources.internal
import org.jetbrains.kotlin.gradle.plugin.sources.withDependsOnClosure
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetConfigurator import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetConfigurator
import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider
@@ -37,7 +36,7 @@ open class TransformKotlinGranularMetadata
@get:OutputDirectory @get:OutputDirectory
val outputsDir: File by project.provider { val outputsDir: File by project.provider {
project.buildDir.resolve("kotlinSourceSetMetadata/${kotlinSourceSet.name}") project.kotlinTransformedMetadataLibraryDirectoryForBuild(kotlinSourceSet.name)
} }
@Suppress("unused") // Gradle input @Suppress("unused") // Gradle input
@@ -98,14 +97,20 @@ open class TransformKotlinGranularMetadata
transformation.metadataDependencyResolutions transformation.metadataDependencyResolutions
} }
/**
* Contains "actually transformed" metadata libraries (extracted from any [CompositeMetadataArtifact]) as well as
* [FileCollection]s pointing to project dependency klibs.
*
* Project dependencies are still required to be listed here (despite not being produced by this task), since
* this [filesByResolution] will be used to build the compile-classpath for metadata compilations.
*/
@get:Internal @get:Internal
internal val filesByResolution: Map<out MetadataDependencyResolution, FileCollection> internal val filesByResolution: Map<out MetadataDependencyResolution, FileCollection>
get() = metadataDependencyResolutions get() = metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>() .filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.associateWith { chooseVisibleSourceSets -> .associateWith { chooseVisibleSourceSets ->
chooseVisibleSourceSets.getAllCompiledSourceSetMetadata( val files = project.transformMetadataLibrariesForBuild(chooseVisibleSourceSets, outputsDir, materializeFiles = false)
project, outputDirectoryWhenMaterialised = outputsDir, materializeFilesIfNecessary = false project.files(files).builtBy(this)
).builtBy(this)
} }
@TaskAction @TaskAction
@@ -118,9 +123,7 @@ open class TransformKotlinGranularMetadata
metadataDependencyResolutions metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>() .filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.forEach { chooseVisibleSourceSets -> .forEach { chooseVisibleSourceSets ->
chooseVisibleSourceSets.getAllCompiledSourceSetMetadata( project.transformMetadataLibrariesForBuild(chooseVisibleSourceSets, outputsDir, true)
project, outputDirectoryWhenMaterialised = outputsDir, materializeFilesIfNecessary = true
)
} }
} }
} }
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.Companion.asMetadataProvider import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider
import org.jetbrains.kotlin.project.model.* import org.jetbrains.kotlin.project.model.*
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
import java.io.File import java.io.File
@@ -79,6 +79,7 @@ internal class GradleKpmFragmentGranularMetadataResolver(
is GradleKpmExternalPlainModule -> { is GradleKpmExternalPlainModule -> {
MetadataDependencyResolution.KeepOriginalDependency(resolvedComponentResult, isResolvedAsProject) MetadataDependencyResolution.KeepOriginalDependency(resolvedComponentResult, isResolvedAsProject)
} }
else -> run { else -> run {
val metadataSourceComponent = dependencyNode.run { metadataSourceComponent ?: selectedComponent } val metadataSourceComponent = dependencyNode.run { metadataSourceComponent ?: selectedComponent }
@@ -108,14 +109,17 @@ internal class GradleKpmFragmentGranularMetadataResolver(
moduleIdentifier = projectStructureMetadataExtractor.moduleIdentifier moduleIdentifier = projectStructureMetadataExtractor.moduleIdentifier
) )
is JarMppDependencyProjectStructureMetadataExtractor -> CompositeMetadataJar( is JarMppDependencyProjectStructureMetadataExtractor -> JarMetadataProvider(
moduleIdentifier = ModuleIds.fromComponent(project, metadataSourceComponent).toString(), CompositeMetadataArtifactImpl(
projectStructureMetadata = projectStructureMetadata, moduleDependencyIdentifier = ModuleIds.fromComponent(project, metadataSourceComponent),
primaryArtifactFile = projectStructureMetadataExtractor.primaryArtifactFile, moduleDependencyVersion = metadataSourceComponent.moduleVersion?.version ?: "unspecified",
hostSpecificArtifactsBySourceSet = if ( kotlinProjectStructureMetadata = projectStructureMetadata,
dependencyModule is GradleKpmExternalImportedModule && chosenFragments != null primaryArtifactFile = projectStructureMetadataExtractor.primaryArtifactFile,
) resolveHostSpecificMetadataArtifacts(dependencyModule, chosenFragments) else emptyMap(), hostSpecificArtifactFilesBySourceSetName = if (
).asMetadataProvider() dependencyModule is GradleKpmExternalImportedModule && chosenFragments != null
) resolveHostSpecificMetadataArtifacts(dependencyModule, chosenFragments) else emptyMap(),
)
)
} }
MetadataDependencyResolution.ChooseVisibleSourceSets( MetadataDependencyResolution.ChooseVisibleSourceSets(
@@ -10,8 +10,8 @@ import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
import org.jetbrains.kotlin.gradle.plugin.mpp.getAllCompiledSourceSetMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.disambiguateName import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.disambiguateName
import org.jetbrains.kotlin.gradle.plugin.mpp.transformMetadataLibrariesForBuild
import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider
import org.jetbrains.kotlin.gradle.utils.getValue import org.jetbrains.kotlin.gradle.utils.getValue
import java.io.File import java.io.File
@@ -72,9 +72,8 @@ internal open class TransformKotlinGranularMetadataForFragment
get() = metadataDependencyResolutions get() = metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>() .filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.associateWith { chooseVisibleSourceSets -> .associateWith { chooseVisibleSourceSets ->
chooseVisibleSourceSets.getAllCompiledSourceSetMetadata( project.files(project.transformMetadataLibrariesForBuild(chooseVisibleSourceSets, outputsDir, materializeFiles = false))
project, outputsDir, materializeFilesIfNecessary = false .builtBy(this)
).builtBy(this)
} }
@TaskAction @TaskAction
@@ -87,7 +86,7 @@ internal open class TransformKotlinGranularMetadataForFragment
metadataDependencyResolutions metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>() .filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.forEach { chooseVisibleSourceSets -> .forEach { chooseVisibleSourceSets ->
chooseVisibleSourceSets.getAllCompiledSourceSetMetadata(project, outputsDir, materializeFilesIfNecessary = true) project.transformMetadataLibrariesForBuild(chooseVisibleSourceSets, outputsDir, materializeFiles = true)
} }
} }
} }
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2022 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.
*/
package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider
import java.io.File
/**
* Returns a map from 'visibleSourceSetName' to the transformed metadata libraries.
* The map is necessary to support [MetadataDependencyTransformation]'s shape, which
* is used in import and therefore hard to change.
*
* When this function will also support project dependencies and just return the compiled output files.
*/
internal fun Project.transformMetadataLibrariesForIde(
resolution: MetadataDependencyResolution.ChooseVisibleSourceSets
): Map<String /* visibleSourceSetName */, Iterable<File>> {
return when (val metadataProvider = resolution.metadataProvider) {
is ProjectMetadataProvider -> resolution.visibleSourceSetNamesExcludingDependsOn.associateWith { visibleSourceSetName ->
metadataProvider.getSourceSetCompiledMetadata(visibleSourceSetName)
}
is JarMetadataProvider -> transformMetadataLibrariesForIde(
kotlinTransformedMetadataLibraryDirectoryForIde, resolution, metadataProvider
)
}
}
/**
* Will transform the [CompositeMetadataArtifact] extracting the visible source sets specified in the [resolution]
* @param materializeFiles: If true, the klib files will actually be created and extracted
*
* In case the [resolution] points to a project dependency, then the output file collections will be returned.
*/
internal fun Project.transformMetadataLibrariesForBuild(
resolution: MetadataDependencyResolution.ChooseVisibleSourceSets, outputDirectory: File, materializeFiles: Boolean
): Iterable<File> {
return when (resolution.metadataProvider) {
is ProjectMetadataProvider -> project.files(
resolution.visibleSourceSetNamesExcludingDependsOn.map { visibleSourceSetName ->
resolution.metadataProvider.getSourceSetCompiledMetadata(visibleSourceSetName)
}
)
is JarMetadataProvider -> transformMetadataLibrariesForBuild(
resolution, outputDirectory, materializeFiles, resolution.metadataProvider
)
}
}
/* Implementations for transforming the Composite Metadata Artifact */
private fun transformMetadataLibrariesForIde(
baseOutputDirectory: File,
resolution: MetadataDependencyResolution.ChooseVisibleSourceSets,
compositeMetadataArtifact: CompositeMetadataArtifact
): Map<String /* visibleSourceSetName */, Iterable<File>> {
return compositeMetadataArtifact.read { artifactHandle ->
resolution.visibleSourceSetNamesExcludingDependsOn.mapNotNull { visibleSourceSetName ->
val sourceSet = artifactHandle.findSourceSet(visibleSourceSetName) ?: return@mapNotNull null
val sourceSetMetadataLibrary = sourceSet.metadataLibrary ?: return@mapNotNull null
val metadataLibraryOutputFile = baseOutputDirectory.resolve(sourceSetMetadataLibrary.relativeFile)
metadataLibraryOutputFile.parentFile.mkdirs()
if (!metadataLibraryOutputFile.exists()) {
sourceSetMetadataLibrary.copyTo(metadataLibraryOutputFile)
if (!metadataLibraryOutputFile.exists()) return@mapNotNull null
}
visibleSourceSetName to listOf(metadataLibraryOutputFile)
}.toMap()
}
}
private fun transformMetadataLibrariesForBuild(
resolution: MetadataDependencyResolution.ChooseVisibleSourceSets,
outputDirectory: File,
materializeFiles: Boolean,
compositeMetadataArtifact: CompositeMetadataArtifact
): Set<File> {
return compositeMetadataArtifact.read { artifactHandle ->
resolution.visibleSourceSetNamesExcludingDependsOn.mapNotNull { visibleSourceSetName ->
val sourceSet = artifactHandle.findSourceSet(visibleSourceSetName) ?: return@mapNotNull null
val metadataLibrary = sourceSet.metadataLibrary ?: return@mapNotNull null
val metadataLibraryFile = outputDirectory.resolve(metadataLibrary.relativeFile)
if (materializeFiles) {
metadataLibraryFile.parentFile?.mkdirs()
metadataLibrary.copyTo(metadataLibraryFile)
}
metadataLibraryFile
}
}.toSet()
}
@@ -8,7 +8,6 @@
package org.jetbrains.kotlin.gradle.plugin.sources package org.jetbrains.kotlin.gradle.plugin.sources
import org.gradle.api.Action import org.gradle.api.Action
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet import org.gradle.api.file.SourceDirectorySet
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
@@ -19,7 +18,6 @@ import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.tooling.core.closure import org.jetbrains.kotlin.tooling.core.closure
import org.jetbrains.kotlin.tooling.core.withClosure
import java.io.File import java.io.File
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@@ -156,15 +154,6 @@ abstract class DefaultKotlinSourceSet @Inject constructor(
?.associateBy { ModuleIds.fromComponent(project, it.dependency) } ?.associateBy { ModuleIds.fromComponent(project, it.dependency) }
?: emptyMap() ?: emptyMap()
val baseDir = SourceSetMetadataStorageForIde.sourceSetStorageWithScope(project, this@DefaultKotlinSourceSet.name, scope)
if (metadataDependencyResolutionByModule.values.any { it is MetadataDependencyResolution.ChooseVisibleSourceSets }) {
if (baseDir.isDirectory) {
baseDir.deleteRecursively()
}
baseDir.mkdirs()
}
return metadataDependencyResolutionByModule.mapNotNull { (groupAndName, resolution) -> return metadataDependencyResolutionByModule.mapNotNull { (groupAndName, resolution) ->
val (group, name) = groupAndName val (group, name) = groupAndName
val projectPath = resolution.projectDependency?.path val projectPath = resolution.projectDependency?.path
@@ -178,20 +167,11 @@ abstract class DefaultKotlinSourceSet @Inject constructor(
MetadataDependencyTransformation(group, name, projectPath, null, emptySet(), emptyMap()) MetadataDependencyTransformation(group, name, projectPath, null, emptySet(), emptyMap())
is MetadataDependencyResolution.ChooseVisibleSourceSets -> { is MetadataDependencyResolution.ChooseVisibleSourceSets -> {
val filesBySourceSet = resolution.visibleSourceSetNamesExcludingDependsOn.associateWith { visibleSourceSetName ->
resolution.metadataProvider.getSourceSetCompiledMetadata(
project,
sourceSetName = visibleSourceSetName,
outputDirectoryWhenMaterialised = baseDir,
materializeFilesIfNecessary = true
)
}.filter { (_, files) -> files.any(File::exists) }
MetadataDependencyTransformation( MetadataDependencyTransformation(
group, name, projectPath, group, name, projectPath,
resolution.projectStructureMetadata, resolution.projectStructureMetadata,
resolution.allVisibleSourceSetNames, resolution.allVisibleSourceSetNames,
filesBySourceSet project.transformMetadataLibrariesForIde(resolution)
) )
} }
} }
@@ -1,34 +0,0 @@
/*
* Copyright 2010-2020 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.
*/
package org.jetbrains.kotlin.gradle.plugin.sources
import org.gradle.api.Project
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.api.services.BuildServiceSpec
import org.gradle.tooling.events.FinishEvent
import org.gradle.tooling.events.OperationCompletionListener
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import java.io.File
object SourceSetMetadataStorageForIde {
private fun getStorageRoot(project: Project): File = project.rootDir.resolve(".gradle/kotlin/sourceSetMetadata")
private fun projectStorage(project: Project): File {
val projectPathSegments = generateSequence(project) { it.parent }.map { it.name }
return getStorageRoot(project).resolve(
// Escape dots in project names to avoid ambiguous paths.
projectPathSegments.joinToString(".") { it.replace(".", "_.") }
)
}
fun sourceSetStorage(project: Project, sourceSetName: String) = projectStorage(project).resolve(sourceSetName)
internal fun sourceSetStorageWithScope(project: Project, sourceSetName: String, scope: KotlinDependencyScope) =
sourceSetStorage(project, sourceSetName).resolve(scope.scopeName)
}
@@ -15,14 +15,12 @@ import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api
import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn
import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.JarMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toKpmModuleIdentifiers import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toKpmModuleIdentifiers
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.SourceSetMetadataStorageForIde
import org.jetbrains.kotlin.gradle.tasks.dependsOn import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.withType import org.jetbrains.kotlin.gradle.tasks.withType
@@ -33,6 +31,7 @@ import org.jetbrains.kotlin.gradle.utils.outputFilesProvider
import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION
import org.jetbrains.kotlin.project.model.KpmModuleIdentifier import org.jetbrains.kotlin.project.model.KpmModuleIdentifier
import java.io.File import java.io.File
import java.io.Serializable
import java.util.concurrent.Callable import java.util.concurrent.Callable
import javax.inject.Inject import javax.inject.Inject
@@ -46,7 +45,11 @@ internal fun Project.locateOrRegisterCInteropMetadataDependencyTransformationTas
args = listOf( args = listOf(
sourceSet, sourceSet,
/* outputDirectory = */ /* outputDirectory = */
project.buildDir.resolve("kotlinSourceSetMetadata").resolve(sourceSet.name + "-cinterop") project.kotlinTransformedCInteropMetadataLibraryDirectoryForBuild(sourceSet.name),
/* outputLibraryFilesDiscovery = */
CInteropMetadataDependencyTransformationTask.OutputLibraryFilesDiscovery.ScanOutputDirectory,
/* cleaning = */
CInteropMetadataDependencyTransformationTask.Cleaning.DeleteOutputDirectory
), ),
configureTask = { configureTaskOrder(); onlyIfSourceSetIsSharedNative() } configureTask = { configureTaskOrder(); onlyIfSourceSetIsSharedNative() }
) )
@@ -70,7 +73,11 @@ internal fun Project.locateOrRegisterCInteropMetadataDependencyTransformationTas
args = listOf( args = listOf(
sourceSet, sourceSet,
/* outputDirectory = */ /* outputDirectory = */
SourceSetMetadataStorageForIde.sourceSetStorage(project, sourceSet.name).resolve("cinterop") project.kotlinTransformedCInteropMetadataLibraryDirectoryForIde,
/* outputLibraryFilesDiscovery = */
CInteropMetadataDependencyTransformationTask.OutputLibraryFilesDiscovery.Precise,
/* cleaning = */
CInteropMetadataDependencyTransformationTask.Cleaning.None
), ),
configureTask = { configureTaskOrder(); onlyIfSourceSetIsSharedNative() } configureTask = { configureTaskOrder(); onlyIfSourceSetIsSharedNative() }
) )
@@ -99,13 +106,62 @@ private fun CInteropMetadataDependencyTransformationTask.onlyIfSourceSetIsShared
internal open class CInteropMetadataDependencyTransformationTask @Inject constructor( internal open class CInteropMetadataDependencyTransformationTask @Inject constructor(
@Transient @get:Internal val sourceSet: DefaultKotlinSourceSet, @Transient @get:Internal val sourceSet: DefaultKotlinSourceSet,
@get:OutputDirectory val outputDirectory: File @get:OutputDirectory val outputDirectory: File,
@get:Internal val outputLibraryFilesDiscovery: OutputLibraryFilesDiscovery,
@get:Internal val cleaning: Cleaning
) : DefaultTask() { ) : DefaultTask() {
init { init {
notCompatibleWithConfigurationCache("Task $name does not support Gradle Configuration Cache. Check KT-49933 for more info") notCompatibleWithConfigurationCache("Task $name does not support Gradle Configuration Cache. Check KT-49933 for more info")
} }
sealed class OutputLibraryFilesDiscovery : Serializable {
abstract fun resolveOutputLibraryFiles(outputDirectory: File, resolutions: Iterable<ChooseVisibleSourceSets>): Set<File>
/**
* Can be used when the used output directory is only used by this task.
* In this case, all libraries in the given outputDirectory will be library files produced by this task, so just
* scanning its content will be enough.
*/
object ScanOutputDirectory : OutputLibraryFilesDiscovery() {
override fun resolveOutputLibraryFiles(outputDirectory: File, resolutions: Iterable<ChooseVisibleSourceSets>): Set<File> {
return outputDirectory.walkTopDown().maxDepth(2).filter { it.isFile && it.extension == KLIB_FILE_EXTENSION }.toSet()
}
}
/**
* Will actually read the [CompositeMetadataArtifact] and infer the exact file locations.
* This can be used if the output directory might be shared with other tasks.
*/
object Precise : OutputLibraryFilesDiscovery() {
override fun resolveOutputLibraryFiles(outputDirectory: File, resolutions: Iterable<ChooseVisibleSourceSets>): Set<File> {
return resolutions.flatMap { chooseVisibleSourceSets ->
if (chooseVisibleSourceSets.metadataProvider !is JarMetadataProvider) return@flatMap emptyList()
chooseVisibleSourceSets.metadataProvider.read { artifactHandle ->
chooseVisibleSourceSets.visibleSourceSetsProvidingCInterops
.mapNotNull { visibleSourceSetName -> artifactHandle.findSourceSet(visibleSourceSetName) }
.flatMap { sourceSet -> sourceSet.cinteropMetadataLibraries }
.map { cInteropMetadataLibrary -> outputDirectory.resolve(cInteropMetadataLibrary.relativeFile) }
}
}.toSet()
}
}
}
sealed class Cleaning : Serializable {
abstract fun cleanOutputDirectory(outputDirectory: File)
object DeleteOutputDirectory : Cleaning() {
override fun cleanOutputDirectory(outputDirectory: File) {
if (outputDirectory.isDirectory) outputDirectory.deleteRecursively()
}
}
object None : Cleaning() {
override fun cleanOutputDirectory(outputDirectory: File) = Unit
}
}
@Suppress("unused") @Suppress("unused")
class ChooseVisibleSourceSetProjection( class ChooseVisibleSourceSetProjection(
@Input val dependencyModuleIdentifiers: List<KpmModuleIdentifier>, @Input val dependencyModuleIdentifiers: List<KpmModuleIdentifier>,
@@ -137,33 +193,30 @@ internal open class CInteropMetadataDependencyTransformationTask @Inject constru
get() = chooseVisibleSourceSets.map(::ChooseVisibleSourceSetProjection).toSet() get() = chooseVisibleSourceSets.map(::ChooseVisibleSourceSetProjection).toSet()
@get:Internal @get:Internal
val outputLibraryFiles = outputFilesProvider { val outputLibraryFiles = outputFilesProvider(lazy {
outputDirectory.walkTopDown().maxDepth(2).filter { it.isFile && it.extension == KLIB_FILE_EXTENSION }.toList() outputLibraryFilesDiscovery.resolveOutputLibraryFiles(outputDirectory, chooseVisibleSourceSets)
} })
@TaskAction @TaskAction
protected fun transformDependencies() { protected fun transformDependencies() {
if (outputDirectory.isDirectory) { cleaning.cleanOutputDirectory(outputDirectory)
outputDirectory.deleteRecursively()
}
if (project.getCommonizerTarget(sourceSet) !is SharedCommonizerTarget) return if (project.getCommonizerTarget(sourceSet) !is SharedCommonizerTarget) return
chooseVisibleSourceSets.flatMap(::materializeMetadata) chooseVisibleSourceSets.forEach(::materializeMetadata)
} }
private fun materializeMetadata( private fun materializeMetadata(
chooseVisibleSourceSets: ChooseVisibleSourceSets chooseVisibleSourceSets: ChooseVisibleSourceSets
): Set<File> = when (chooseVisibleSourceSets.metadataProvider) { ): Unit = when (chooseVisibleSourceSets.metadataProvider) {
/* Nothing to transform: We will use original commonizer output in such cases */ /* Nothing to transform: We will use original commonizer output in such cases */
is ProjectMetadataProvider -> emptySet() is ProjectMetadataProvider -> Unit
/* Extract/Materialize all cinterop files from composite jar file */ /* Extract/Materialize all cinterop files from composite jar file */
is JarMetadataProvider -> is JarMetadataProvider -> chooseVisibleSourceSets.metadataProvider.read { artifactHandle ->
chooseVisibleSourceSets.visibleSourceSetsProvidingCInterops.flatMap { visibleSourceSetName -> chooseVisibleSourceSets.visibleSourceSetsProvidingCInterops
chooseVisibleSourceSets.metadataProvider.getSourceSetCInteropMetadata( .mapNotNull { visibleSourceSetName -> artifactHandle.findSourceSet(visibleSourceSetName) }
visibleSourceSetName, outputDirectory, materializeFiles = true .flatMap { sourceSet -> sourceSet.cinteropMetadataLibraries }
) .forEach { cInteropMetadataLibrary -> cInteropMetadataLibrary.copyIntoDirectory(outputDirectory) }
}.toSet() }
} }
private fun Configuration.withoutProjectDependencies(): FileCollection { private fun Configuration.withoutProjectDependencies(): FileCollection {
@@ -116,6 +116,10 @@ internal fun <T : Task> T.outputFilesProvider(provider: T.() -> Any): Configurab
return project.filesProvider(this) { provider() } return project.filesProvider(this) { provider() }
} }
internal fun <T : Task> T.outputFilesProvider(lazy: Lazy<Any>): ConfigurableFileCollection {
return project.filesProvider(this) { lazy.value }
}
internal inline fun <reified T> Project.listProperty(noinline itemsProvider: () -> Iterable<T>): ListProperty<T> = internal inline fun <reified T> Project.listProperty(noinline itemsProvider: () -> Iterable<T>): ListProperty<T> =
objects.listProperty(T::class.java).apply { set(provider(itemsProvider)) } objects.listProperty(T::class.java).apply { set(provider(itemsProvider)) }
@@ -1,271 +0,0 @@
/*
* 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.gradle.kotlin.dsl.support.zipTo
import org.jetbrains.kotlin.gradle.plugin.mpp.CompositeMetadataJar
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.plugin.mpp.ModuleDependencyIdentifier
import org.jetbrains.kotlin.gradle.plugin.mpp.SourceSetMetadataLayout
import org.jetbrains.kotlin.gradle.plugin.mpp.SourceSetMetadataLayout.KLIB
import org.jetbrains.kotlin.gradle.plugin.mpp.SourceSetMetadataLayout.METADATA
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.io.File
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
class CompositeMetadataJarTest {
@get:Rule
val temporaryFolder = TemporaryFolder()
@Test
fun `empty jar - 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 = 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()
)
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 - get cinterop metadata - returns emptySet`() {
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(),
primaryArtifactFile = primaryArtifactFile,
hostSpecificArtifactsBySourceSet = emptyMap()
)
val metadataOutputDirectory = temporaryFolder.newFolder()
val metadataFiles = metadataJar.getSourceSetCInteropMetadata("testSourceSetName", metadataOutputDirectory, true)
assertEquals(
emptySet(), metadataFiles,
"Expected no cinterop metadata files discovered in jar"
)
}
@Test
fun `get metadata`() {
/* Setup Artifact content */
val primaryArtifactContent = temporaryFolder.newFolder()
primaryArtifactContent.resolve("sourceSetA/sourceSetAStub1.txt")
.withParentDirectoriesCreated()
.writeText("Content of sourceSetA stub1")
primaryArtifactContent.resolve("sourceSetA/nested/sourceSetAStub2.txt")
.withParentDirectoriesCreated()
.writeText("Content of sourceSetB stub2")
primaryArtifactContent.resolve("sourceSetB/sourceSetBStub1.txt")
.withParentDirectoriesCreated()
.writeText("Content of sourceSetB stub1")
primaryArtifactContent.resolve("sourceSetB/nested/sourceSetBStub2.txt")
.withParentDirectoriesCreated()
.writeText("Content of sourceSetB stub2")
/* Create metadata jar */
val primaryArtifactFile = temporaryFolder.newFile("metadata.jar")
zipTo(primaryArtifactFile, primaryArtifactContent)
val metadataJar = CompositeMetadataJar(
moduleIdentifier = "test-id",
projectStructureMetadata = createProjectStructureMetadata(
sourceSetBinaryLayout = mapOf("sourceSetA" to KLIB, "sourceSetB" to METADATA)
),
primaryArtifactFile = primaryArtifactFile,
hostSpecificArtifactsBySourceSet = emptyMap()
)
/* Extract and assert sourceSetA */
val metadataOutputDirectory = temporaryFolder.newFolder()
val sourceSetAMetadataFile = metadataJar.getSourceSetCompiledMetadata("sourceSetA", metadataOutputDirectory, true)
assertTrue(sourceSetAMetadataFile.isFile, "Expected sourceSetAMetadataFile.isFile")
assertEquals(
KLIB.archiveExtension, sourceSetAMetadataFile.extension,
"Expected correct archiveExtension for extracted sourceSetA"
)
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetA"), sourceSetAMetadataFile,
"Expected correct content of extracted 'sourceSetA'"
)
/* Extract and assert sourceSetB */
val sourceSetBMetadataFile = metadataJar.getSourceSetCompiledMetadata("sourceSetB", metadataOutputDirectory, true)
assertTrue(sourceSetAMetadataFile.isFile, "Expected sourceSetBMetadataFile.isFile")
assertEquals(
METADATA.archiveExtension, sourceSetBMetadataFile.extension,
"Expected correct archiveExtension for extracted sourceSetB"
)
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetB"), sourceSetBMetadataFile,
"Expected correct content of extracted 'sourceSetA'"
)
}
@Test
fun `get cinterop metadata`() {
/* Setup Artifact content */
val primaryArtifactContent = temporaryFolder.newFolder()
primaryArtifactContent.resolve("sourceSetA-cinterop/interopA0/stub0")
.withParentDirectoriesCreated()
.writeText("stub0 content")
primaryArtifactContent.resolve("sourceSetA-cinterop/interopA0/nested/stub1")
.withParentDirectoriesCreated()
.writeText("stub1 content")
primaryArtifactContent.resolve("sourceSetA-cinterop/interopA1/stub2")
.withParentDirectoriesCreated()
.writeText("stub2 content")
primaryArtifactContent.resolve("nested/sourceSetB/interops/interopB0/stub3")
.withParentDirectoriesCreated()
.writeText("stub3 content")
/* Create metadata jar */
val primaryArtifactFile = temporaryFolder.newFile("metadata.jar")
zipTo(primaryArtifactFile, primaryArtifactContent)
val metadataJar = CompositeMetadataJar(
moduleIdentifier = "test-id",
projectStructureMetadata = createProjectStructureMetadata(
sourceSetCInteropMetadataDirectory = mapOf(
"sourceSetA" to "sourceSetA-cinterop",
"sourceSetB" to "nested/sourceSetB/interops/"
)
),
primaryArtifactFile = primaryArtifactFile,
hostSpecificArtifactsBySourceSet = emptyMap()
)
/* Assertions on sourceSetA */
run {
val sourceSetAOutputDirectory = temporaryFolder.newFolder()
val sourceSetAInteropMetadataFiles = metadataJar.getSourceSetCInteropMetadata("sourceSetA", sourceSetAOutputDirectory, true)
assertEquals(2, sourceSetAInteropMetadataFiles.size, "Expected 2 cinterops in sourceSetA")
/* Assertions on interopA0 */
run {
val interopA0MetadataFile = sourceSetAInteropMetadataFiles.firstOrNull { it.name == "interopA0.klib" }
?: fail("Failed to find 'interopA0.klib'")
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetA-cinterop/interopA0"), interopA0MetadataFile,
"Expected correct content for extracted 'interopA0'"
)
}
/* Assertions on interopA1 */
run {
val interopA1MetadataFile = sourceSetAInteropMetadataFiles.firstOrNull { it.name == "interopA1.klib" }
?: fail("Failed to find 'interopA1.klib")
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetA-cinterop/interopA1"), interopA1MetadataFile,
"Expected correct content for extracted 'interopA1'"
)
}
}
/* Assertions on sourceSetB */
run {
val sourceSetBOutputDirectory = temporaryFolder.newFolder()
val sourceSetBInteropMetadataFiles = metadataJar.getSourceSetCInteropMetadata("sourceSetB", sourceSetBOutputDirectory, true)
assertEquals(1, sourceSetBInteropMetadataFiles.size, "Expected only one cinterop in sourceSetB")
val interopB0MetadataFile = sourceSetBInteropMetadataFiles.firstOrNull { it.name == "interopB0.klib" }
?: fail("Failed to find 'interopB0.klib'")
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("nested/sourceSetB/interops/interopB0"), interopB0MetadataFile,
"Expected correct content for extracted 'interopB0'"
)
}
}
}
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(),
isPublishedAsRoot: Boolean = true,
): KotlinProjectStructureMetadata {
return KotlinProjectStructureMetadata(
sourceSetNamesByVariantName = sourceSetNamesByVariantName,
sourceSetsDependsOnRelation = sourceSetsDependsOnRelation,
sourceSetBinaryLayout = sourceSetBinaryLayout,
sourceSetCInteropMetadataDirectory = sourceSetCInteropMetadataDirectory,
sourceSetModuleDependencies = sourceSetModuleDependencies,
hostSpecificSourceSets = hostSpecificSourceSets,
isPublishedAsRoot = isPublishedAsRoot
)
}
private fun File.withParentDirectoriesCreated(): File = apply { parentFile.mkdirs() }