If a source set is host-specific, publish it within the Native modules
Consider host-specific all source sets that participate in a compilation of a target that cannot be built on some of the hosts. Exclude those source sets from the `-metadata` artifact since the `metadata` target must publish in the same way from any host. Instead, add a metadata variant to those targets which use such a source set and pack the source set's klib into a similar metadata JAR as the artifact of those metadata variants.
This commit is contained in:
+1
@@ -376,6 +376,7 @@ class HierarchicalMppIT : BaseGradleIT() {
|
||||
ModuleDependencyIdentifier(it.first, it.second)
|
||||
}.toSet()
|
||||
},
|
||||
hostSpecificSourceSets = emptySet(),
|
||||
sourceSetBinaryLayout = sourceSetModuleDependencies.mapValues { SourceSetMetadataLayout.KLIB }
|
||||
)
|
||||
}
|
||||
|
||||
+20
-1
@@ -60,7 +60,10 @@ data class KotlinProjectStructureMetadata(
|
||||
val sourceSetModuleDependencies: Map<String, Set<ModuleDependencyIdentifier>>,
|
||||
|
||||
@Input
|
||||
val formatVersion: String = FORMAT_VERSION_0_2
|
||||
val hostSpecificSourceSets: Set<String>,
|
||||
|
||||
@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<String, Set<String>>()
|
||||
val sourceSetModuleDependencies = mutableMapOf<String, Set<ModuleDependencyIdentifier>>()
|
||||
val sourceSetBinaryLayout = mutableMapOf<String, SourceSetMetadataLayout>()
|
||||
val hostSpecificSourceSets = mutableSetOf<String>()
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
+6
-3
@@ -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<PublishArtifact>? = null
|
||||
private val overrideConfigurationArtifacts: Set<PublishArtifact>? = 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
|
||||
|
||||
+34
-29
@@ -179,10 +179,11 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
// Do this after all targets are configured by the user build script
|
||||
|
||||
val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project)
|
||||
val hostSpecificSourceSets: Set<KotlinSourceSet> = getHostSpecificSourceSets(project).toSet()
|
||||
|
||||
val sourceSetsWithMetadataCompilations: Map<KotlinSourceSet, AbstractKotlinCompilation<*>> =
|
||||
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<KotlinSourceSet> {
|
||||
val compilationsBySourceSet: Map<KotlinSourceSet, Set<KotlinCompilation<*>>> =
|
||||
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<GenerateProjectStructureMetadata> =
|
||||
project.registerTask("generateProjectStructureMetadata") { task ->
|
||||
task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getPublishedCommonSourceSets(project: Project): Set<KotlinSourceSet> {
|
||||
val compilationsBySourceSet: Map<KotlinSourceSet, Set<KotlinCompilation<*>>> =
|
||||
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<KotlinUsageContext, KotlinCompilation<*>> {
|
||||
val result = mutableMapOf<KotlinUsageContext, KotlinCompilation<*>>()
|
||||
|
||||
@@ -502,6 +506,7 @@ internal fun getPublishedPlatformCompilations(project: Project): Map<KotlinUsage
|
||||
.forEach { component ->
|
||||
component.usages
|
||||
.filterIsInstance<KotlinUsageContext>()
|
||||
.filter { it.includeIntoProjectStructureMetadata }
|
||||
.forEach { usage -> result[usage] = usage.compilation }
|
||||
}
|
||||
}
|
||||
@@ -509,7 +514,7 @@ internal fun getPublishedPlatformCompilations(project: Project): Map<KotlinUsage
|
||||
return result
|
||||
}
|
||||
|
||||
private fun Project.filesWithUnpackedArchives(from: FileCollection, extensions: Set<String>): FileCollection =
|
||||
internal fun Project.filesWithUnpackedArchives(from: FileCollection, extensions: Set<String>): FileCollection =
|
||||
project.files(project.provider {
|
||||
from.map {
|
||||
if (it.extension in extensions)
|
||||
|
||||
+101
-2
@@ -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<KotlinTargetComponent> 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<Jar>(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<KotlinSourceSet> {
|
||||
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<KotlinNativeCompilation>()
|
||||
val nativeEnabledOn = nativeCompilations.flatMapTo(mutableSetOf()) { compilation -> canBeBuiltOnHosts(compilation.konanTarget) }
|
||||
platformCompilations == nativeCompilations && allHosts != nativeEnabledOn
|
||||
}
|
||||
}
|
||||
|
||||
abstract class KotlinNativeTargetWithTests<T : KotlinNativeBinaryTestRun>(
|
||||
project: Project,
|
||||
konanTarget: KonanTarget
|
||||
|
||||
Reference in New Issue
Block a user