diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/CompositeMetadataJar.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/CompositeMetadataJar.kt new file mode 100644 index 00000000000..1894ca7167c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/CompositeMetadataJar.kt @@ -0,0 +1,98 @@ +/* + * 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.listChildren +import java.io.File +import java.util.zip.ZipFile + +internal fun CompositeMetadataJar( + moduleIdentifier: String, + projectStructureMetadata: KotlinProjectStructureMetadata, + primaryArtifactFile: File, + hostSpecificArtifactsBySourceSet: Map, +): 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 +} + +private class CompositeMetadataJarImpl( + private val moduleIdentifier: String, + private val projectStructureMetadata: KotlinProjectStructureMetadata, + private val primaryArtifactFile: File, + private val hostSpecificArtifactsBySourceSet: Map, +) : 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 { + val artifactFile = getArtifactFile(sourceSetName) + val moduleOutputDirectory = outputDirectory.resolve(moduleIdentifier).also(File::mkdirs) + + ZipFile(getArtifactFile(sourceSetName)).use { compoundMetadataArtifactZipFile -> + val cinteropRootDirectory = compoundMetadataArtifactZipFile.entries().asSequence() + .firstOrNull { zipEntry -> zipEntry.name == "$sourceSetName-cinterop/" && zipEntry.isDirectory } + ?: return emptySet() + + val cinterops = compoundMetadataArtifactZipFile.listChildren(cinteropRootDirectory) + + val cinteropsByOutputFile = cinterops.associateBy { cinteropZipEntry -> + moduleOutputDirectory.resolve("${cinteropZipEntry.name.removePrefix(cinteropRootDirectory.name).removeSuffix("/")}.klib") + } + + if (materializeFiles) { + cinteropsByOutputFile.forEach { (cinteropOutputFile, cinteropZipEntry) -> + copyZipFilePartially(artifactFile, cinteropOutputFile, cinteropZipEntry.name) + } + } + + return cinteropsByOutputFile.keys + } + } + + private fun getArtifactFile(sourceSetName: String): File = + hostSpecificArtifactsBySourceSet[sourceSetName] ?: primaryArtifactFile +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GetAllCompiledSourceSetMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GetAllCompiledSourceSetMetadata.kt new file mode 100644 index 00000000000..619ce7b14e2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GetAllCompiledSourceSetMetadata.kt @@ -0,0 +1,47 @@ +/* + * 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 + ) + ) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt index f44c4196e2d..3e3d40e293c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -13,24 +13,14 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.attributes.Attribute import org.gradle.api.file.FileCollection -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.dsl.topLevelExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet -import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension -import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toSingleModuleIdentifier +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.Companion.asMetadataProvider import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope 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_RUNTIME_METADATA_CONFIGURATION_NAME import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies -import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier -import java.io.File -import java.io.InputStream import java.util.* -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream -import javax.xml.parsers.DocumentBuilderFactory internal sealed class MetadataDependencyResolution( @field:Transient // can't be used with Gradle Instant Execution, but fortunately not needed when deserialized @@ -60,23 +50,31 @@ internal sealed class MetadataDependencyResolution( projectDependency: Project? ) : MetadataDependencyResolution(dependency, projectDependency) - abstract class ChooseVisibleSourceSets( + class ChooseVisibleSourceSets internal constructor( dependency: ResolvedComponentResult, projectDependency: Project?, val projectStructureMetadata: KotlinProjectStructureMetadata, val allVisibleSourceSetNames: Set, val visibleSourceSetNamesExcludingDependsOn: Set, - val visibleTransitiveDependencies: Set + val visibleTransitiveDependencies: Set, + internal val metadataProvider: MetadataProvider ) : MetadataDependencyResolution(dependency, projectDependency) { - /** Returns the mapping of source set names to files which should be used as the [dependency] parts representing the source sets. - * If any temporary files need to be created, their paths are built from the [baseDir]. - * If [createFiles] is true, these temporary files are actually re-created during the call, - * otherwise only their paths are returned, while the files might be missing. - */ - fun getMetadataFilesBySourceSet(baseDir: File, createFiles: Boolean): Map = - getExtractableMetadataFiles(baseDir).getMetadataFilesPerSourceSet(createFiles) - abstract fun getExtractableMetadataFiles(baseDir: File): ExtractableMetadataFiles + internal sealed class MetadataProvider { + class JarMetadataProvider(private val compositeMetadataArtifact: CompositeMetadataJar) : + MetadataProvider(), CompositeMetadataJar by compositeMetadataArtifact + + abstract class ProjectMetadataProvider : MetadataProvider() { + enum class MetadataConsumer { Ide, Cli } + + abstract fun getSourceSetCompiledMetadata(sourceSetName: String): FileCollection + abstract fun getSourceSetCInteropMetadata(sourceSetName: String, consumer: MetadataConsumer): FileCollection + } + + companion object { + fun CompositeMetadataJar.asMetadataProvider() = JarMetadataProvider(this) + } + } override fun toString(): String = super.toString() + ", sourceSets = " + allVisibleSourceSetNames.joinToString(", ", "[", "]") { @@ -109,7 +107,7 @@ internal class GranularMetadataTransformation( private val allSourceSetsConfiguration: Configuration = commonMetadataDependenciesConfigurationForScopes(project, sourceSetRequestedScopes) - private val configurationToResolve: Configuration by lazy { + internal val configurationToResolve: Configuration by lazy { resolvableMetadataConfiguration(project, allSourceSetsConfiguration, requestedDependencies) } @@ -204,10 +202,8 @@ internal class GranularMetadataTransformation( parentResolutionsForModule: Iterable, parent: ResolvedComponentResult? ): MetadataDependencyResolution { - val mppDependencyMetadataExtractor = getMetadataExtractor( - project, - module, - configurationToResolve, + val mppDependencyMetadataExtractor = MppDependencyProjectStructureMetadataExtractor.create( + project, module, configurationToResolve, resolveViaAvailableAt = false // we will process the available-at module as a dependency later in the queue ) @@ -225,10 +221,6 @@ internal class GranularMetadataTransformation( resolvedToProject ) - if (mppDependencyMetadataExtractor is JarArtifactMppDependencyMetadataExtractor) { - mppDependencyMetadataExtractor.metadataArtifactBySourceSet.putAll(sourceSetVisibility.hostSpecificMetadataArtifactBySourceSet) - } - val allVisibleSourceSets = sourceSetVisibility.visibleSourceSetNames val sourceSetsVisibleInParents = parentResolutionsForModule @@ -253,33 +245,32 @@ internal class GranularMetadataTransformation( val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in sourceSetsVisibleInParents } - return ChooseVisibleSourceSetsImpl( - module, resolvedToProject, projectStructureMetadata, allVisibleSourceSets, visibleSourceSetsExcludingDependsOn, - transitiveDependenciesToVisit, mppDependencyMetadataExtractor + val metadataProvider = when (mppDependencyMetadataExtractor) { + is ProjectMppDependencyProjectStructureMetadataExtractor -> ProjectMetadataProvider( + dependencyProject = mppDependencyMetadataExtractor.dependencyProject, + moduleIdentifier = mppDependencyMetadataExtractor.moduleIdentifier + ) + + is JarMppDependencyProjectStructureMetadataExtractor -> CompositeMetadataJar( + moduleIdentifier = ModuleIds.fromComponent(project, module).toString(), + projectStructureMetadata = projectStructureMetadata, + primaryArtifactFile = mppDependencyMetadataExtractor.primaryArtifactFile, + hostSpecificArtifactsBySourceSet = sourceSetVisibility.hostSpecificMetadataArtifactBySourceSet, + ).asMetadataProvider() + } + + return MetadataDependencyResolution.ChooseVisibleSourceSets( + dependency = module, + projectDependency = resolvedToProject, + projectStructureMetadata = projectStructureMetadata, + allVisibleSourceSetNames = allVisibleSourceSets, + visibleSourceSetNamesExcludingDependsOn = visibleSourceSetsExcludingDependsOn, + visibleTransitiveDependencies = transitiveDependenciesToVisit, + metadataProvider = metadataProvider ) } } -internal class ChooseVisibleSourceSetsImpl( - dependency: ResolvedComponentResult, - projectDependency: Project?, - projectStructureMetadata: KotlinProjectStructureMetadata, - allVisibleSourceSetNames: Set, - visibleSourceSetNamesExcludingDependsOn: Set, - visibleTransitiveDependencies: Set, - private val metadataExtractor: MppDependencyMetadataExtractor -) : MetadataDependencyResolution.ChooseVisibleSourceSets( - dependency, - projectDependency, - projectStructureMetadata, - allVisibleSourceSetNames, - visibleSourceSetNamesExcludingDependsOn, - visibleTransitiveDependencies -) { - override fun getExtractableMetadataFiles(baseDir: File): ExtractableMetadataFiles = - metadataExtractor.getExtractableMetadataFiles(visibleSourceSetNamesExcludingDependsOn, baseDir) -} - internal fun ResolvedComponentResult.toProjectOrNull(currentProject: Project): Project? { val identifier = id return when { @@ -354,283 +345,3 @@ internal fun requestedDependencies( val otherContributingSourceSets = dependsOnClosureWithInterCompilationDependencies(project, sourceSet) return listOf(sourceSet, *otherContributingSourceSets.toTypedArray()).flatMap(::collectScopedDependenciesFromSourceSet) } - - -internal abstract class MppDependencyMetadataExtractor(val project: Project, val component: ResolvedComponentResult) { - abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? - - abstract fun getExtractableMetadataFiles( - visibleSourceSetNames: Set, - baseDir: File - ): ExtractableMetadataFiles -} - -private class ProjectMppDependencyMetadataExtractor( - project: Project, - dependency: ResolvedComponentResult, - val moduleIdentifier: KotlinModuleIdentifier, - val dependencyProject: Project -) : MppDependencyMetadataExtractor(project, dependency) { - override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? { - return when (val topLevelExtension = dependencyProject.topLevelExtension) { - is KotlinPm20ProjectExtension -> buildProjectStructureMetadata( - topLevelExtension.modules.single { it.moduleIdentifier == moduleIdentifier } - ) - else -> buildKotlinProjectStructureMetadata(dependencyProject) - } - } - - override fun getExtractableMetadataFiles( - visibleSourceSetNames: Set, - baseDir: File - ): ExtractableMetadataFiles { - val result = when (val projectExtension = dependencyProject.topLevelExtension) { - is KotlinMultiplatformExtension -> projectExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations - .filter { it.name in visibleSourceSetNames }.associate { it.defaultSourceSet.name to it.output.classesDirs } - is KotlinPm20ProjectExtension -> { - val moduleId = moduleIdentifier - val module = projectExtension.modules.single { it.moduleIdentifier == moduleId } - val metadataCompilationRegistry = projectExtension.metadataCompilationRegistryByModuleId.getValue(moduleId) - visibleSourceSetNames.associateWith { - metadataCompilationRegistry.byFragment(module.fragments.getByName(it)).output.classesDirs - } - } - else -> error("unexpected top-level Kotlin extension $projectExtension") - } - - return object : ExtractableMetadataFiles() { - override fun getMetadataFilesPerSourceSet(createFiles: Boolean): Map = result - } - } -} - -private class IncludedBuildMetadataExtractor( - project: Project, - dependency: ResolvedComponentResult, - primaryArtifact: File -) : JarArtifactMppDependencyMetadataExtractor(project, dependency, primaryArtifact) { - - private val id: ProjectComponentIdentifier - - init { - val id = dependency.id - require(id is ProjectComponentIdentifier) { "dependency should resolve to a project" } - require(!id.build.isCurrentBuild) { "should be a project from an included build" } - this.id = id - } - - override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? = - GlobalProjectStructureMetadataStorage.getProjectStructureMetadata(project, id.build.name, id.projectPath) -} - -internal open class JarArtifactMppDependencyMetadataExtractor( - project: Project, - dependency: ResolvedComponentResult, - val primaryArtifact: File -) : MppDependencyMetadataExtractor(project, dependency) { - - // TODO: add proper API to make an artifact extractor with just the composite artifact "evolve" into one with host-specific artifacts - val metadataArtifactBySourceSet: MutableMap = mutableMapOf() - - private fun parseJsonProjectStructureMetadata(input: InputStream) = - parseKotlinSourceSetMetadataFromJson(input.reader().readText()) - - private fun parseXmlProjectStructureMetadata(input: InputStream) = - parseKotlinSourceSetMetadataFromXml(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(input)) - - override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? { - return ZipFile(primaryArtifact).use { zip -> - val (metadata, parseFunction) = - zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME")?.to(::parseJsonProjectStructureMetadata) - ?: zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME")?.to(::parseXmlProjectStructureMetadata) - ?: return null - - zip.getInputStream(metadata).use(parseFunction) - } - } - - override fun getExtractableMetadataFiles( - visibleSourceSetNames: Set, - baseDir: File - ): ExtractableMetadataFiles { - val primaryArtifact = primaryArtifact - val moduleId = ModuleIds.fromComponent(project, component) - - return JarExtractableMetadataFiles( - module = moduleId, - project = project, - baseDir = baseDir, - artifactBySourceSet = visibleSourceSetNames.associateWith { (metadataArtifactBySourceSet[it] ?: primaryArtifact) }, - projectStructureMetadata = checkNotNull(getProjectStructureMetadata()) { - "project structure metadata is needed to extract files" - } - ) - } - - private class JarExtractableMetadataFiles( - private val module: ModuleDependencyIdentifier, - private val project: Project, - private val baseDir: File, - private val artifactBySourceSet: Map, - private val projectStructureMetadata: KotlinProjectStructureMetadata - ) : ExtractableMetadataFiles() { - - override fun getMetadataFilesPerSourceSet(createFiles: Boolean): Map { - return artifactBySourceSet.mapValues { (sourceSetName, artifact) -> - project.files( - getCompiledSourceSetMetadata(sourceSetName, artifact, createFiles) - ) - } - } - - private fun getCompiledSourceSetMetadata(sourceSetName: String, artifact: File, createFiles: Boolean): File { - val moduleString = "${module.groupId}-${module.moduleId}" - val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } } - - val extension = projectStructureMetadata.sourceSetBinaryLayout[sourceSetName]?.archiveExtension - ?: SourceSetMetadataLayout.METADATA.archiveExtension - - val metadataOutputFile = transformedModuleRoot.resolve("$moduleString-$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 (!artifact.isFile) { - return metadataOutputFile - } - - if (!createFiles) { - return metadataOutputFile - } - - if (metadataOutputFile.exists()) { - metadataOutputFile.delete() - } - - ZipFile(artifact).use { zip -> - val entries = zip.entries().asSequence().filter { it.name.startsWith("$sourceSetName/") }.toList() - - // TODO: once IJ supports non-JAR metadata dependencies, extract to a directory, not a JAR - // Also, if both IJ and the CLI compiler can read metadata from a path inside a JAR, then no operations will be needed - - if (entries.any()) { - ZipOutputStream(metadataOutputFile.outputStream()).use { resultZipOutput -> - for (entry in entries) { - if (entry.isDirectory) - continue - - // Drop the source set name from the entry path - val resultEntry = ZipEntry(entry.name.substringAfter("/")) - - zip.getInputStream(entry).use { inputStream -> - resultZipOutput.putNextEntry(resultEntry) - inputStream.copyTo(resultZipOutput) - resultZipOutput.closeEntry() - } - } - } - } - } - - return metadataOutputFile - } - } -} - -internal fun getMetadataExtractor( - project: Project, - resolvedComponentResult: ResolvedComponentResult, - configuration: Configuration, - resolveViaAvailableAt: Boolean -): MppDependencyMetadataExtractor? { - val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project) - val moduleIdentifier = - resolvedComponentResult.toSingleModuleIdentifier() // FIXME this loses information about auxiliary module deps - // TODO check how this code works with multi-capability resolutions - - return mppDependencyMetadataExtractor( - resolvedMppVariantsProvider, - moduleIdentifier, - configuration, - resolveViaAvailableAt, - resolvedComponentResult, - project - ) -} - -internal fun getMetadataExtractor( - project: Project, - resolvedComponentResult: ResolvedComponentResult, - moduleIdentifier: KotlinModuleIdentifier, - configuration: Configuration -): MppDependencyMetadataExtractor? { - val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project) - return mppDependencyMetadataExtractor( - resolvedMppVariantsProvider, - moduleIdentifier, - configuration, - true, - resolvedComponentResult, - project - ) -} - -private fun mppDependencyMetadataExtractor( - resolvedMppVariantsProvider: ResolvedMppVariantsProvider, - moduleIdentifier: KotlinModuleIdentifier, - configuration: Configuration, - resolveViaAvailableAt: Boolean, - resolvedComponentResult: ResolvedComponentResult, - project: Project -): MppDependencyMetadataExtractor? { - var resolvedViaAvailableAt = false - - val metadataArtifact = resolvedMppVariantsProvider.getResolvedArtifactByPlatformModule( - moduleIdentifier, - configuration - ) ?: if (resolveViaAvailableAt) { - resolvedMppVariantsProvider.getHostSpecificMetadataArtifactByRootModule( - moduleIdentifier, - configuration - )?.also { - resolvedViaAvailableAt = true - } - } else null - - val actualComponent = if (resolvedViaAvailableAt) { - resolvedComponentResult.dependencies.filterIsInstance().singleOrNull()?.selected - ?: resolvedComponentResult - } else resolvedComponentResult - - val moduleId = actualComponent.id - return when { - moduleId is ProjectComponentIdentifier -> when { - moduleId.build.isCurrentBuild -> - ProjectMppDependencyMetadataExtractor(project, actualComponent, moduleIdentifier, project.project(moduleId.projectPath)) - metadataArtifact != null -> - IncludedBuildMetadataExtractor(project, actualComponent, metadataArtifact) - else -> null - } - metadataArtifact != null -> JarArtifactMppDependencyMetadataExtractor(project, actualComponent, metadataArtifact) - else -> null - } -} - -internal fun getProjectStructureMetadata( - project: Project, - module: ResolvedComponentResult, - configuration: Configuration, - moduleIdentifier: KotlinModuleIdentifier? = null -): KotlinProjectStructureMetadata? { - val extractor = if (moduleIdentifier != null) - getMetadataExtractor(project, module, moduleIdentifier, configuration) - else - getMetadataExtractor(project, module, configuration, resolveViaAvailableAt = true) - - return extractor?.getProjectStructureMetadata() -} - -// This class is needed to encapsulate how we extract the files and point to them in a way that doesn't capture the Gradle project state -internal abstract class ExtractableMetadataFiles { - abstract fun getMetadataFilesPerSourceSet(createFiles: Boolean): Map -} - diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt index 9cfa1d37ec9..6a8c61257fa 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt @@ -13,7 +13,6 @@ import org.gradle.api.Project import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.dsl.topLevelExtensionOrNull import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule @@ -28,6 +27,7 @@ import org.w3c.dom.Document import org.w3c.dom.Element import org.w3c.dom.Node import org.w3c.dom.NodeList +import java.io.Serializable import java.io.StringWriter import javax.xml.parsers.DocumentBuilderFactory @@ -35,7 +35,7 @@ import javax.xml.parsers.DocumentBuilderFactory open class ModuleDependencyIdentifier( open val groupId: String?, open val moduleId: String -) { +) : Serializable { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ModuleDependencyIdentifier) return false @@ -54,6 +54,10 @@ open class ModuleDependencyIdentifier( operator fun component1(): String? = groupId operator fun component2(): String = moduleId + + override fun toString(): String { + return "${groupId}-${moduleId}" + } } class ChangingModuleDependencyIdentifier( @@ -71,7 +75,7 @@ sealed class SourceSetMetadataLayout( val name: String, @get:Internal val archiveExtension: String -) { +) : Serializable { object METADATA : SourceSetMetadataLayout("metadata", "jar") object KLIB : SourceSetMetadataLayout("klib", "klib") @@ -109,7 +113,7 @@ data class KotlinProjectStructureMetadata( @Input val formatVersion: String = FORMAT_VERSION_0_3_1 -) { +) : Serializable { @Suppress("UNUSED") // Gradle input @get:Input internal val sourceSetModuleDependenciesInput: Map>> @@ -296,7 +300,7 @@ internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProj val nodeNamed: Element.(String) -> Element? = { name -> getElementsByTagName(name).elements.singleOrNull() } val valueNamed: Element.(String) -> String? = { name -> getElementsByTagName(name).run { if (length > 0) item(0).textContent else null } } - val multiObjects: Element.(String) -> Iterable = { name -> nodeNamed(name)?.childNodes?.elements ?: emptyList()} + val multiObjects: Element.(String) -> Iterable = { name -> nodeNamed(name)?.childNodes?.elements ?: emptyList() } val multiValues: Element.(String) -> Iterable = { name -> getElementsByTagName(name).elements.map { it.textContent } } return parseKotlinSourceSetMetadata( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/MppDependencyProjectStructureMetadataExtractor.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/MppDependencyProjectStructureMetadataExtractor.kt new file mode 100644 index 00000000000..4bcd5e96329 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/MppDependencyProjectStructureMetadataExtractor.kt @@ -0,0 +1,79 @@ +/* + * 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.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.jetbrains.kotlin.gradle.dsl.topLevelExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension +import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier +import java.io.File +import java.io.InputStream +import java.util.zip.ZipFile +import javax.xml.parsers.DocumentBuilderFactory + +sealed class MppDependencyProjectStructureMetadataExtractor { + abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? + + companion object Factory +} + +internal class ProjectMppDependencyProjectStructureMetadataExtractor( + val moduleIdentifier: KotlinModuleIdentifier, + val dependencyProject: Project +) : MppDependencyProjectStructureMetadataExtractor() { + + override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? { + return when (val topLevelExtension = dependencyProject.topLevelExtension) { + is KotlinPm20ProjectExtension -> buildProjectStructureMetadata( + topLevelExtension.modules.single { it.moduleIdentifier == moduleIdentifier } + ) + else -> buildKotlinProjectStructureMetadata(dependencyProject) + } + } +} + +internal open class JarMppDependencyProjectStructureMetadataExtractor( + val primaryArtifactFile: File +) : MppDependencyProjectStructureMetadataExtractor() { + + private fun parseJsonProjectStructureMetadata(input: InputStream) = + parseKotlinSourceSetMetadataFromJson(input.reader().readText()) + + private fun parseXmlProjectStructureMetadata(input: InputStream) = + parseKotlinSourceSetMetadataFromXml(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(input)) + + override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? { + return ZipFile(primaryArtifactFile).use { zip -> + val (metadata, parseFunction) = + zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME")?.to(::parseJsonProjectStructureMetadata) + ?: zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME")?.to(::parseXmlProjectStructureMetadata) + ?: return null + + zip.getInputStream(metadata).use(parseFunction) + } + } +} + +internal class IncludedBuildMppDependencyProjectStructureMetadataExtractor( + private val project: Project, + dependency: ResolvedComponentResult, + primaryArtifact: File +) : JarMppDependencyProjectStructureMetadataExtractor(primaryArtifact) { + + private val id: ProjectComponentIdentifier + + init { + val id = dependency.id + require(id is ProjectComponentIdentifier) { "dependency should resolve to a project" } + require(!id.build.isCurrentBuild) { "should be a project from an included build" } + this.id = id + } + + override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? = + GlobalProjectStructureMetadataStorage.getProjectStructureMetadata(project, id.build.name, id.projectPath) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/MppDependencyProjectStructureMetadataExtractorFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/MppDependencyProjectStructureMetadataExtractorFactory.kt new file mode 100644 index 00000000000..e10ad1128a2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/MppDependencyProjectStructureMetadataExtractorFactory.kt @@ -0,0 +1,90 @@ +/* + * 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.artifacts.Configuration +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.artifacts.result.ResolvedDependencyResult +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toSingleModuleIdentifier +import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier + +internal fun MppDependencyProjectStructureMetadataExtractor.Factory.create( + project: Project, + resolvedComponentResult: ResolvedComponentResult, + configuration: Configuration, + resolveViaAvailableAt: Boolean +): MppDependencyProjectStructureMetadataExtractor? { + return create( + resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project), + /* + FIXME this loses information about auxiliary module deps + TODO check how this code works with multi-capability resolutions, + */ + moduleIdentifier = resolvedComponentResult.toSingleModuleIdentifier(), + configuration = configuration, + resolveViaAvailableAt = resolveViaAvailableAt, + resolvedComponentResult = resolvedComponentResult, + project = project + ) +} + +internal fun MppDependencyProjectStructureMetadataExtractor.Factory.create( + project: Project, + resolvedComponentResult: ResolvedComponentResult, + moduleIdentifier: KotlinModuleIdentifier, + configuration: Configuration +): MppDependencyProjectStructureMetadataExtractor? { + return create( + resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project), + moduleIdentifier = moduleIdentifier, + configuration = configuration, + resolveViaAvailableAt = true, + resolvedComponentResult = resolvedComponentResult, + project = project + ) +} + +private fun MppDependencyProjectStructureMetadataExtractor.Factory.create( + resolvedMppVariantsProvider: ResolvedMppVariantsProvider, + moduleIdentifier: KotlinModuleIdentifier, + configuration: Configuration, + resolveViaAvailableAt: Boolean, + resolvedComponentResult: ResolvedComponentResult, + project: Project +): MppDependencyProjectStructureMetadataExtractor? { + var resolvedViaAvailableAt = false + + val metadataArtifact = resolvedMppVariantsProvider.getResolvedArtifactByPlatformModule( + moduleIdentifier, + configuration + ) ?: if (resolveViaAvailableAt) { + resolvedMppVariantsProvider.getHostSpecificMetadataArtifactByRootModule( + moduleIdentifier, configuration + )?.also { + resolvedViaAvailableAt = true + } + } else null + + val actualComponent = if (resolvedViaAvailableAt) { + resolvedComponentResult.dependencies.filterIsInstance().singleOrNull()?.selected + ?: resolvedComponentResult + } else resolvedComponentResult + + val moduleId = actualComponent.id + return when { + moduleId is ProjectComponentIdentifier -> when { + moduleId.build.isCurrentBuild -> + ProjectMppDependencyProjectStructureMetadataExtractor(moduleIdentifier, project.project(moduleId.projectPath)) + metadataArtifact != null -> + IncludedBuildMppDependencyProjectStructureMetadataExtractor(project, actualComponent, metadataArtifact) + else -> null + } + metadataArtifact != null -> JarMppDependencyProjectStructureMetadataExtractor(metadataArtifact) + else -> null + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ProjectMetadataProviderImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ProjectMetadataProviderImpl.kt new file mode 100644 index 00000000000..22c529986dc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ProjectMetadataProviderImpl.kt @@ -0,0 +1,57 @@ +/* + * 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.FileCollection +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.topLevelExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension +import org.jetbrains.kotlin.gradle.targets.native.internal.* +import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier + +internal fun ProjectMetadataProvider( + dependencyProject: Project, + moduleIdentifier: KotlinModuleIdentifier +): ProjectMetadataProvider { + return ProjectMetadataProviderImpl(dependencyProject, moduleIdentifier) +} + +private class ProjectMetadataProviderImpl( + private val dependencyProject: Project, + private val moduleIdentifier: KotlinModuleIdentifier +) : ProjectMetadataProvider() { + override fun getSourceSetCompiledMetadata(sourceSetName: String): FileCollection { + return when (val projectExtension = dependencyProject.topLevelExtension) { + is KotlinMultiplatformExtension -> projectExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations + .firstOrNull { it.name == sourceSetName } + ?.output?.classesDirs ?: dependencyProject.files() + + is KotlinPm20ProjectExtension -> { + val moduleId = moduleIdentifier + val module = projectExtension.modules.single { it.moduleIdentifier == moduleId } + val metadataCompilationRegistry = projectExtension.metadataCompilationRegistryByModuleId.getValue(moduleId) + metadataCompilationRegistry.byFragment(module.fragments.getByName(sourceSetName)).output.classesDirs + } + else -> error("unexpected top-level Kotlin extension $projectExtension") + } + } + + override fun getSourceSetCInteropMetadata(sourceSetName: String, consumer: MetadataConsumer): FileCollection { + val multiplatformExtension = dependencyProject.topLevelExtension as? KotlinMultiplatformExtension + ?: return dependencyProject.files() + + val commonizeCInteropTask = when (consumer) { + MetadataConsumer.Ide -> dependencyProject.copyCommonizeCInteropForIdeTask ?: return dependencyProject.files() + MetadataConsumer.Cli -> dependencyProject.commonizeCInteropTask ?: return dependencyProject.files() + } + + val sourceSet = multiplatformExtension.sourceSets.findByName(sourceSetName) ?: return dependencyProject.files() + val dependent = CInteropCommonizerDependent.from(dependencyProject, sourceSet) ?: return dependencyProject.files() + return commonizeCInteropTask.get().commonizedOutputLibraries(dependent) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt index 4ef1b023ccb..dc291ffd41d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/TransformKotlinGranularMetadata.kt @@ -54,8 +54,8 @@ open class TransformKotlinGranularMetadata CompilationSourceSetUtil.compilationsBySourceSets(project) .filterKeys { it in sourceSets } .entries.associate { (sourceSet, compilations) -> - sourceSet.name to compilations.map { it.name }.sorted() - } + sourceSet.name to compilations.map { it.name }.sorted() + } } private val participatingCompilations: Iterable> @@ -96,18 +96,15 @@ open class TransformKotlinGranularMetadata transformation.metadataDependencyResolutions } - private val extractableFilesByResolution: Map + @get:Internal + internal val filesByResolution: Map get() = metadataDependencyResolutions .filterIsInstance() - .associateWith { it.getExtractableMetadataFiles(outputsDir) } - - @get:Internal - internal val filesByResolution: Map - get() = extractableFilesByResolution.mapValues { (_, value) -> - project.files(value.getMetadataFilesPerSourceSet(false).values).builtBy(this) - } - - private val extractableFiles by project.provider { extractableFilesByResolution.values } + .associateWith { chooseVisibleSourceSets -> + chooseVisibleSourceSets.getAllCompiledSourceSetMetadata( + project, outputDirectoryWhenMaterialised = outputsDir, materializeFilesIfNecessary = false + ).builtBy(this) + } @TaskAction fun transformMetadata() { @@ -116,7 +113,13 @@ open class TransformKotlinGranularMetadata } outputsDir.mkdirs() - extractableFiles.forEach { it.getMetadataFilesPerSourceSet(createFiles = true) } + metadataDependencyResolutions + .filterIsInstance() + .forEach { chooseVisibleSourceSets -> + chooseVisibleSourceSets.getAllCompiledSourceSetMetadata( + project, outputDirectoryWhenMaterialised = outputsDir, materializeFilesIfNecessary = true + ) + } } } @@ -125,5 +128,6 @@ internal class SourceSetResolvedMetadataProvider( ) : ResolvedMetadataFilesProvider { override val buildDependencies: Iterable> = listOf(taskProvider) override val metadataResolutions: Iterable by taskProvider.map { it.metadataDependencyResolutions } - override val metadataFilesByResolution: Map by taskProvider.map { it.filesByResolution } + override val metadataFilesByResolution: Map + by taskProvider.map { it.filesByResolution } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt index a06614cf323..696f89867dd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt @@ -13,9 +13,7 @@ import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.capabilities.Capability import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.plugin.mpp.getProjectStructureMetadata import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder -import org.jetbrains.kotlin.gradle.utils.getOrPut import org.jetbrains.kotlin.gradle.utils.getOrPutRootProjectProperty import org.jetbrains.kotlin.project.model.* import java.util.* @@ -214,3 +212,17 @@ internal fun ComponentIdentifier.matchesModuleIdentifier(id: KotlinModuleIdentif } else -> false } + +private fun getProjectStructureMetadata( + project: Project, + module: ResolvedComponentResult, + configuration: Configuration, + moduleIdentifier: KotlinModuleIdentifier? = null +): KotlinProjectStructureMetadata? { + val extractor = if (moduleIdentifier != null) + MppDependencyProjectStructureMetadataExtractor.create(project, module, moduleIdentifier, configuration) + else + MppDependencyProjectStructureMetadataExtractor.create(project, module, configuration, resolveViaAvailableAt = true) + + return extractor?.getProjectStructureMetadata() +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt index 4f92f5aad8b..e9d1740f8a0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/FragmentGranularMetadataResolver.kt @@ -8,13 +8,11 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.pm20 import org.gradle.api.Project import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.plugin.mpp.ChooseVisibleSourceSetsImpl -import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution -import org.jetbrains.kotlin.gradle.plugin.mpp.getMetadataExtractor -import org.jetbrains.kotlin.gradle.plugin.mpp.getProjectStructureMetadata +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.Companion.asMetadataProvider import org.jetbrains.kotlin.project.model.* import org.jetbrains.kotlin.utils.addToStdlib.flattenTo -import java.util.ArrayDeque +import java.io.File +import java.util.* internal class FragmentGranularMetadataResolver( private val requestingFragment: KotlinGradleFragment, @@ -80,33 +78,46 @@ internal class FragmentGranularMetadataResolver( MetadataDependencyResolution.KeepOriginalDependency(resolvedComponentResult, isResolvedAsProject) } else -> run { + val metadataSourceComponent = dependencyNode.run { metadataSourceComponent ?: selectedComponent } - val metadataExtractor = getMetadataExtractor(project, resolvedComponentResult, configurationToResolve, true) - - if (dependencyModule is ExternalImportedKotlinModule && - metadataExtractor is JarArtifactMppDependencyMetadataExtractor && - chosenFragments != null - ) { - resolveHostSpecificMetadataArtifacts(dependencyModule, chosenFragments, metadataExtractor) - } - - val projectStructureMetadata = (dependencyModule as? ExternalImportedKotlinModule)?.projectStructureMetadata - ?: checkNotNull(metadataExtractor?.getProjectStructureMetadata()) - val visibleFragmentNames = visibleFragments.map { it.fragmentName }.toSet() val visibleFragmentNamesExcludingVisibleByParents = - visibleFragmentNames - .minus(fragmentsNamesVisibleByParents(metadataSourceComponent.toSingleModuleIdentifier())) + visibleFragmentNames.minus(fragmentsNamesVisibleByParents(metadataSourceComponent.toSingleModuleIdentifier())) - ChooseVisibleSourceSetsImpl( - metadataSourceComponent, - isResolvedAsProject, - projectStructureMetadata, - visibleFragmentNames, - visibleFragmentNamesExcludingVisibleByParents, + val metadataExtractor = MppDependencyProjectStructureMetadataExtractor.create( + project, resolvedComponentResult, configurationToResolve, true + ) ?: error("Failed to create 'MppDependencyProjectStructureMetadataExtractor' for ${resolvedComponentResult.id}") + + val projectStructureMetadata = (dependencyModule as? ExternalImportedKotlinModule)?.projectStructureMetadata + ?: checkNotNull(metadataExtractor.getProjectStructureMetadata()) + + + val metadataProvider = when (metadataExtractor) { + is ProjectMppDependencyProjectStructureMetadataExtractor -> ProjectMetadataProvider( + dependencyProject = metadataExtractor.dependencyProject, + moduleIdentifier = metadataExtractor.moduleIdentifier + ) + + is JarMppDependencyProjectStructureMetadataExtractor -> CompositeMetadataJar( + moduleIdentifier = ModuleIds.fromComponent(project, metadataSourceComponent).toString(), + projectStructureMetadata = projectStructureMetadata, + primaryArtifactFile = metadataExtractor.primaryArtifactFile, + hostSpecificArtifactsBySourceSet = if ( + dependencyModule is ExternalImportedKotlinModule && chosenFragments != null + ) resolveHostSpecificMetadataArtifacts(dependencyModule, chosenFragments) else emptyMap(), + ).asMetadataProvider() + } + + MetadataDependencyResolution.ChooseVisibleSourceSets( + dependency = metadataSourceComponent, + projectDependency = isResolvedAsProject, + projectStructureMetadata = projectStructureMetadata, + allVisibleSourceSetNames = visibleFragmentNames, + visibleSourceSetNamesExcludingDependsOn = visibleFragmentNamesExcludingVisibleByParents, + visibleTransitiveDependencies = visibleTransitiveDependencies.map { resolvedDependenciesByModuleId.getValue(it.module.moduleIdentifier) }.toSet(), - checkNotNull(metadataExtractor) + metadataProvider = metadataProvider ) } } @@ -124,19 +135,18 @@ internal class FragmentGranularMetadataResolver( private fun fragmentsNamesVisibleByParents(kotlinModuleIdentifier: KotlinModuleIdentifier): MutableSet { val parentResolutionsForDependency = parentResultsByModuleIdentifier[kotlinModuleIdentifier].orEmpty() - return parentResolutionsForDependency.filterIsInstance() + return parentResolutionsForDependency.filterIsInstance() .flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames } } private fun resolveHostSpecificMetadataArtifacts( dependencyModule: ExternalImportedKotlinModule, chosenFragments: FragmentResolution.ChosenFragments, - metadataExtractor: JarArtifactMppDependencyMetadataExtractor - ) { + ): Map { val visibleFragments = chosenFragments.visibleFragments val variantResolutions = chosenFragments.variantResolutions val hostSpecificFragments = dependencyModule.hostSpecificFragments - val hostSpecificFragmentToArtifact = visibleFragments.intersect(hostSpecificFragments).mapNotNull { hostSpecificFragment -> + return visibleFragments.intersect(hostSpecificFragments).mapNotNull { hostSpecificFragment -> val relevantVariantResolution = variantResolutions .filterIsInstance() // find some of our variants that resolved a dependency's variant containing the fragment @@ -152,7 +162,6 @@ internal class FragmentGranularMetadataResolver( ) hostSpecificArtifact?.let { hostSpecificFragment.fragmentName to it } } - } - metadataExtractor.metadataArtifactBySourceSet.putAll(hostSpecificFragmentToArtifact) + }.toMap() } -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt index 3d0e90f305b..8e42c5a8966 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinMetadataCompilationData.kt @@ -17,9 +17,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonOptionsImpl import org.jetbrains.kotlin.gradle.dsl.pm20Extension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder import org.jetbrains.kotlin.gradle.targets.metadata.ResolvedMetadataFilesProvider -import org.jetbrains.kotlin.gradle.targets.metadata.createTransformedMetadataClasspath +import org.jetbrains.kotlin.gradle.targets.metadata.createMetadataDependencyTransformationClasspath import org.jetbrains.kotlin.gradle.utils.getValue import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.konan.target.HostManager @@ -59,7 +58,7 @@ internal abstract class AbstractKotlinFragmentMetadataCompilationData + @get:Internal + internal val filesByResolution: Map get() = metadataDependencyResolutions .filterIsInstance() - .associateWith { it.getExtractableMetadataFiles(outputsDir) } - - @get:Internal - internal val filesByResolution: Map - get() = extractableFilesByResolution.mapValues { (_, value) -> - project.files(value.getMetadataFilesPerSourceSet(false).values).builtBy(this) - } - - private val extractableFiles by project.provider { extractableFilesByResolution.values } + .associateWith { chooseVisibleSourceSets -> + chooseVisibleSourceSets.getAllCompiledSourceSetMetadata( + project, outputsDir, materializeFilesIfNecessary = false + ).builtBy(this) + } @TaskAction fun transformMetadata() { @@ -84,7 +81,11 @@ internal open class TransformKotlinGranularMetadataForFragment } outputsDir.mkdirs() - extractableFiles.forEach { it.getMetadataFilesPerSourceSet(createFiles = true) } + metadataDependencyResolutions + .filterIsInstance() + .forEach { chooseVisibleSourceSets -> + chooseVisibleSourceSets.getAllCompiledSourceSetMetadata(project, outputsDir, materializeFilesIfNecessary = true) + } } } @@ -93,5 +94,5 @@ internal class FragmentResolvedMetadataProvider( ) : ResolvedMetadataFilesProvider { override val buildDependencies: Iterable> = listOf(taskProvider) override val metadataResolutions: Iterable by taskProvider.map { it.metadataDependencyResolutions } - override val metadataFilesByResolution: Map by taskProvider.map { it.filesByResolution } + override val metadataFilesByResolution: Map by taskProvider.map { it.filesByResolution } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt index b364bd064ec..0e9c459f6e2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt @@ -155,7 +155,6 @@ class DefaultKotlinSourceSet( return getDependenciesTransformation(scope) } - @Suppress("unused") // Used in IDE import fun getAdditionalVisibleSourceSets(): List = getVisibleSourceSetsFromAssociateCompilations(project, this) @@ -184,10 +183,14 @@ class DefaultKotlinSourceSet( MetadataDependencyTransformation(group, name, projectPath, null, emptySet(), emptyMap()) is MetadataDependencyResolution.ChooseVisibleSourceSets -> { - val filesBySourceSet = resolution.getMetadataFilesBySourceSet( - baseDir, - createFiles = true - ).filter { it.value.any { it.exists() } } + val filesBySourceSet = resolution.visibleSourceSetNamesExcludingDependsOn.associateWith { visibleSourceSetName -> + resolution.metadataProvider.getSourceSetCompiledMetadata( + project, + sourceSetName = visibleSourceSetName, + outputDirectoryWhenMaterialised = baseDir, + materializeFilesIfNecessary = true + ) + }.filter { (_, files) -> files.any(File::exists) } MetadataDependencyTransformation( group, name, projectPath, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt index c7cb98d8fb7..16751b4b7ee 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt @@ -113,7 +113,7 @@ class KotlinMetadataTargetConfigurator : override fun setupCompilationDependencyFiles(compilation: KotlinCompilation) { val project = compilation.target.project - /** See [createTransformedMetadataClasspath] and its usage. */ + /** See [createMetadataDependencyTransformationClasspath] and its usage. */ if (project.isKotlinGranularMetadataEnabled && compilation.name != KotlinCompilation.MAIN_COMPILATION_NAME) compilation.compileDependencyFiles = project.files() else @@ -332,10 +332,14 @@ class KotlinMetadataTargetConfigurator : listOf(sourceSet) ) { } - compilation.compileDependencyFiles += createTransformedMetadataClasspath( + compilation.compileDependencyFiles += createMetadataDependencyTransformationClasspath( project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME), compilation ) + + if (compilation is KotlinSharedNativeCompilation && sourceSet is DefaultKotlinSourceSet) { + compilation.compileDependencyFiles += project.createCInteropMetadataDependencyClasspath(sourceSet) + } } private fun setupDependencyTransformationForSourceSet( @@ -418,7 +422,7 @@ class KotlinMetadataTargetConfigurator : } } - private fun createTransformedMetadataClasspath( + private fun createMetadataDependencyTransformationClasspath( fromFiles: Configuration, compilation: AbstractKotlinCompilation<*> ): FileCollection { @@ -440,7 +444,7 @@ class KotlinMetadataTargetConfigurator : transformationTaskHolders.map { SourceSetResolvedMetadataProvider(it) } } - return createTransformedMetadataClasspath( + return createMetadataDependencyTransformationClasspath( project, fromFiles, dependsOnCompilationOutputs, @@ -492,14 +496,14 @@ internal fun Project.createGenerateProjectStructureMetadataTask(): TaskProvider< internal interface ResolvedMetadataFilesProvider { val buildDependencies: Iterable> val metadataResolutions: Iterable - val metadataFilesByResolution: Map + val metadataFilesByResolution: Map } -internal fun createTransformedMetadataClasspath( +internal fun createMetadataDependencyTransformationClasspath( project: Project, fromFiles: Configuration, parentCompiledMetadataFiles: Lazy>, - metadataResolutionProviders: Lazy> + metadataResolutionProviders: Lazy>, ): FileCollection { return project.files( Callable { @@ -547,7 +551,7 @@ internal fun createTransformedMetadataClasspath( } internal fun isSharedNativeSourceSet(project: Project, sourceSet: KotlinSourceSet): Boolean { - val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project)[sourceSet].orEmpty() + val compilations = compilationsBySourceSets(project)[sourceSet].orEmpty() return compilations.isNotEmpty() && compilations.all { it.platformType == KotlinPlatformType.common || it.platformType == KotlinPlatformType.native } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt index 4dc72f55f34..1965bfe2a71 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.base64Hash import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.ensureMaxFileNameLength import org.jetbrains.kotlin.commonizer.identityString import org.jetbrains.kotlin.gradle.utils.filesProvider +import org.jetbrains.kotlin.gradle.utils.outputFilesProvider import java.io.File internal abstract class AbstractCInteropCommonizerTask : DefaultTask() { @@ -33,12 +34,10 @@ internal fun AbstractCInteropCommonizerTask.outputDirectory(group: CInteropCommo } internal fun AbstractCInteropCommonizerTask.commonizedOutputLibraries(dependent: CInteropCommonizerDependent): FileCollection { - val fileProvider = project.filesProvider { - (commonizedOutputDirectory(dependent) ?: return@filesProvider emptySet()) + return outputFilesProvider { + (commonizedOutputDirectory(dependent) ?: return@outputFilesProvider emptySet()) .listFiles().orEmpty().toSet() } - - return fileProvider.builtBy(this) } internal fun AbstractCInteropCommonizerTask.commonizedOutputDirectory(dependent: CInteropCommonizerDependent): File? { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerAllMetadataKlib.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerAllMetadataKlib.kt deleted file mode 100644 index 61a95248bd8..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerAllMetadataKlib.kt +++ /dev/null @@ -1,25 +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.targets.native.internal - -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 - -internal fun Project.includeCommonizedCInteropMetadata(metadataKlib: TaskProvider, compilation: KotlinSharedNativeCompilation) { - metadataKlib.configure { jar -> includeCommonizedCInteropMetadata(jar, compilation) } -} - -internal fun Project.includeCommonizedCInteropMetadata(metadataKlib: Zip, compilation: KotlinSharedNativeCompilation) { - val commonizerTask = commonizeCInteropTask?.get() ?: return - val commonizerDependencyToken = CInteropCommonizerDependent.from(compilation) ?: return - val outputDirectory = commonizerTask.commonizedOutputDirectory(commonizerDependencyToken) ?: return - - metadataKlib.from(outputDirectory) { spec -> - spec.into(compilation.defaultSourceSet.name + "-cinterop") - } -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerDependencies.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerDependencies.kt index ceb2f8621f9..a3bdf0ced66 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerDependencies.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerDependencies.kt @@ -6,9 +6,11 @@ package org.jetbrains.kotlin.gradle.targets.native.internal import org.gradle.api.Project +import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.whenEvaluated import org.jetbrains.kotlin.gradle.utils.filesProvider import java.io.File @@ -21,6 +23,7 @@ internal fun Project.setupCInteropCommonizerDependencies() { kotlin.forAllDefaultKotlinSourceSets { sourceSet -> setupCInteropCommonizerDependenciesForIde(sourceSet) + setupCInteropTransformCompositeMetadataDependenciesForIde(sourceSet) } } @@ -50,3 +53,12 @@ private fun Project.setupCInteropCommonizerDependenciesForIde(sourceSet: Default } }) } + +private fun Project.setupCInteropTransformCompositeMetadataDependenciesForIde(sourceSet: DefaultKotlinSourceSet) { + whenEvaluated { + if (getCommonizerTarget(sourceSet) !is SharedCommonizerTarget) return@whenEvaluated + addIntransitiveMetadataDependencyIfPossible( + sourceSet, createCInteropMetadataDependencyClasspathForIde(sourceSet) + ) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyClasspath.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyClasspath.kt new file mode 100644 index 00000000000..6539c3dffa8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyClasspath.kt @@ -0,0 +1,101 @@ +/* + * 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.targets.native.internal + +import org.gradle.api.Project +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 org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider.MetadataConsumer.Cli +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider.MetadataConsumer.Ide +import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet +import org.jetbrains.kotlin.gradle.utils.filesProvider +import java.io.File + +internal fun Project.createCInteropMetadataDependencyClasspath(sourceSet: DefaultKotlinSourceSet): FileCollection { + return createCInteropMetadataDependencyClasspath(sourceSet, forIde = false) +} + +internal fun Project.createCInteropMetadataDependencyClasspathForIde(sourceSet: DefaultKotlinSourceSet): FileCollection { + return createCInteropMetadataDependencyClasspath(sourceSet, forIde = true) +} + +/** + * @param forIde: A different task for dependency transformation will be used. This task will not use the regular 'build' directory + * as transformation output to ensure IDE still being able to resolve the dependencies even when the project is cleaned. + */ +internal fun Project.createCInteropMetadataDependencyClasspath(sourceSet: DefaultKotlinSourceSet, forIde: Boolean): FileCollection { + val dependencyTransformationTask = if (forIde) locateOrRegisterCInteropMetadataDependencyTransformationTaskForIde(sourceSet) + else locateOrRegisterCInteropMetadataDependencyTransformationTask(sourceSet) + + /* + The classpath will be assembled by three independent parts + 1) C-Interop Metadata which will be downloaded Jar files that get transformed by the transformation task + 2) C-Interop Metadata directly provided by dependency projects (in the same build) + 3) C-Interop Metadata from 'associated compilations' / additionalVisible source sets + (e.g. 'nativeTest' will be able to access the classpath from 'nativeMain') + */ + return project.files(dependencyTransformationTask.map { it.outputLibraryFiles }) + + createCInteropMetadataDependencyClasspathFromProjectDependencies(sourceSet, forIde) + + createCInteropMetadataDependencyClasspathFromAssociatedCompilations(sourceSet, forIde) +} + +private fun Project.createCInteropMetadataDependencyClasspathFromProjectDependencies( + sourceSet: DefaultKotlinSourceSet, + forIde: Boolean +): FileCollection { + return filesProvider { + sourceSet.dependencyTransformations.values.flatMap { it.metadataDependencyResolutions } + .filterIsInstance() + .flatMap { chooseVisibleSourceSets -> + /* We only want to access resolutions that provide metadata from dependency projects */ + val projectMetadataProvider = when (chooseVisibleSourceSets.metadataProvider) { + is ProjectMetadataProvider -> chooseVisibleSourceSets.metadataProvider + is JarMetadataProvider -> return@flatMap emptyList() + } + + chooseVisibleSourceSets.visibleSourceSetsProvidingCInterops.map { visibleSourceSetName -> + projectMetadataProvider.getSourceSetCInteropMetadata(visibleSourceSetName, if (forIde) Ide else Cli) + } + } + } +} + +private fun Project.createCInteropMetadataDependencyClasspathFromAssociatedCompilations( + sourceSet: DefaultKotlinSourceSet, + forIde: Boolean +): FileCollection { + return filesProvider files@{ + val commonizerTarget = getSharedCommonizerTarget(sourceSet) ?: return@files emptySet() + + /* + We will find the 'most suitable' / 'closest matching' source set + (like 'nativeTest' -> 'nativeMain', 'appleTest' -> 'appleMain', ...). + If no source set is found that matches the commonizer target explicitly, the next "bigger" source set shall be chosen + */ + val (associatedSourceSet, _) = sourceSet.getAdditionalVisibleSourceSets() + .filterIsInstance() + .mapNotNull { otherSourceSet -> otherSourceSet to (getSharedCommonizerTarget(otherSourceSet) ?: return@mapNotNull null) } + .filter { (_, otherCommonizerTarget) -> otherCommonizerTarget.targets.containsAll(commonizerTarget.targets) } + .minByOrNull { (_, otherCommonizerTarget) -> otherCommonizerTarget.targets.size } ?: return@files emptySet() + + createCInteropMetadataDependencyClasspath(associatedSourceSet, forIde) + } +} + +/** + * Names of all source sets that may potentially provide necessary cinterops for this resolution. + * This will select 'the most bottom' source sets in [ChooseVisibleSourceSets.allVisibleSourceSetNames] + */ +internal val ChooseVisibleSourceSets.visibleSourceSetsProvidingCInterops: Set + get() { + val dependsOnSourceSets = allVisibleSourceSetNames + .flatMap { projectStructureMetadata.sourceSetsDependsOnRelation[it].orEmpty() } + .toSet() + + return allVisibleSourceSetNames.filter { it !in dependsOnSourceSets }.toSet() + } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt new file mode 100644 index 00000000000..62eccfc6181 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt @@ -0,0 +1,130 @@ +/* + * 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.targets.native.internal + +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation +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 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.locateOrRegisterTask +import org.jetbrains.kotlin.gradle.utils.filesProvider +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.gradle.utils.outputFilesProvider +import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION +import java.io.File +import javax.inject.Inject + +internal fun Project.locateOrRegisterCInteropMetadataDependencyTransformationTask( + sourceSet: DefaultKotlinSourceSet, +): TaskProvider { + return locateOrRegisterTask( + lowerCamelCaseName("transform", sourceSet.name, "CInteropDependenciesMetadata"), + args = listOf( + sourceSet, + /* outputDirectory = */ + project.buildDir.resolve("kotlinSourceSetMetadata").resolve(sourceSet.name + "-cinterop") + ) + ) +} + +internal fun Project.locateOrRegisterCInteropMetadataDependencyTransformationTaskForIde( + sourceSet: DefaultKotlinSourceSet, +): TaskProvider { + return locateOrRegisterTask( + lowerCamelCaseName("transform", sourceSet.name, "CInteropDependenciesMetadataForIde"), + invokeWhenRegistered = { commonizeTask.dependsOn(this) }, + args = listOf( + sourceSet, + /* outputDirectory = */ + SourceSetMetadataStorageForIde.sourceSetStorage(project, sourceSet.name).resolve("cinterop") + ), + ) +} + +internal open class CInteropMetadataDependencyTransformationTask @Inject constructor( + @get:Internal val sourceSet: DefaultKotlinSourceSet, + @get:OutputDirectory val outputDirectory: File +) : DefaultTask() { + + /** + * Esoteric output file that every [CInteropMetadataDependencyTransformationTask] will point to. + * Sharing this output file will tell Gradle that those tasks cannot run in parallel. + * Parallelling this task is not supported, because parallel access to [GranularMetadataTransformation.metadataDependencyResolutions] + * is not supported during Gradle's execution phase. This is due to this property's Lazy's lock and + * another Lock used by Gradle which would lead to deadlocking. + * + * This task could potentially be ordered topologically based on dependsOn edges or parent transformations, however + * we actually do not care about the actual order chosen by Gradle. + */ + @get:OutputFile + protected val mutex: File = project.file(".cinteropMetadataDependencyTransformationMutex") + + @Suppress("unused") + @get:InputFiles + @get:Classpath + protected val inputArtifactFiles: FileCollection = project.filesProvider { + sourceSet.dependencyTransformations.values.map { it.configurationToResolve.withoutProjectDependencies() } + } + + @get:Internal + protected val chooseVisibleSourceSets + get() = sourceSet.dependencyTransformations.values + .flatMap { it.metadataDependencyResolutions } + .filterIsInstance() + + @Suppress("unused") + @get:Input + protected val dependencyProjectStructureMetadata + get() = chooseVisibleSourceSets.map { it.projectStructureMetadata } + + @get:Internal + val outputLibraryFiles = outputFilesProvider { + outputDirectory.walkTopDown().maxDepth(2).filter { it.isFile && it.extension == KLIB_FILE_EXTENSION }.toList() + } + + @TaskAction + protected fun transformDependencies() { + if (outputDirectory.isDirectory) { + outputDirectory.deleteRecursively() + } + + if (project.getCommonizerTarget(sourceSet) !is SharedCommonizerTarget) return + chooseVisibleSourceSets.flatMap(::materializeMetadata) + } + + private fun materializeMetadata( + chooseVisibleSourceSets: ChooseVisibleSourceSets + ): Set = when (chooseVisibleSourceSets.metadataProvider) { + /* Nothing to transform: We will use original commonizer output in such cases */ + is ProjectMetadataProvider -> emptySet() + + /* Extract/Materialize all cinterop files from composite jar file */ + is JarMetadataProvider -> + chooseVisibleSourceSets.visibleSourceSetsProvidingCInterops.flatMap { visibleSourceSetName -> + chooseVisibleSourceSets.metadataProvider.getSourceSetCInteropMetadata( + visibleSourceSetName, outputDirectory, materializeFiles = true + ) + }.toSet() + } + + private fun Configuration.withoutProjectDependencies(): FileCollection { + return incoming.artifactView { view -> + view.componentFilter { componentIdentifier -> + componentIdentifier !is ProjectComponentIdentifier + } + }.files + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/GetCommonizerTargetOfSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/GetCommonizerTargetOfSourceSet.kt index 24aa732ec98..caa8fb40846 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/GetCommonizerTargetOfSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/GetCommonizerTargetOfSourceSet.kt @@ -24,3 +24,7 @@ internal fun Project.getCommonizerTarget(sourceSet: KotlinSourceSet): Commonizer else -> SharedCommonizerTarget(compilationTargets.allLeaves()) } } + +internal fun Project.getSharedCommonizerTarget(sourceSet: KotlinSourceSet): SharedCommonizerTarget? { + return getCommonizerTarget(sourceSet) as? SharedCommonizerTarget +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt index a00f1464a08..275a8e32dd7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt @@ -6,11 +6,13 @@ package org.jetbrains.kotlin.gradle.utils import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider import java.io.File import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -91,3 +93,7 @@ internal fun Project.filesProvider( ): ConfigurableFileCollection { return project.files(project.provider(provider)).builtBy(*buildDependencies) } + +internal fun T.outputFilesProvider(provider: T.() -> Any): ConfigurableFileCollection { + return project.filesProvider(this) { provider() } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/zipUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/zipUtils.kt new file mode 100644 index 00000000000..0aaacfa4551 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/zipUtils.kt @@ -0,0 +1,49 @@ +/* + * 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.utils + +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +internal fun copyZipFilePartially(sourceZipFile: File, destinationZipFile: File, path: String) { + require(path.endsWith("/")) { "Expected path to end with '/', found '$path'" } + + ZipFile(sourceZipFile).use { zip -> + val entries = zip.entries().asSequence() + .filter { it.name.startsWith(path) } + .filter { !it.isDirectory }.toList() + + if (entries.isEmpty()) return + + ZipOutputStream(destinationZipFile.outputStream()).use { destinationZipOutputStream -> + entries.forEach { entry -> + // Drop the source set name from the entry path + val destinationEntry = ZipEntry(entry.name.substringAfter(path)) + + zip.getInputStream(entry).use { inputStream -> + destinationZipOutputStream.putNextEntry(destinationEntry) + inputStream.copyTo(destinationZipOutputStream) + destinationZipOutputStream.closeEntry() + } + } + } + } +} + +internal fun ZipFile.listDescendants(zipEntry: ZipEntry): Sequence { + require(zipEntry.isDirectory) + return entries().asSequence().filter { entry -> + entry.name != zipEntry.name && entry.name.startsWith(zipEntry.name) + } +} + +internal fun ZipFile.listChildren(zipEntry: ZipEntry): Sequence { + return listDescendants(zipEntry).filter { entry -> + entry.name.removePrefix(zipEntry.name).count { it == '/' } == 1 + } +}