diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTarget.kt index a5ffb3c19ef..8c493f52ff5 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTarget.kt @@ -13,7 +13,6 @@ import org.gradle.api.Project import org.gradle.api.attributes.AttributeContainer import org.gradle.api.attributes.HasAttributes import org.gradle.api.component.SoftwareComponent -import org.gradle.api.internal.component.UsageContext import org.gradle.api.publish.maven.MavenPublication import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions @@ -21,6 +20,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions interface KotlinTargetComponent : SoftwareComponent { val target: KotlinTarget val publishable: Boolean + val defaultArtifactId: String } interface KotlinTarget : Named, HasAttributes { @@ -41,9 +41,7 @@ interface KotlinTarget : Named, HasAttributes { val publishable: Boolean - val component: KotlinTargetComponent - - fun createUsageContexts(): Set + val components: Set fun mavenPublication(action: Closure) fun mavenPublication(action: Action) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index e11cc460645..e3361fa0718 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -1114,7 +1114,7 @@ class NewMultiplatformIT : BaseGradleIT() { assertTasksExecuted(":publishedLibrary:cinteropStdio${host.capitalize()}") assertTrue(output.contains("Published test"), "No test output found") assertFileExists("publishedLibrary/build/classes/kotlin/$host/main/publishedLibrary-cinterop-stdio.klib") - assertFileExists("repo/org/example/publishedLibrary-$host/1.0/publishedLibrary-$host-1.0-cinterop-stdio.klib") + assertFileExists("repo/org/example/publishedlibrary-$host/1.0/publishedlibrary-$host-1.0-cinterop-stdio.klib") } build(":build") { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 6d012474152..50895027e92 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -14,6 +14,7 @@ import org.gradle.api.internal.FeaturePreviews import org.gradle.api.internal.file.FileResolver import org.gradle.api.internal.plugins.DslObject import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.publish.PublicationContainer import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal @@ -155,12 +156,13 @@ class KotlinMultiplatformPlugin( } val targets = project.multiplatformExtension!!.targets - val kotlinSoftwareComponent = KotlinSoftwareComponent(project, "kotlin", targets) + val kotlinSoftwareComponent = KotlinSoftwareComponent("kotlin", targets) project.extensions.configure(PublishingExtension::class.java) { publishing -> // The root publication that references the platform specific publications as its variants: val rootPublication = publishing.publications.create("kotlinMultiplatform", MavenPublication::class.java).apply { + artifactId = project.name.toLowerCase() from(kotlinSoftwareComponent) (this as MavenPublicationInternal).publishWithOriginalFileName() } @@ -170,36 +172,34 @@ class KotlinMultiplatformPlugin( publishTask.onlyIf { publishTask.publication != rootPublication || project.multiplatformExtension!!.isGradleMetadataAvailable } } - fun createTargetPublication(target: KotlinTarget) { - val variant = target.component as KotlinVariant - val name = target.name + // Enforce the order of creating the publications, since the metadata publication is used in the other publications: + (targets.getByName(METADATA_TARGET_NAME) as AbstractKotlinTarget).createMavenPublications(publishing.publications) + targets + .withType(AbstractKotlinTarget::class.java).matching { it.publishable && it.name != METADATA_TARGET_NAME } + .all { it.createMavenPublications(publishing.publications) } + } - val variantPublication = publishing.publications.create(name, MavenPublication::class.java).apply { + project.components.add(kotlinSoftwareComponent) + } + + private fun AbstractKotlinTarget.createMavenPublications(publications: PublicationContainer) { + components + .filter { it.publishable } + .forEach { variant -> + val variantPublication = publications.create(variant.name, MavenPublication::class.java).apply { // do this in whenEvaluated since older Gradle versions seem to check the files in the variant eagerly: project.whenEvaluated { from(variant) - (project.tasks.findByName(target.sourcesJarTaskName) as Jar?)?.let { sourcesJar -> + (project.tasks.findByName(sourcesJarTaskName) as Jar?)?.let { sourcesJar -> artifact(sourcesJar) } } (this as MavenPublicationInternal).publishWithOriginalFileName() - artifactId = variant.target.defaultArtifactId + artifactId = variant.defaultArtifactId } - variant.publicationDelegate = variantPublication - (target as AbstractKotlinTarget).publicationConfigureActions.all { - it.execute(variantPublication) - } - } - - // Enforce the order of creating the publications, since the metadata publication is used in the other publications: - createTargetPublication(targets.getByName(METADATA_TARGET_NAME)) - targets.matching { it.publishable && it.name != METADATA_TARGET_NAME }.all(::createTargetPublication) - } - - project.components.add(kotlinSoftwareComponent) - targets.all { - project.components.add(it.component) + (variant as? KotlinTargetComponentWithPublication)?.publicationDelegate = variantPublication + publicationConfigureActions.all { it.execute(variantPublication) } } } @@ -293,8 +293,6 @@ class KotlinMultiplatformPlugin( } } -internal val KotlinTarget.defaultArtifactId get() = "${project.name}-${name.toLowerCase()}" - internal fun compilationsBySourceSet(project: Project): Map>> = HashMap>>().also { result -> for (target in project.multiplatformExtension!!.targets) { 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 fbb9acc1420..c39c9e56fba 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 @@ -17,7 +17,6 @@ import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.utils.ifEmpty class KotlinSoftwareComponent( - private val project: Project, private val name: String, private val kotlinTargets: Iterable ) : SoftwareComponentInternal, ComponentWithVariants { @@ -25,7 +24,7 @@ class KotlinSoftwareComponent( override fun getUsages(): Set = emptySet() override fun getVariants(): Set = - kotlinTargets.map { it.component }.toSet() + kotlinTargets.flatMap { it.components }.toSet() override fun getName(): String = name } @@ -36,12 +35,20 @@ object NativeUsage { const val KOTLIN_KLIB = "kotlin-klib" } -internal class KotlinPlatformUsageContext( - val kotlinTarget: KotlinTarget, +interface KotlinUsageContext : UsageContext { + val compilation: KotlinCompilation<*> + val dependencyConfigurationName: String +} + +class DefaultKotlinUsageContext( + override val compilation: KotlinCompilation<*>, private val usage: Usage, - val dependencyConfigurationName: String, - private val publishWithGradleMetadata: Boolean -) : UsageContext { + override val dependencyConfigurationName: String, + private val publishWithGradleMetadata: Boolean, + private val overrideConfigurationArtifacts: Set? = null +) : KotlinUsageContext { + + val kotlinTarget: KotlinTarget get() = compilation.target private val project: Project get() = kotlinTarget.project override fun getUsage(): Usage = usage @@ -49,7 +56,7 @@ internal class KotlinPlatformUsageContext( override fun getName(): String = kotlinTarget.targetName + when (dependencyConfigurationName) { kotlinTarget.apiElementsConfigurationName -> "-api" kotlinTarget.runtimeElementsConfigurationName -> "-runtime" - else -> error("unexpected configuration") + else -> "-$dependencyConfigurationName" // for Android variants } private val configuration: Configuration @@ -64,7 +71,8 @@ internal class KotlinPlatformUsageContext( override fun getDependencyConstraints(): MutableSet = configuration.incoming.dependencyConstraints - override fun getArtifacts(): MutableSet = + override fun getArtifacts(): Set = + overrideConfigurationArtifacts ?: // TODO Gradle Java plugin does that in a different way; check whether we can improve this configuration.artifacts @@ -77,7 +85,7 @@ internal class KotlinPlatformUsageContext( } private fun rewriteMppDependenciesToTargetModuleDependencies( - context: KotlinPlatformUsageContext, + context: DefaultKotlinUsageContext, configuration: Configuration ): Set = with(context.kotlinTarget.project) { val target = context.kotlinTarget @@ -126,12 +134,13 @@ private fun rewriteMppDependenciesToTargetModuleDependencies( ) } ?: return@map dependency - val publicationDelegate = (dependencyTarget.component as KotlinVariant).publicationDelegate + val dependencyTargetComponent = dependencyTarget.components.single() // FIXME handle multiple components later + val publicationDelegate = (dependencyTargetComponent as KotlinVariant).publicationDelegate dependencies.module( listOf( publicationDelegate?.groupId ?: dependency.group, - publicationDelegate?.artifactId ?: dependencyTarget.defaultArtifactId, + publicationDelegate?.artifactId ?: dependencyTargetComponent.defaultArtifactId, publicationDelegate?.version ?: dependency.version ).joinToString(":") ) as ModuleDependency diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt index 412d796d849..21210c355ca 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt @@ -14,7 +14,6 @@ import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.AttributeContainer import org.gradle.api.attributes.Usage.JAVA_API import org.gradle.api.attributes.Usage.JAVA_RUNTIME_JARS -import org.gradle.api.internal.component.UsageContext import org.gradle.api.plugins.JavaPlugin import org.gradle.api.publish.maven.MavenPublication import org.gradle.util.ConfigureUtil @@ -52,52 +51,67 @@ abstract class AbstractKotlinTarget( override val publishable: Boolean get() = true - override val component: KotlinTargetComponent by lazy { - if (isGradleVersionAtLeast(4, 7)) { - createKotlinTargetComponent() - } else { - KotlinVariant(this) - } + override val components: Set by lazy { + val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) + val usageContexts = createUsageContexts(mainCompilation, targetName, targetName.toLowerCase()) + setOf( + if (isGradleVersionAtLeast(4, 7)) { + createKotlinVariant(mainCompilation.target.name, mainCompilation, usageContexts) + } else { + KotlinVariant(mainCompilation, usageContexts) + } + ).also { project.components.addAll(it) } } - private fun createKotlinTargetComponent(): KotlinTargetComponent { + protected fun createKotlinVariant( + componentName: String, + compilation: KotlinCompilation<*>, + usageContexts: Set + ): KotlinVariant { + val kotlinExtension = project.kotlinExtension as? KotlinMultiplatformExtension - ?: return KotlinVariantWithCoordinates(this) + ?: return KotlinVariantWithCoordinates(compilation, usageContexts) if (targetName == KotlinMultiplatformPlugin.METADATA_TARGET_NAME) - return KotlinVariantWithCoordinates(this) + return KotlinVariantWithCoordinates(compilation, usageContexts) val separateMetadataTarget = kotlinExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME) - return if (kotlinExtension.isGradleMetadataAvailable) { - KotlinVariantWithMetadataVariant(this, separateMetadataTarget) + val result = if (kotlinExtension.isGradleMetadataAvailable) { + KotlinVariantWithMetadataVariant(compilation, usageContexts, separateMetadataTarget) } else { // we should only add the Kotlin metadata dependency if we publish no Gradle metadata related to Kotlin MPP; // with metadata, such a dependency would get invalid, since a platform module should only depend on modules for that // same platform, not Kotlin metadata modules - KotlinVariantWithMetadataDependency(this, separateMetadataTarget) + KotlinVariantWithMetadataDependency(compilation, usageContexts, separateMetadataTarget) } + + result.componentName = componentName + return result } - override fun createUsageContexts(): Set { + private fun createUsageContexts( + producingCompilation: KotlinCompilation<*>, + componentName: String, + artifactNameAppendix: String + ): Set { val publishWithKotlinMetadata = (project.kotlinExtension as? KotlinMultiplatformExtension)?.isGradleMetadataAvailable ?: true // In non-MPP project, these usage contexts do not get published anyway // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for - // ordering of the usage contexts (prioritizing the dependencies); + // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM; // These Java usages should not be replaced with the custom Kotlin usages. return listOfNotNull( - KotlinPlatformUsageContext( - this, project.usageByName(JAVA_API), - apiElementsConfigurationName, publishWithKotlinMetadata - ), - if (compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) is KotlinCompilationToRunnableFiles) { - KotlinPlatformUsageContext( - this, project.usageByName(JAVA_RUNTIME_JARS), - runtimeElementsConfigurationName, publishWithKotlinMetadata + JAVA_API to apiElementsConfigurationName, + (JAVA_RUNTIME_JARS to runtimeElementsConfigurationName).takeIf { producingCompilation is KotlinCompilationToRunnableFiles } + ).mapTo(mutableSetOf()) { (usageName, dependenciesConfigurationName) -> + DefaultKotlinUsageContext( + producingCompilation, + project.usageByName(usageName), + dependenciesConfigurationName, + publishWithKotlinMetadata ) - } else null - ).toSet() + } } @Suppress("UNCHECKED_CAST") @@ -134,10 +148,8 @@ open class KotlinAndroidTarget( override val compilations: NamedDomainObjectContainer = project.container(compilationFactory.itemClass, compilationFactory) - override fun createUsageContexts(): Set { - //TODO setup Android libraries publishing. This will likely require new API in the Android Gradle plugin - return emptySet() - } + override val components: Set + get() = emptySet() } open class KotlinWithJavaTarget( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt index 603459c3abd..bd3b4814097 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt @@ -14,27 +14,22 @@ import org.gradle.api.component.ComponentWithVariants import org.gradle.api.internal.component.SoftwareComponentInternal import org.gradle.api.internal.component.UsageContext import org.gradle.api.publish.maven.MavenPublication +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.gradle.utils.lowerSpinalCaseName -open class KotlinVariant( - override val target: KotlinTarget -) : SoftwareComponentInternal, KotlinTargetComponent { - override fun getUsages(): Set = target.createUsageContexts() - override fun getName(): String = target.name - - // This property is declared in the parent class to allow usages to reference it without forcing the subclass to load, +internal interface KotlinTargetComponentWithPublication : KotlinTargetComponent { + // This property is declared in the separate parent type to allow the usages to reference it without forcing the subtypes to load, // which is needed for compatibility with older Gradle versions - internal var publicationDelegate: MavenPublication? = null - - override val publishable: Boolean - get() = target.publishable + var publicationDelegate: MavenPublication? } -open class KotlinVariantWithCoordinates( - target: KotlinTarget -) : KotlinVariant(target), - ComponentWithCoordinates /* Gradle 4.7+ API, don't use with older versions */ { +private interface KotlinTargetComponentWithCoordinatesAndPublication : + KotlinTargetComponentWithPublication, + ComponentWithCoordinates /* Gradle 4.7+ API, don't use with older versions */ +{ override fun getCoordinates() = object : ModuleVersionIdentifier { private val project get() = target.project @@ -57,29 +52,65 @@ open class KotlinVariantWithCoordinates( } } -class KotlinVariantWithMetadataVariant(target: KotlinTarget, private val metadataTarget: KotlinTarget) : - KotlinVariantWithCoordinates(target), ComponentWithVariants { - override fun getVariants() = setOf(metadataTarget.component) +open class KotlinVariant( + val producingCompilation: KotlinCompilation<*>, + private val usages: Set +) : KotlinTargetComponentWithPublication, SoftwareComponentInternal { + var componentName: String? = null + + final override val target: KotlinTarget + get() = producingCompilation.target + + override fun getUsages(): Set = usages + + override fun getName(): String = componentName ?: producingCompilation.target.targetName + + override val publishable: Boolean + get() = target.publishable + + internal var defaultArtifactIdSuffix: String? = null + + override val defaultArtifactId: String + get() = lowerSpinalCaseName(target.project.name, target.targetName, defaultArtifactIdSuffix) + + override var publicationDelegate: MavenPublication? = null } -class KotlinVariantWithMetadataDependency(target: KotlinTarget, private val metadataTarget: KotlinTarget) : - KotlinVariantWithCoordinates(target) { - override fun getUsages(): Set = target.createUsageContexts().mapTo(mutableSetOf()) { usageContext -> - UsageContextWithAdditionalDependencies(usageContext, setOf(metadataDependency())) +open class KotlinVariantWithCoordinates( + producingCompilation: KotlinCompilation<*>, + usages: Set +) : KotlinVariant(producingCompilation, usages), + KotlinTargetComponentWithCoordinatesAndPublication /* Gradle 4.7+ API, don't use with older versions */ + +class KotlinVariantWithMetadataVariant( + producingCompilation: KotlinCompilation<*>, + usages: Set, + private val metadataTarget: KotlinTarget +) : KotlinVariantWithCoordinates(producingCompilation, usages), ComponentWithVariants { + override fun getVariants() = metadataTarget.components +} + +class KotlinVariantWithMetadataDependency( + producingCompilation: KotlinCompilation<*>, + val originalUsages: Set, + private val metadataTarget: KotlinTarget +) : KotlinVariantWithCoordinates(producingCompilation, originalUsages) { + override fun getUsages(): Set = originalUsages.mapTo(mutableSetOf()) { usageContext -> + KotlinUsageContextWithAdditionalDependencies(usageContext, setOf(metadataDependency())) } private fun metadataDependency(): ModuleDependency { - val metadataPublication = (metadataTarget.component as KotlinVariant).publicationDelegate!! + val metadataPublication = (metadataTarget.components.single() as KotlinTargetComponentWithPublication).publicationDelegate!! val metadataGroupId = metadataPublication.groupId val metadataArtifactId = metadataPublication.artifactId val metadataVersion = metadataPublication.version return target.project.dependencies.module("$metadataGroupId:$metadataArtifactId:$metadataVersion") as ModuleDependency } - private class UsageContextWithAdditionalDependencies( - val parentUsageContext: UsageContext, + class KotlinUsageContextWithAdditionalDependencies( + val parentUsageContext: DefaultKotlinUsageContext, val additionalDependencies: Set - ) : UsageContext by parentUsageContext { + ) : KotlinUsageContext by parentUsageContext { override fun getDependencies() = parentUsageContext.dependencies + additionalDependencies override fun getGlobalExcludes(): Set = emptySet() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt index 6037743c889..b55535d582d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/stringUtils.kt @@ -13,3 +13,13 @@ internal fun lowerCamelCaseName(vararg nameParts: String?): String { transform = String::capitalize ) } + +internal fun lowerSpinalCaseName(nameParts: Iterable) = lowerSpinalCaseName(*nameParts.toList().toTypedArray()) + +internal fun lowerSpinalCaseName(vararg nameParts: String?): String { + val nonEmptyParts = nameParts.mapNotNull { it?.takeIf(String::isNotEmpty) } + return nonEmptyParts.joinToString( + separator = "-", + transform = String::toLowerCase + ) +} \ No newline at end of file