diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt index 3841c0700e0..1ff0a66a6e1 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt @@ -376,6 +376,7 @@ class HierarchicalMppIT : BaseGradleIT() { ModuleDependencyIdentifier(it.first, it.second) }.toSet() }, + hostSpecificSourceSets = emptySet(), sourceSetBinaryLayout = sourceSetModuleDependencies.mapValues { SourceSetMetadataLayout.KLIB } ) } 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 28327ea60c0..11802444d74 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 @@ -60,7 +60,10 @@ data class KotlinProjectStructureMetadata( val sourceSetModuleDependencies: Map>, @Input - val formatVersion: String = FORMAT_VERSION_0_2 + val hostSpecificSourceSets: Set, + + @Input + val formatVersion: String = FORMAT_VERSION_0_3 ) { @Suppress("UNUSED") // Gradle input @get:Input @@ -69,7 +72,12 @@ data class KotlinProjectStructureMetadata( companion object { internal const val FORMAT_VERSION_0_1 = "0.1" + + // + binaryFormat (klib, metadata/jar) internal const val FORMAT_VERSION_0_2 = "0.2" + + // + 'hostSpecific' flag for source sets + internal const val FORMAT_VERSION_0_3 = "0.3" } } @@ -107,6 +115,7 @@ internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjec ModuleDependencyIdentifier(it.group.orEmpty(), it.name) }.toSet() }, + hostSpecificSourceSets = getHostSpecificSourceSets(project).map { it.name }.toSet(), sourceSetBinaryLayout = sourceSetsWithMetadataCompilations.keys.associate { sourceSet -> sourceSet.name to SourceSetMetadataLayout.chooseForProducingProject(project) } @@ -145,6 +154,9 @@ internal fun KotlinProjectStructureMetadata.toXmlDocument(): Document { sourceSetBinaryLayout[sourceSet]?.let { binaryLayout -> textNode("binaryLayout", binaryLayout.name) } + if (sourceSet in hostSpecificSourceSets) { + textNode("hostSpecific", "true") + } } } } @@ -173,6 +185,7 @@ internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProj val sourceSetDependsOnRelation = mutableMapOf>() val sourceSetModuleDependencies = mutableMapOf>() val sourceSetBinaryLayout = mutableMapOf() + val hostSpecificSourceSets = mutableSetOf() val sourceSetsNode = projectStructureNode.getElementsByTagName("sourceSets").item(0) ?: return null @@ -194,6 +207,11 @@ internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProj sourceSetBinaryLayout[sourceSetName] = binaryLayout } } + "hostSpecific" -> { + if (node.textContent == "true") { + hostSpecificSourceSets.add(sourceSetName) + } + } } } @@ -206,6 +224,7 @@ internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProj sourceSetDependsOnRelation, sourceSetBinaryLayout, sourceSetModuleDependencies, + hostSpecificSourceSets, formatVersion ) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt index ccb74a56ee7..48c3b039a41 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -55,13 +55,16 @@ object NativeUsage { interface KotlinUsageContext : UsageContext { val compilation: KotlinCompilation<*> val dependencyConfigurationName: String + val includeIntoProjectStructureMetadata: Boolean } class DefaultKotlinUsageContext( override val compilation: KotlinCompilation<*>, private val usage: Usage, override val dependencyConfigurationName: String, - private val overrideConfigurationArtifacts: Set? = null + private val overrideConfigurationArtifacts: Set? = null, + private val overrideConfigurationAttributes: AttributeContainer? = null, + override val includeIntoProjectStructureMetadata: Boolean = true ) : KotlinUsageContext { private val kotlinTarget: KotlinTarget get() = compilation.target @@ -72,7 +75,7 @@ class DefaultKotlinUsageContext( override fun getName(): String = kotlinTarget.targetName + when (dependencyConfigurationName) { kotlinTarget.apiElementsConfigurationName -> "-api" kotlinTarget.runtimeElementsConfigurationName -> "-runtime" - else -> "-$dependencyConfigurationName" // for Android variants + else -> "-$dependencyConfigurationName" } private val configuration: Configuration @@ -90,7 +93,7 @@ class DefaultKotlinUsageContext( configuration.artifacts override fun getAttributes(): AttributeContainer { - val configurationAttributes = configuration.attributes + val configurationAttributes = overrideConfigurationAttributes ?: configuration.attributes /** TODO Using attributes of a detached configuration is a small and 'conservative' fix for KT-29758, [HierarchyAttributeContainer] * being rejected by Gradle 5.2+; we may need to either not filter the attributes, which will lead to 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 c3f8c2fad01..c9197c9ebf4 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 @@ -179,10 +179,11 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : // Do this after all targets are configured by the user build script val publishedCommonSourceSets: Set = getPublishedCommonSourceSets(project) + val hostSpecificSourceSets: Set = getHostSpecificSourceSets(project).toSet() val sourceSetsWithMetadataCompilations: Map> = publishedCommonSourceSets.associate { sourceSet -> - sourceSet to createMetadataCompilation(target, sourceSet, allMetadataJar) + sourceSet to createMetadataCompilation(target, sourceSet, allMetadataJar, sourceSet in hostSpecificSourceSets) } if (project.isCompatibilityMetadataVariantEnabled) { @@ -250,7 +251,8 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : private fun createMetadataCompilation( target: KotlinMetadataTarget, sourceSet: KotlinSourceSet, - allMetadataJar: Jar + allMetadataJar: Jar, + isHostSpecific: Boolean ): AbstractKotlinCompilation<*> { val project = target.project @@ -270,8 +272,10 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : configureMetadataDependenciesForCompilation(this@apply) - val metadataContent = project.filesWithUnpackedArchives(this@apply.output.allOutputs, setOf("klib")) - allMetadataJar.from(metadataContent) { spec -> spec.into(this@apply.defaultSourceSet.name) } + if (!isHostSpecific) { + val metadataContent = project.filesWithUnpackedArchives(this@apply.output.allOutputs, setOf("klib")) + allMetadataJar.from(metadataContent) { spec -> spec.into(this@apply.defaultSourceSet.name) } + } } } @@ -460,36 +464,36 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : } } - private fun getPublishedCommonSourceSets(project: Project): Set { - val compilationsBySourceSet: Map>> = - CompilationSourceSetUtil.compilationsBySourceSets(project) - - val sourceSetsUsedInMultipleTargets = compilationsBySourceSet.filterValues { compilations -> - compilations.map { it.target.platformType }.distinct().run { - size > 1 && toSet() != setOf(KotlinPlatformType.androidJvm, KotlinPlatformType.jvm) || ( - singleOrNull() == KotlinPlatformType.native && - compilations.map { it.target }.distinct().size > 1 && - compilations.any { (it as AbstractKotlinNativeCompilation).konanTarget.enabledOnCurrentHost } - ) - // TODO: platform-shared source sets other than Kotlin/Native ones are not yet supported; support will be needed for JVM, JS - } - } - - // We don't want to publish source set metadata from source sets that don't participate in any compilation that is published, - // such as test or benchmark sources; find all published compilations: - val publishedCompilations = getPublishedPlatformCompilations(project).values - - return sourceSetsUsedInMultipleTargets - .filterValues { compilations -> compilations.any { it in publishedCompilations } } - .keys - } - private fun Project.createGenerateProjectStructureMetadataTask(): TaskProvider = project.registerTask("generateProjectStructureMetadata") { task -> task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) } } } +internal fun getPublishedCommonSourceSets(project: Project): Set { + val compilationsBySourceSet: Map>> = + CompilationSourceSetUtil.compilationsBySourceSets(project) + + val sourceSetsUsedInMultipleTargets = compilationsBySourceSet.filterValues { compilations -> + compilations.map { it.target.platformType }.distinct().run { + size > 1 && toSet() != setOf(KotlinPlatformType.androidJvm, KotlinPlatformType.jvm) || ( + singleOrNull() == KotlinPlatformType.native && + compilations.map { it.target }.distinct().size > 1 && + compilations.any { (it as AbstractKotlinNativeCompilation).konanTarget.enabledOnCurrentHost } + ) + // TODO: platform-shared source sets other than Kotlin/Native ones are not yet supported; support will be needed for JVM, JS + } + } + + // We don't want to publish source set metadata from source sets that don't participate in any compilation that is published, + // such as test or benchmark sources; find all published compilations: + val publishedCompilations = getPublishedPlatformCompilations(project).values + + return sourceSetsUsedInMultipleTargets + .filterValues { compilations -> compilations.any { it in publishedCompilations } } + .keys +} + internal fun getPublishedPlatformCompilations(project: Project): Map> { val result = mutableMapOf>() @@ -502,6 +506,7 @@ internal fun getPublishedPlatformCompilations(project: Project): Map component.usages .filterIsInstance() + .filter { it.includeIntoProjectStructureMetadata } .forEach { usage -> result[usage] = usage.compilation } } } @@ -509,7 +514,7 @@ internal fun getPublishedPlatformCompilations(project: Project): Map): FileCollection = +internal fun Project.filesWithUnpackedArchives(from: FileCollection, extensions: Set): FileCollection = project.files(project.provider { from.map { if (it.extension in extensions) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt index 7631f6dc956..c363a80e3dc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt @@ -8,15 +8,26 @@ package org.jetbrains.kotlin.gradle.plugin.mpp import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.Usage +import org.gradle.jvm.tasks.Jar import org.gradle.util.WrapUtil import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer -import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTests +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.targets.metadata.filesWithUnpackedArchives +import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedCommonSourceSets +import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeBinaryTestRun import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeHostTestRun import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeSimulatorTestRun import org.jetbrains.kotlin.gradle.targets.native.NativeBinaryTestRunSource +import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask +import org.jetbrains.kotlin.gradle.utils.setArchiveAppendixCompatible +import org.jetbrains.kotlin.gradle.utils.setArchiveClassifierCompatible +import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget import javax.inject.Inject @@ -32,6 +43,74 @@ open class KotlinNativeTarget @Inject constructor( attributes.attribute(konanTargetAttribute, konanTarget.name) } + private val hostSpecificMetadataJarTaskName get() = disambiguateName("MetadataJar") + + private val hostSpecificMetadataElementsConfigurationName get() = disambiguateName("MetadataElements") + + override val kotlinComponents: Set by lazy { + if (!project.isKotlinGranularMetadataEnabled) + return@lazy super.kotlinComponents + + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + + // NB: another usage context for the host-specific metadata may be added to this set below + val mutableUsageContexts = createUsageContexts(mainCompilation).toMutableSet() + + project.whenEvaluated { + val hostSpecificSourceSets = getHostSpecificSourceSets(project) + .intersect(mainCompilation.allKotlinSourceSets) + + if (hostSpecificSourceSets.isNotEmpty()) { + val hostSpecificMetadataJar = project.locateOrRegisterTask(hostSpecificMetadataJarTaskName) { + it.setArchiveAppendixCompatible { disambiguationClassifier.orEmpty().toLowerCase() } + it.setArchiveClassifierCompatible { "metadata" } + } + project.artifacts.add(Dependency.ARCHIVES_CONFIGURATION, hostSpecificMetadataJar) + + hostSpecificMetadataJar.configure { metadataJar -> + metadataJar.onlyIf { this@KotlinNativeTarget.publishable } + + val metadataCompilations = hostSpecificSourceSets.mapNotNull { + project.multiplatformExtension.metadata().compilations.findByName(it.name) + } + + metadataCompilations.forEach { + metadataJar.from(project.filesWithUnpackedArchives(it.output.allOutputs, setOf("klib"))) { spec -> + spec.into(it.name) + } + metadataJar.dependsOn(it.output.classesDirs) + } + } + + val metadataConfiguration = project.configurations.findByName(hostSpecificMetadataElementsConfigurationName) + ?: project.configurations.create(hostSpecificMetadataElementsConfigurationName) { configuration -> + configuration.isCanBeConsumed = false + configuration.isCanBeResolved = false + project.artifacts.add(configuration.name, hostSpecificMetadataJar) { artifact -> + artifact.classifier = "metadata" + } + } + + val metadataAttributes = + HierarchyAttributeContainer(project.configurations.getByName(apiElementsConfigurationName).attributes).apply { + attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) + } + + mutableUsageContexts.add( + DefaultKotlinUsageContext( + mainCompilation, + project.usageByName(javaApiUsageForMavenScoping()), + metadataConfiguration.name, + overrideConfigurationAttributes = metadataAttributes, + includeIntoProjectStructureMetadata = false + ) + ) + } + } + + setOf(createKotlinVariant(targetName, mainCompilation, mutableUsageContexts)) + } + override val binaries = // Use newInstance to allow accessing binaries by their names in Groovy using the extension mechanism. project.objects.newInstance(KotlinNativeBinaryContainer::class.java, this, WrapUtil.toDomainObjectSet(NativeBinary::class.java)) @@ -59,6 +138,26 @@ open class KotlinNativeTarget @Inject constructor( } } +internal fun getHostSpecificSourceSets(project: Project): List { + val compilationsBySourceSet = CompilationSourceSetUtil.compilationsBySourceSets(project) + + val enabledByHost = HostManager().enabledByHost + val allHosts = enabledByHost.keys + + fun canBeBuiltOnHosts(konanTarget: KonanTarget) = enabledByHost.filterValues { konanTarget in it }.keys + + return project.kotlinExtension.sourceSets.filter { sourceSet -> + // If the source set participates in compilations such that some host can't run either of them, then on that host, + // we can't analyze the source set, so the source set's metadata can't be published from that host, and therefore + // we consider it platform-specific and publish as a part of the Native targets where the source set takes part, + // not the common metadata artifact; + val platformCompilations = compilationsBySourceSet.getValue(sourceSet).filter { it !is KotlinMetadataCompilation } + val nativeCompilations = platformCompilations.filterIsInstance() + val nativeEnabledOn = nativeCompilations.flatMapTo(mutableSetOf()) { compilation -> canBeBuiltOnHosts(compilation.konanTarget) } + platformCompilations == nativeCompilations && allHosts != nativeEnabledOn + } +} + abstract class KotlinNativeTargetWithTests( project: Project, konanTarget: KonanTarget