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:
Sergey Igushkin
2020-03-02 17:33:19 +03:00
parent d7007044c2
commit f30aada84a
5 changed files with 162 additions and 35 deletions
@@ -376,6 +376,7 @@ class HierarchicalMppIT : BaseGradleIT() {
ModuleDependencyIdentifier(it.first, it.second) ModuleDependencyIdentifier(it.first, it.second)
}.toSet() }.toSet()
}, },
hostSpecificSourceSets = emptySet(),
sourceSetBinaryLayout = sourceSetModuleDependencies.mapValues { SourceSetMetadataLayout.KLIB } sourceSetBinaryLayout = sourceSetModuleDependencies.mapValues { SourceSetMetadataLayout.KLIB }
) )
} }
@@ -60,7 +60,10 @@ data class KotlinProjectStructureMetadata(
val sourceSetModuleDependencies: Map<String, Set<ModuleDependencyIdentifier>>, val sourceSetModuleDependencies: Map<String, Set<ModuleDependencyIdentifier>>,
@Input @Input
val formatVersion: String = FORMAT_VERSION_0_2 val hostSpecificSourceSets: Set<String>,
@Input
val formatVersion: String = FORMAT_VERSION_0_3
) { ) {
@Suppress("UNUSED") // Gradle input @Suppress("UNUSED") // Gradle input
@get:Input @get:Input
@@ -69,7 +72,12 @@ data class KotlinProjectStructureMetadata(
companion object { companion object {
internal const val FORMAT_VERSION_0_1 = "0.1" internal const val FORMAT_VERSION_0_1 = "0.1"
// + binaryFormat (klib, metadata/jar)
internal const val FORMAT_VERSION_0_2 = "0.2" 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) ModuleDependencyIdentifier(it.group.orEmpty(), it.name)
}.toSet() }.toSet()
}, },
hostSpecificSourceSets = getHostSpecificSourceSets(project).map { it.name }.toSet(),
sourceSetBinaryLayout = sourceSetsWithMetadataCompilations.keys.associate { sourceSet -> sourceSetBinaryLayout = sourceSetsWithMetadataCompilations.keys.associate { sourceSet ->
sourceSet.name to SourceSetMetadataLayout.chooseForProducingProject(project) sourceSet.name to SourceSetMetadataLayout.chooseForProducingProject(project)
} }
@@ -145,6 +154,9 @@ internal fun KotlinProjectStructureMetadata.toXmlDocument(): Document {
sourceSetBinaryLayout[sourceSet]?.let { binaryLayout -> sourceSetBinaryLayout[sourceSet]?.let { binaryLayout ->
textNode("binaryLayout", binaryLayout.name) 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 sourceSetDependsOnRelation = mutableMapOf<String, Set<String>>()
val sourceSetModuleDependencies = mutableMapOf<String, Set<ModuleDependencyIdentifier>>() val sourceSetModuleDependencies = mutableMapOf<String, Set<ModuleDependencyIdentifier>>()
val sourceSetBinaryLayout = mutableMapOf<String, SourceSetMetadataLayout>() val sourceSetBinaryLayout = mutableMapOf<String, SourceSetMetadataLayout>()
val hostSpecificSourceSets = mutableSetOf<String>()
val sourceSetsNode = projectStructureNode.getElementsByTagName("sourceSets").item(0) ?: return null val sourceSetsNode = projectStructureNode.getElementsByTagName("sourceSets").item(0) ?: return null
@@ -194,6 +207,11 @@ internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProj
sourceSetBinaryLayout[sourceSetName] = binaryLayout sourceSetBinaryLayout[sourceSetName] = binaryLayout
} }
} }
"hostSpecific" -> {
if (node.textContent == "true") {
hostSpecificSourceSets.add(sourceSetName)
}
}
} }
} }
@@ -206,6 +224,7 @@ internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProj
sourceSetDependsOnRelation, sourceSetDependsOnRelation,
sourceSetBinaryLayout, sourceSetBinaryLayout,
sourceSetModuleDependencies, sourceSetModuleDependencies,
hostSpecificSourceSets,
formatVersion formatVersion
) )
} }
@@ -55,13 +55,16 @@ object NativeUsage {
interface KotlinUsageContext : UsageContext { interface KotlinUsageContext : UsageContext {
val compilation: KotlinCompilation<*> val compilation: KotlinCompilation<*>
val dependencyConfigurationName: String val dependencyConfigurationName: String
val includeIntoProjectStructureMetadata: Boolean
} }
class DefaultKotlinUsageContext( class DefaultKotlinUsageContext(
override val compilation: KotlinCompilation<*>, override val compilation: KotlinCompilation<*>,
private val usage: Usage, private val usage: Usage,
override val dependencyConfigurationName: String, 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 { ) : KotlinUsageContext {
private val kotlinTarget: KotlinTarget get() = compilation.target private val kotlinTarget: KotlinTarget get() = compilation.target
@@ -72,7 +75,7 @@ class DefaultKotlinUsageContext(
override fun getName(): String = kotlinTarget.targetName + when (dependencyConfigurationName) { override fun getName(): String = kotlinTarget.targetName + when (dependencyConfigurationName) {
kotlinTarget.apiElementsConfigurationName -> "-api" kotlinTarget.apiElementsConfigurationName -> "-api"
kotlinTarget.runtimeElementsConfigurationName -> "-runtime" kotlinTarget.runtimeElementsConfigurationName -> "-runtime"
else -> "-$dependencyConfigurationName" // for Android variants else -> "-$dependencyConfigurationName"
} }
private val configuration: Configuration private val configuration: Configuration
@@ -90,7 +93,7 @@ class DefaultKotlinUsageContext(
configuration.artifacts configuration.artifacts
override fun getAttributes(): AttributeContainer { 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] /** 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 * being rejected by Gradle 5.2+; we may need to either not filter the attributes, which will lead to
@@ -179,10 +179,11 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
// Do this after all targets are configured by the user build script // Do this after all targets are configured by the user build script
val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project) val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project)
val hostSpecificSourceSets: Set<KotlinSourceSet> = getHostSpecificSourceSets(project).toSet()
val sourceSetsWithMetadataCompilations: Map<KotlinSourceSet, AbstractKotlinCompilation<*>> = val sourceSetsWithMetadataCompilations: Map<KotlinSourceSet, AbstractKotlinCompilation<*>> =
publishedCommonSourceSets.associate { sourceSet -> publishedCommonSourceSets.associate { sourceSet ->
sourceSet to createMetadataCompilation(target, sourceSet, allMetadataJar) sourceSet to createMetadataCompilation(target, sourceSet, allMetadataJar, sourceSet in hostSpecificSourceSets)
} }
if (project.isCompatibilityMetadataVariantEnabled) { if (project.isCompatibilityMetadataVariantEnabled) {
@@ -250,7 +251,8 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
private fun createMetadataCompilation( private fun createMetadataCompilation(
target: KotlinMetadataTarget, target: KotlinMetadataTarget,
sourceSet: KotlinSourceSet, sourceSet: KotlinSourceSet,
allMetadataJar: Jar allMetadataJar: Jar,
isHostSpecific: Boolean
): AbstractKotlinCompilation<*> { ): AbstractKotlinCompilation<*> {
val project = target.project val project = target.project
@@ -270,8 +272,10 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
configureMetadataDependenciesForCompilation(this@apply) configureMetadataDependenciesForCompilation(this@apply)
val metadataContent = project.filesWithUnpackedArchives(this@apply.output.allOutputs, setOf("klib")) if (!isHostSpecific) {
allMetadataJar.from(metadataContent) { spec -> spec.into(this@apply.defaultSourceSet.name) } 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> = private fun Project.createGenerateProjectStructureMetadataTask(): TaskProvider<GenerateProjectStructureMetadata> =
project.registerTask("generateProjectStructureMetadata") { task -> project.registerTask("generateProjectStructureMetadata") { task ->
task.lazyKotlinProjectStructureMetadata = lazy { checkNotNull(buildKotlinProjectStructureMetadata(project)) } 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<*>> { internal fun getPublishedPlatformCompilations(project: Project): Map<KotlinUsageContext, KotlinCompilation<*>> {
val result = mutableMapOf<KotlinUsageContext, KotlinCompilation<*>>() val result = mutableMapOf<KotlinUsageContext, KotlinCompilation<*>>()
@@ -502,6 +506,7 @@ internal fun getPublishedPlatformCompilations(project: Project): Map<KotlinUsage
.forEach { component -> .forEach { component ->
component.usages component.usages
.filterIsInstance<KotlinUsageContext>() .filterIsInstance<KotlinUsageContext>()
.filter { it.includeIntoProjectStructureMetadata }
.forEach { usage -> result[usage] = usage.compilation } .forEach { usage -> result[usage] = usage.compilation }
} }
} }
@@ -509,7 +514,7 @@ internal fun getPublishedPlatformCompilations(project: Project): Map<KotlinUsage
return result 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 { project.files(project.provider {
from.map { from.map {
if (it.extension in extensions) if (it.extension in extensions)
@@ -8,15 +8,26 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.Attribute 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.gradle.util.WrapUtil
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTests 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.KotlinNativeBinaryTestRun
import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeHostTestRun import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeHostTestRun
import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeSimulatorTestRun import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeSimulatorTestRun
import org.jetbrains.kotlin.gradle.targets.native.NativeBinaryTestRunSource 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 org.jetbrains.kotlin.konan.target.KonanTarget
import javax.inject.Inject import javax.inject.Inject
@@ -32,6 +43,74 @@ open class KotlinNativeTarget @Inject constructor(
attributes.attribute(konanTargetAttribute, konanTarget.name) 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 = override val binaries =
// Use newInstance to allow accessing binaries by their names in Groovy using the extension mechanism. // 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)) 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>( abstract class KotlinNativeTargetWithTests<T : KotlinNativeBinaryTestRun>(
project: Project, project: Project,
konanTarget: KonanTarget konanTarget: KonanTarget