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)
}.toSet()
},
hostSpecificSourceSets = emptySet(),
sourceSetBinaryLayout = sourceSetModuleDependencies.mapValues { SourceSetMetadataLayout.KLIB }
)
}
@@ -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
)
}
@@ -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
@@ -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)
@@ -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