Support basic publishing with maven-publish in the PM20 plugin

This commit is contained in:
Sergey Igushkin
2021-02-16 12:07:09 +03:00
parent 0bf408e147
commit ec3fa2610e
12 changed files with 241 additions and 36 deletions
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.LazyCapability
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.CalculatedCapability
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.directoryNpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.moduleName
@@ -76,7 +76,9 @@ class DefaultKotlinDependencyHandler(
val dependency = when (dependencyNotation) {
is KotlinGradleModule -> project.dependencies.create(dependencyNotation.project).apply {
(this as ModuleDependency).capabilities {
it.requireCapability(LazyCapability.fromModule(dependencyNotation))
if (dependencyNotation.moduleClassifier != null) {
it.requireCapability(CalculatedCapability.fromModule(dependencyNotation))
}
}
}
else -> dependencyNotation
@@ -267,8 +267,13 @@ class GradleModuleVariantResolver(val project: Project) : ModuleVariantResolver
// TODO maybe improve this behavior? Currently it contradicts dependency resolution in that it may return a chosen variant for an
// unrequested dependency. This workaround is needed for synthetic modules which were not produced from module metadata, so maybe
// those modules should be marked somehow
dependencyModule.variants.singleOrNull()
?.let { return VariantResolution.fromMatchingVariants(requestingVariant, dependencyModule, listOf(it)) }
if (dependencyModule is ExternalSyntheticKotlinModule) {
return VariantResolution.fromMatchingVariants(
requestingVariant,
dependencyModule,
listOf(dependencyModule.variants.single())
)
}
val module = requestingVariant.containingModule
@@ -198,6 +198,7 @@ internal fun buildProjectStructureMetadata(module: KotlinGradleModule): KotlinPr
module.fragments.associate { it.name to it.directRefinesDependencies.map { it.name }.toSet() }
// FIXME: support native implementation-as-api-dependencies
// FIXME: support dependencies on auxiliary modules
val fragmentDependencies =
module.fragments.associate { fragment ->
fragment.name to fragment.declaredModuleDependencies.map {
@@ -15,13 +15,12 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.dsl.topLevelExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.currentBuildId
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.CalculatedCapability
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import org.jetbrains.kotlin.project.model.LocalModuleIdentifier
import org.jetbrains.kotlin.project.model.MavenModuleIdentifier
import java.lang.IllegalArgumentException
internal object ModuleIds {
fun fromDependency(dependency: Dependency) = when (dependency) {
@@ -77,7 +76,8 @@ internal object ModuleIds {
is LocalModuleIdentifier -> {
check(moduleIdentifier.buildId == thisProject.currentBuildId().name)
val dependencyProject = thisProject.project(moduleIdentifier.projectId)
val getRootPublication: () -> MavenPublication? = when (val topLevelExtension = dependencyProject.topLevelExtension) {
val topLevelExtension = dependencyProject.topLevelExtension
val getRootPublication: () -> MavenPublication? = when (topLevelExtension) {
is KotlinMultiplatformExtension -> {
{ topLevelExtension.rootSoftwareComponent.publicationDelegate }
}
@@ -86,7 +86,19 @@ internal object ModuleIds {
}
else -> error("unexpected top-level extension $topLevelExtension")
}
val coordinatesProvider = MultiplatformRootPublicationCoordinatesProvider(dependencyProject, getRootPublication)
val capabilities = when (topLevelExtension) {
is KotlinMultiplatformExtension -> emptyList()
is KotlinPm20ProjectExtension -> listOfNotNull(CalculatedCapability.capabilityStringFromModule(
topLevelExtension.modules.single { it.moduleIdentifier == moduleIdentifier }
))
else -> error("unexpected top-level extension $topLevelExtension")
}
val coordinatesProvider = MavenPublicationCoordinatesProvider(
dependencyProject,
getRootPublication,
defaultModuleSuffix = null,
capabilities = capabilities
)
return ChangingModuleDependencyIdentifier({ coordinatesProvider.group }, { coordinatesProvider.name })
}
is MavenModuleIdentifier -> {
@@ -104,9 +116,11 @@ interface PublishedModuleCoordinatesProvider {
val capabilities: Iterable<String>
}
class MultiplatformRootPublicationCoordinatesProvider(
open class MavenPublicationCoordinatesProvider(
project: Project,
val getPublication: () -> MavenPublication?
val getPublication: () -> MavenPublication?,
defaultModuleSuffix: String?,
override val capabilities: Iterable<String> = emptyList()
) : PublishedModuleCoordinatesProvider {
override val group: String by project.provider {
@@ -114,13 +128,10 @@ class MultiplatformRootPublicationCoordinatesProvider(
}
override val name: String by project.provider {
getPublication()?.artifactId ?: project.group.toString()
getPublication()?.artifactId ?: project.name.plus(defaultModuleSuffix?.let { "-$it" }.orEmpty())
}
override val version: String by project.provider {
getPublication()?.artifactId ?: project.version.toString()
getPublication()?.version ?: project.version.toString()
}
override val capabilities: Iterable<String>
get() = emptyList()
}
@@ -6,7 +6,9 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.*
import org.gradle.api.component.SoftwareComponentFactory
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
@@ -14,8 +16,11 @@ import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import javax.inject.Inject
abstract class KotlinPm20GradlePlugin : Plugin<Project> {
abstract class KotlinPm20GradlePlugin @Inject constructor(
@Inject private val softwareComponentFactory: SoftwareComponentFactory
) : Plugin<Project> {
override fun apply(project: Project) {
checkGradleCompatibility("the Kotlin Multiplatform plugin", GradleVersion.version("6.1"))
@@ -25,6 +30,8 @@ abstract class KotlinPm20GradlePlugin : Plugin<Project> {
createDefaultModules(project)
customizeKotlinDependencies(project)
registerDefaultVariantFactories(project)
setupFragmentsMetadata(project)
setupPublication(project)
}
private fun registerDefaultVariantFactories(project: Project) {
@@ -42,17 +49,54 @@ abstract class KotlinPm20GradlePlugin : Plugin<Project> {
modules.create(KotlinGradleModule.TEST_MODULE_NAME)
main { makePublic() }
}
setupFragmentsMetadata(project)
}
private fun setupFragmentsMetadata(project: Project) {
project.pm20Extension.modules.all { module ->
configureMetadataResolutionAndBuild(module)
module.ifMadePublic { configureMetadataExposure(module) }
configureMetadataExposure(module)
}
}
private fun setupPublication(project: Project) {
project.pm20Extension.modules.all { module ->
setupPublicationForModule(module)
}
}
private fun setupPublicationForModule(module: KotlinGradleModule) {
val project = module.project
val metadataElements = project.configurations.getByName(metadataElementsConfigurationName(module))
val componentName = rootPublicationComponentName(module)
val rootSoftwareComponent = softwareComponentFactory.adhoc(componentName).also {
project.components.add(it)
it.addVariantsFromConfiguration(metadataElements) { }
}
module.ifMadePublic {
val metadataDependencyConfiguration = resolvableMetadataConfiguration(module)
project.pluginManager.withPlugin("maven-publish") {
project.extensions.getByType(PublishingExtension::class.java).publications.create(
componentName,
MavenPublication::class.java
) { publication ->
publication.from(rootSoftwareComponent)
publication.versionMapping { versionMapping ->
versionMapping.allVariants {
it.fromResolutionOf(metadataDependencyConfiguration)
}
}
}
}
}
}
}
fun rootPublicationComponentName(module: KotlinGradleModule) =
module.disambiguateName("root")
open class KotlinPm20ProjectExtension(project: Project) : KotlinTopLevelExtension(project) {
val modules: NamedDomainObjectContainer<KotlinGradleModule> by lazy {
project.objects.domainObjectContainer(
@@ -42,6 +42,7 @@ open class KotlinGradleModule(
private var setPublicHandlers: MutableList<() -> Unit> = mutableListOf()
fun ifMadePublic(action: () -> Unit) {
// FIXME reentrancy?
if (isPublic) action() else setPublicHandlers.add(action)
}
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.project.model.refinesClosure
open class KotlinJvmVariant(containingModule: KotlinGradleModule, fragmentName: String) :
KotlinGradleVariantWithRuntimeDependencies(containingModule, fragmentName) {
KotlinGradlePublishedVariantWithRuntime(containingModule, fragmentName) {
override val platformType: KotlinPlatformType
get() = KotlinPlatformType.jvm
@@ -6,12 +6,20 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.attributes.Usage
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.api.component.SoftwareComponentFactory
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.LazyCapability
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.CalculatedCapability
import org.jetbrains.kotlin.gradle.plugin.mpp.publishedConfigurationName
import org.jetbrains.kotlin.gradle.plugin.mpp.sourcesJarTask
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.dashSeparatedName
import org.jetbrains.kotlin.project.model.refinesClosure
abstract class AbstractKotlinGradleVariantFactory<T : KotlinGradleVariant>(
@@ -79,7 +87,7 @@ abstract class AbstractKotlinGradleVariantFactory<T : KotlinGradleVariant>(
}
}
abstract class AbstractKotlinGradleVariantWithRuntimeDependenciesFactory<T : KotlinGradleVariantWithRuntimeDependencies>(module: KotlinGradleModule) :
abstract class AbstractKotlinGradleVariantWithRuntimeFactory<T : KotlinGradleVariantWithRuntime>(module: KotlinGradleModule) :
AbstractKotlinGradleVariantFactory<T>(module) {
open fun configureRuntimeResolvableConfiguration(fragment: T, configuration: Configuration) {
@@ -98,7 +106,7 @@ abstract class AbstractKotlinGradleVariantWithRuntimeDependenciesFactory<T : Kot
fragment.runtimeDependencyFiles = project.configurations.create(fragment.runtimeDependencyConfigurationName).apply {
isCanBeConsumed = false
isCanBeResolved = true
configureCompileResolvableConfiguration(fragment, this@apply)
configureRuntimeResolvableConfiguration(fragment, this@apply)
project.addExtendsFromRelation(name, fragment.transitiveApiConfigurationName)
project.addExtendsFromRelation(name, fragment.transitiveImplementationConfigurationName)
}
@@ -122,6 +130,82 @@ abstract class AbstractKotlinGradleVariantWithRuntimeDependenciesFactory<T : Kot
}
}
abstract class AbstractKotlinGradleRuntimePublishedVariantFactory<T : KotlinGradlePublishedVariantWithRuntime>(module: KotlinGradleModule) :
AbstractKotlinGradleVariantWithRuntimeFactory<T>(module) {
override fun create(name: String): T {
val result = super.create(name)
configureVariantPublishing(result)
return result
}
open fun configureVariantPublishing(variant: T) {
val rootSoftwareComponent =
project.components
.withType(AdhocComponentWithVariants::class.java)
.getByName(rootPublicationComponentName(module))
val platformModuleDependencyProvider = project.provider {
val coordinates = variant.publishedMavenModuleCoordinates
(project.dependencies.create("${coordinates.group}:${coordinates.name}:${coordinates.version}") as ModuleDependency).apply {
if (module.moduleClassifier != null) {
capabilities { it.requireCapability(CalculatedCapability.fromModule(module)) }
}
}
}
// FIXME inject vs internal API
val platformComponentName = platformComponentName(variant)
val platformComponent = (project as ProjectInternal).services
.get(SoftwareComponentFactory::class.java)
.adhoc(platformComponentName)
project.components.add(platformComponent)
platformComponent.addVariantsFromConfiguration(project.configurations.getByName(variant.apiElementsConfigurationName)) {
it.mapToMavenScope("compile")
}
platformComponent.addVariantsFromConfiguration(project.configurations.getByName(variant.runtimeElementsConfigurationName)) {
it.mapToMavenScope("compile")
}
module.ifMadePublic {
project.pluginManager.withPlugin("maven-publish") {
project.extensions.getByType(PublishingExtension::class.java).apply {
publications.create(platformComponentName, MavenPublication::class.java).apply {
from(platformComponent)
variant.assignMavenPublication(this)
artifactId = dashSeparatedName(project.name, variant.defaultPublishedModuleSuffix)
}
}
}
}
val publishedApiConfiguration =
project.configurations.create(publishedConfigurationName(variant.apiElementsConfigurationName)).apply {
isCanBeConsumed = false
isCanBeResolved = false
configureApiElementsConfiguration(variant, this)
setModuleCapability(this, module)
dependencies.addLater(platformModuleDependencyProvider)
}
val publishedRuntimeConfiguration =
project.configurations.create(publishedConfigurationName(variant.runtimeElementsConfigurationName)).apply {
isCanBeConsumed = false
isCanBeResolved = false
configureRuntimeElementsConfiguration(variant, this)
setModuleCapability(this, module)
dependencies.addLater(platformModuleDependencyProvider)
}
listOf(publishedApiConfiguration, publishedRuntimeConfiguration).forEach {
rootSoftwareComponent.addVariantsFromConfiguration(it) { }
}
}
open fun platformComponentName(variant: T) = variant.disambiguateName("")
}
internal fun setModuleCapability(configuration: Configuration, module: KotlinGradleModule) {
configuration.outgoing.capability(LazyCapability.fromModule(module))
if (module.moduleClassifier != null) {
configuration.outgoing.capability(CalculatedCapability.fromModule(module))
}
}
@@ -7,13 +7,18 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationOutput
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinCompilationOutput
import org.jetbrains.kotlin.gradle.plugin.mpp.MavenPublicationCoordinatesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.PublishedModuleCoordinatesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.CalculatedCapability
import org.jetbrains.kotlin.gradle.plugin.mpp.publishedConfigurationName
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.dashSeparatedName
import org.jetbrains.kotlin.project.model.KotlinAttributeKey
import org.jetbrains.kotlin.project.model.KotlinModuleVariant
import org.jetbrains.kotlin.project.model.KotlinPlatformTypeAttribute
@@ -49,19 +54,47 @@ abstract class KotlinGradleVariant(
val apiElementsConfigurationName: String
get() = disambiguateName("apiElements")
open val gradleVariantNames: Set<String>
get() = listOf(apiElementsConfigurationName, publishedConfigurationName(apiElementsConfigurationName)).toSet()
abstract val gradleVariantNames: Set<String>
override fun toString(): String = "variant $fragmentName in $containingModule"
}
interface SingleMavenPublicationHolder {
fun assignMavenPublication(publication: MavenPublication)
val defaultPublishedModuleSuffix: String?
val publishedMavenModuleCoordinates: PublishedModuleCoordinatesProvider
}
class DefaultSingleMavenPublicationHolder(
private var module: KotlinGradleModule,
override val defaultPublishedModuleSuffix: String?
) : SingleMavenPublicationHolder {
private val project get() = module.project
private var assignedMavenPublication: MavenPublication? = null
override fun assignMavenPublication(publication: MavenPublication) {
if (assignedMavenPublication != null)
error("already assigned publication $publication")
assignedMavenPublication = publication
}
override val publishedMavenModuleCoordinates: PublishedModuleCoordinatesProvider =
MavenPublicationCoordinatesProvider(
project,
{ assignedMavenPublication },
defaultPublishedModuleSuffix,
capabilities = listOfNotNull(CalculatedCapability.capabilityStringFromModule(module))
)
}
private fun kotlinPlatformTypeAttributeFromPlatform(platformType: KotlinPlatformType) = platformType.name
// TODO: rewrite with the artifacts API
internal val KotlinGradleVariant.defaultSourceArtifactTaskName: String
get() = disambiguateName("sourcesJar")
abstract class KotlinGradleVariantWithRuntimeDependencies(
abstract class KotlinGradleVariantWithRuntime(
containingModule: KotlinGradleModule,
fragmentName: String
) : KotlinGradleVariant(containingModule, fragmentName) {
@@ -78,8 +111,21 @@ abstract class KotlinGradleVariantWithRuntimeDependencies(
// TODO generalize exposing outputs: what if a variant has more than one such configurations or none?
val runtimeElementsConfigurationName: String
get() = disambiguateName("runtimeElements")
}
private fun defaultModuleSuffix(module: KotlinGradleModule, variantName: String): String =
dashSeparatedName(variantName, module.moduleClassifier)
abstract class KotlinGradlePublishedVariantWithRuntime(containingModule: KotlinGradleModule, fragmentName: String) :
KotlinGradleVariantWithRuntime(containingModule, fragmentName),
SingleMavenPublicationHolder by DefaultSingleMavenPublicationHolder(
containingModule,
defaultModuleSuffix(containingModule, fragmentName)
) {
override val gradleVariantNames: Set<String>
get() = super.gradleVariantNames +
listOf(runtimeElementsConfigurationName, publishedConfigurationName(runtimeElementsConfigurationName))
get() =
listOf(apiElementsConfigurationName, runtimeElementsConfigurationName).flatMapTo(mutableSetOf()) {
listOf(it, publishedConfigurationName(it))
}
}
@@ -42,11 +42,14 @@ internal fun configureMetadataResolutionAndBuild(module: KotlinGradleModule) {
internal fun configureMetadataExposure(module: KotlinGradleModule) {
val project = module.project
project.configurations.create(module.disambiguateName("metadataElements")).apply {
isCanBeConsumed = true
project.configurations.create(metadataElementsConfigurationName(module)).apply {
module.ifMadePublic {
isCanBeConsumed = true
}
isCanBeResolved = false
project.artifacts.add(name, project.tasks.named(metadataJarName(module)))
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA))
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common)
module.fragments.all { fragment ->
// FIXME: native api-implementation
project.addExtendsFromRelation(name, fragment.apiConfigurationName)
@@ -55,6 +58,9 @@ internal fun configureMetadataExposure(module: KotlinGradleModule) {
}
}
fun metadataElementsConfigurationName(module: KotlinGradleModule) =
module.disambiguateName("metadataElements")
private fun generateAndExportProjectStructureMetadata(
module: KotlinGradleModule
) {
@@ -123,7 +129,7 @@ private fun configureMetadataJarTask(
}
}
private fun metadataJarName(module: KotlinGradleModule) =
internal fun metadataJarName(module: KotlinGradleModule) =
lowerCamelCaseName(module.moduleClassifier, "metadataJar")
private fun createMetadataCompilation(
@@ -18,7 +18,7 @@ import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
import java.util.concurrent.Callable
open class KotlinJvmVariantFactory(module: KotlinGradleModule) :
AbstractKotlinGradleVariantWithRuntimeDependenciesFactory<KotlinJvmVariant>(module) {
AbstractKotlinGradleRuntimePublishedVariantFactory<KotlinJvmVariant>(module) {
override fun instantiateFragment(name: String) = KotlinJvmVariant(module, name)
// FIXME expose the JAR with the artifacts API
@@ -9,7 +9,7 @@ import org.gradle.api.capabilities.Capability
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinGradleModule
internal class LazyCapability(
internal class CalculatedCapability(
val groupProvider: Provider<String>,
val nameValue: String,
val versionProvider: Provider<String>,
@@ -22,14 +22,19 @@ internal class LazyCapability(
override fun getVersion(): String? = versionProvider.get()
companion object {
fun fromModule(module: KotlinGradleModule): LazyCapability {
fun fromModule(module: KotlinGradleModule): CalculatedCapability {
val project = module.project
return LazyCapability(
return CalculatedCapability(
project.provider { project.group.toString() },
project.name,
project.provider { project.version.toString() },
module.moduleClassifier
)
}
fun capabilityStringFromModule(module: KotlinGradleModule): String? =
if (module.moduleClassifier != null) fromModule(module).notation() else null
}
fun notation(): String = "$group:$name:$version"
}