From bc531610f72bb119df9aab8b708b3ce592e79194 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Wed, 16 Nov 2022 18:10:51 +0100 Subject: [PATCH] [Gradle] ExternalKotlinTarget Api: Add initial APIs to configure publications KT-54981 --- .../build.gradle.kts | 2 +- .../gradle/android/AndroidTargetPrototype.kt | 55 ++++++++++++----- .../gradle/plugin/mpp/AbstractKotlinTarget.kt | 12 +++- .../gradle/plugin/mpp/InternalKotlinTarget.kt | 7 ++- .../plugin/mpp/KotlinSoftwareComponent.kt | 19 +++--- .../kotlin/gradle/plugin/mpp/Publishing.kt | 8 +-- .../external/DecoratedExternalKotlinTarget.kt | 17 +++++- .../external/ExternalKotlinTargetComponent.kt | 59 +++++++++++++++++++ .../mpp/external/ExternalKotlinTargetImpl.kt | 47 ++++++++++----- .../ExternalKotlinTargetSoftwareComponent.kt | 51 ++++++++++++++++ .../external/createExternalKotlinTarget.kt | 53 +++++++++++++---- .../mpp/pm20/util/ConfigurationUtils.kt | 17 +++++- ...ExternalAndroidTargetPrototypeSmokeTest.kt | 26 +++++++- 13 files changed, 304 insertions(+), 69 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetComponent.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetSoftwareComponent.kt diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts index 68eacd781fa..79ed8f17c9f 100644 --- a/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts @@ -5,7 +5,7 @@ plugins { dependencies { compileOnly(gradleKotlinDsl()) - compileOnly("com.android.tools.build:gradle:7.0.0") + compileOnly("com.android.tools.build:gradle:7.2.1") compileOnly(project(":kotlin-gradle-plugin")) } diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt index 1cfdf798af4..c6c8a36a113 100644 --- a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt +++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt @@ -7,7 +7,8 @@ package org.jetbrains.kotlin.gradle.android -import com.android.build.gradle.AppExtension +import com.android.build.api.attributes.AgpVersionAttr +import com.android.build.gradle.LibraryExtension import com.android.build.gradle.internal.publishing.AndroidArtifacts import org.gradle.api.attributes.java.TargetJvmEnvironment import org.gradle.api.attributes.java.TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE @@ -23,7 +24,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetDescr @OptIn(ExternalVariantApi::class) fun KotlinMultiplatformExtension.androidTargetPrototype(): PrototypeAndroidTarget { val project = this.project - val androidExtension = project.extensions.getByType() + val androidExtension = project.extensions.getByType() /* Set a variant filter and only allow 'debug'. @@ -47,19 +48,37 @@ fun KotlinMultiplatformExtension.androidTargetPrototype(): PrototypeAndroidTarge platformType = KotlinPlatformType.jvm targetFactory = TargetFactory { delegate -> PrototypeAndroidTarget(delegate, PrototypeAndroidDsl(31)) } + /* + Configure apiElements configuration attributes (project to project dependency) + In this example we hardcoded AGP version 7.4.0-beta02 as demo + */ apiElements.configure { _, configuration -> configuration.attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID)) + configuration.attributes.attribute(AgpVersionAttr.ATTRIBUTE, project.objects.named("7.4.0-beta02")) /* For demo */ } + /* + Configure runtimeElements configuration attributes (project to project dependency) + In this example we hardcoded AGP version 7.4.0-beta02 as demo + */ runtimeElements.configure { _, configuration -> configuration.attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID)) + configuration.attributes.attribute(AgpVersionAttr.ATTRIBUTE, project.objects.named("7.4.0-beta02")) /* For demo */ } + /* + Configure published configurations (maven publication): + We override KotlinPlatformType to be androidJvm for now (to be discussed w/ Google later) + */ apiElementsPublished.configure { _, configuration -> + /* TODO w/ Google: Find a way to deprecate this attribute */ + configuration.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm) configuration.attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID)) } runtimeElementsPublished.configure { _, configuration -> + /* TODO w/ Google: Find a way to deprecate this attribute */ + configuration.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm) configuration.attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID)) } } @@ -67,8 +86,8 @@ fun KotlinMultiplatformExtension.androidTargetPrototype(): PrototypeAndroidTarge /* Whilst using the .all hook, we only expect the single 'debug' variant to be available through this API. */ - androidExtension.applicationVariants.all { applicationVariant -> - project.logger.quiet("Setting up applicationVariant: ${applicationVariant.name}") + androidExtension.libraryVariants.all { androidVariant -> + project.logger.quiet("Setting up applicationVariant: ${androidVariant.name}") /* Create Compilations: main, unitTest and instrumentedTest @@ -97,21 +116,21 @@ fun KotlinMultiplatformExtension.androidTargetPrototype(): PrototypeAndroidTarge Wire the Kotlin Compilations output (.class files) into the Android artifacts by using the 'registerPreJavacGeneratedBytecode' function */ - applicationVariant.registerPreJavacGeneratedBytecode(mainCompilation.output.classesDirs) - applicationVariant.unitTestVariant.registerPreJavacGeneratedBytecode(unitTestCompilation.output.classesDirs) - applicationVariant.testVariant.registerPreJavacGeneratedBytecode(instrumentedTestCompilation.output.classesDirs) + androidVariant.registerPreJavacGeneratedBytecode(mainCompilation.output.classesDirs) + androidVariant.unitTestVariant.registerPreJavacGeneratedBytecode(unitTestCompilation.output.classesDirs) + androidVariant.testVariant.registerPreJavacGeneratedBytecode(instrumentedTestCompilation.output.classesDirs) /* Add dependencies coming from Kotlin to Android by adding all dependencies from Kotlin to the variants compileConfiguration or runtimeConfiguration */ - applicationVariant.compileConfiguration.extendsFrom(mainCompilation.configurations.compileDependencyConfiguration) - applicationVariant.runtimeConfiguration.extendsFrom(mainCompilation.configurations.runtimeDependencyConfiguration) - applicationVariant.unitTestVariant.compileConfiguration.extendsFrom(unitTestCompilation.configurations.compileDependencyConfiguration) - applicationVariant.unitTestVariant.runtimeConfiguration.extendsFrom(unitTestCompilation.configurations.runtimeDependencyConfiguration) - applicationVariant.testVariant.compileConfiguration.extendsFrom(instrumentedTestCompilation.configurations.compileDependencyConfiguration) - applicationVariant.testVariant.runtimeConfiguration.extendsFrom(instrumentedTestCompilation.configurations.runtimeDependencyConfiguration) + androidVariant.compileConfiguration.extendsFrom(mainCompilation.configurations.compileDependencyConfiguration) + androidVariant.runtimeConfiguration.extendsFrom(mainCompilation.configurations.runtimeDependencyConfiguration) + androidVariant.unitTestVariant.compileConfiguration.extendsFrom(unitTestCompilation.configurations.compileDependencyConfiguration) + androidVariant.unitTestVariant.runtimeConfiguration.extendsFrom(unitTestCompilation.configurations.runtimeDependencyConfiguration) + androidVariant.testVariant.compileConfiguration.extendsFrom(instrumentedTestCompilation.configurations.compileDependencyConfiguration) + androidVariant.testVariant.runtimeConfiguration.extendsFrom(instrumentedTestCompilation.configurations.runtimeDependencyConfiguration) /* @@ -148,12 +167,18 @@ fun KotlinMultiplatformExtension.androidTargetPrototype(): PrototypeAndroidTarge } } + /* + Add .aar artifacts to publications! + */ + androidTarget.apiElementsPublishedConfiguration.outgoing.artifact(androidVariant.packageLibraryProvider) + androidTarget.runtimeElementsPublishedConfiguration.outgoing.artifact(androidVariant.packageLibraryProvider) + /* "Disable" configurations from plain Android plugin This hack will not be necessary in the final implementation */ - project.configurations.findByName("${applicationVariant.name}ApiElements")?.isCanBeConsumed = false - project.configurations.findByName("${applicationVariant.name}RuntimeElements")?.isCanBeConsumed = false + project.configurations.findByName("${androidVariant.name}ApiElements")?.isCanBeConsumed = false + project.configurations.findByName("${androidVariant.name}RuntimeElements")?.isCanBeConsumed = false } return androidTarget diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt index 4f478290a8b..13d48e86822 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt @@ -20,6 +20,7 @@ import org.gradle.api.internal.component.SoftwareComponentInternal import org.gradle.api.internal.component.UsageContext import org.gradle.api.internal.project.ProjectInternal import org.gradle.api.publish.maven.MavenPublication +import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.plugin.* @@ -58,7 +59,8 @@ abstract class AbstractKotlinTarget( override val publishable: Boolean get() = true - internal open val kotlinComponents: Set by lazy { + @InternalKotlinGradlePluginApi + override val kotlinComponents: Set by lazy { val mainCompilation = compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) val usageContexts = createUsageContexts(mainCompilation) @@ -144,14 +146,18 @@ abstract class AbstractKotlinTarget( } @Suppress("UNCHECKED_CAST") - internal val publicationConfigureActions: DomainObjectSet> = project - .objects + private val publicationConfigureActions: DomainObjectSet> = project.objects .domainObjectSet(Action::class.java) as DomainObjectSet> override fun mavenPublication(action: Action) { publicationConfigureActions.add(action) } + @InternalKotlinGradlePluginApi + override fun onPublicationCreated(publication: MavenPublication) { + publicationConfigureActions.all { action -> action.execute(publication) } + } + override var preset: KotlinTargetPreset? = null internal set } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinTarget.kt index ebdc24f89dc..a111dc06bd3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinTarget.kt @@ -5,9 +5,14 @@ package org.jetbrains.kotlin.gradle.plugin.mpp +import org.gradle.api.publish.maven.MavenPublication import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent -internal interface InternalKotlinTarget : KotlinTarget +internal interface InternalKotlinTarget : KotlinTarget { + val kotlinComponents: Set + fun onPublicationCreated(publication: MavenPublication) +} internal val KotlinTarget.internal: InternalKotlinTarget get() = (this as? InternalKotlinTarget) ?: throw IllegalArgumentException( diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt index 69d09188480..6d6c2dc3a58 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -19,14 +19,12 @@ import org.gradle.api.internal.component.UsageContext import org.gradle.api.provider.SetProperty import org.gradle.api.publish.maven.MavenPublication import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension -import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations import org.jetbrains.kotlin.gradle.plugin.usageByName import org.jetbrains.kotlin.gradle.targets.metadata.* -import org.jetbrains.kotlin.gradle.targets.metadata.COMMON_MAIN_ELEMENTS_CONFIGURATION_NAME -import org.jetbrains.kotlin.gradle.targets.metadata.getCommonSourceSetsForMetadataCompilation -import org.jetbrains.kotlin.gradle.targets.metadata.isCompatibilityMetadataVariantEnabled -import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.utils.setProperty abstract class KotlinSoftwareComponent( @@ -40,11 +38,12 @@ abstract class KotlinSoftwareComponent( override fun getVariants(): Set = kotlinTargets .filter { target -> target !is KotlinMetadataTarget } .flatMap { target -> - val targetPublishableComponentNames = - (target as? AbstractKotlinTarget)?.kotlinComponents?.mapNotNullTo(mutableSetOf()) { component -> - component.name.takeIf { component.publishable } - } - target.components.filter { targetPublishableComponentNames?.contains(it.name) ?: true } + val targetPublishableComponentNames = target.internal.kotlinComponents + .filter { component -> component.publishable } + .map { component -> component.name } + .toSet() + + target.components.filter { it.name in targetPublishableComponentNames } }.toSet() private val _usages: Set by lazy { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt index b10471def28..b713e338e73 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt @@ -62,7 +62,7 @@ private fun createTargetPublications(project: Project, publishing: PublishingExt val kotlin = project.multiplatformExtension // Enforce the order of creating the publications, since the metadata publication is used in the other publications: kotlin.targets - .withType(AbstractKotlinTarget::class.java) + .withType(InternalKotlinTarget::class.java) .matching { it.publishable } .all { kotlinTarget -> if (kotlinTarget is KotlinAndroidTarget) @@ -73,7 +73,7 @@ private fun createTargetPublications(project: Project, publishing: PublishingExt } } -private fun AbstractKotlinTarget.createMavenPublications(publications: PublicationContainer) { +private fun InternalKotlinTarget.createMavenPublications(publications: PublicationContainer) { components .map { gradleComponent -> gradleComponent to kotlinComponents.single { it.name == gradleComponent.name } } .filter { (_, kotlinComponent) -> kotlinComponent.publishableOnCurrentHost } @@ -102,7 +102,7 @@ private fun AbstractKotlinTarget.createMavenPublications(publications: Publicati } (kotlinComponent as? KotlinTargetComponentWithPublication)?.publicationDelegate = componentPublication - publicationConfigureActions.all { it.execute(componentPublication) } + onPublicationCreated(componentPublication) } } @@ -124,7 +124,7 @@ private fun rewritePom( * can't read Gradle module metadata won't resolve a dependency on an MPP to the granular metadata variant and won't then choose the * right dependencies for each source set, we put only the dependencies of the legacy common variant into the POM, i.e. commonMain API. */ -private fun dependenciesForPomRewriting(target: AbstractKotlinTarget): Provider>? = +private fun dependenciesForPomRewriting(target: InternalKotlinTarget): Provider>? = if (target !is KotlinMetadataTarget || !target.project.isKotlinGranularMetadataEnabled) null else { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt index 5e9e91453ac..a0decc8246f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt @@ -7,8 +7,9 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.external import org.gradle.api.artifacts.Configuration import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.plugin.mpp.InternalKotlinTarget @ExternalKotlinTargetApi @@ -23,5 +24,15 @@ abstract class DecoratedExternalKotlinTarget internal constructor( val runtimeElementsConfiguration: Configuration = delegate.runtimeElementsConfiguration - internal val logger: Logger = Logging.getLogger("${ExternalKotlinTargetImpl::class.qualifiedName}: $name") -} \ No newline at end of file + val apiElementsPublishedConfiguration: Configuration = delegate.apiElementsPublishedConfiguration + + val runtimeElementsPublishedConfiguration: Configuration = delegate.runtimeElementsPublishedConfiguration + + internal val logger: Logger = delegate.logger +} + +internal val ExternalKotlinTargetImpl.decoratedInstance: DecoratedExternalKotlinTarget + get() = project.multiplatformExtension.targets.getByName(name) as DecoratedExternalKotlinTarget + +internal val ExternalKotlinTargetImpl.decoratedInstanceOrNull: DecoratedExternalKotlinTarget? + get() = project.multiplatformExtensionOrNull?.targets?.findByName(name) as? DecoratedExternalKotlinTarget \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetComponent.kt new file mode 100644 index 00000000000..c5e0a111d64 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetComponent.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp.external + +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.component.ComponentWithCoordinates +import org.gradle.api.publish.maven.MavenPublication +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinTargetComponentWithPublication +import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetComponent.TargetProvider +import org.jetbrains.kotlin.gradle.plugin.mpp.getCoordinatesFromPublicationDelegateAndProject +import org.jetbrains.kotlin.gradle.utils.dashSeparatedName + +internal class ExternalKotlinTargetComponent( + val targetProvider: TargetProvider +) : KotlinTargetComponentWithPublication, ComponentWithCoordinates { + + /* + Target creation requires this component. We will provide the target once it is required + */ + fun interface TargetProvider { + operator fun invoke(): DecoratedExternalKotlinTarget + + companion object { + fun byTargetName(extension: KotlinMultiplatformExtension, targetName: String) = TargetProvider { + extension.targets.getByName(targetName) as DecoratedExternalKotlinTarget + } + } + } + + /* Required for getting correct coordinates */ + override var publicationDelegate: MavenPublication? = null + + override val target: KotlinTarget by lazy { targetProvider() } + + override val publishable: Boolean + get() = target.publishable + + override val publishableOnCurrentHost: Boolean + get() = true + + override val defaultArtifactId: String + get() = dashSeparatedName(target.project.name, target.name.toLowerCase()) + + override val sourcesArtifacts: Set + get() = emptySet() + + override fun getName(): String = target.name + + override fun getCoordinates(): ModuleVersionIdentifier = + getCoordinatesFromPublicationDelegateAndProject(publicationDelegate, target.project, null) +} + + diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetImpl.kt index 1e28f25cae3..c574cbd777d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetImpl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetImpl.kt @@ -5,39 +5,46 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.external -import org.gradle.api.Action -import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.Project -import org.gradle.api.Task +import org.gradle.api.* import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.AttributeContainer import org.gradle.api.component.SoftwareComponent +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent +import org.jetbrains.kotlin.gradle.plugin.mpp.HierarchyAttributeContainer import org.jetbrains.kotlin.gradle.plugin.mpp.InternalKotlinTarget -import org.jetbrains.kotlin.gradle.plugin.mpp.buildAdhocComponentsFromKotlinVariants internal class ExternalKotlinTargetImpl internal constructor( override val project: Project, override val targetName: String, override val platformType: KotlinPlatformType, + override val publishable: Boolean, val defaultConfiguration: Configuration, val apiElementsConfiguration: Configuration, val runtimeElementsConfiguration: Configuration, - override val publishable: Boolean, - internal val kotlinComponents: Set, + val apiElementsPublishedConfiguration: Configuration, + val runtimeElementsPublishedConfiguration: Configuration, + val kotlinTargetComponent: ExternalKotlinTargetComponent, private val artifactsTaskLocator: ArtifactsTaskLocator, ) : InternalKotlinTarget { + fun interface ArtifactsTaskLocator { fun locate(target: ExternalKotlinTargetImpl): TaskProvider } val kotlin = project.multiplatformExtension + override val preset: Nothing? = null + + internal val logger: Logger = Logging.getLogger("${ExternalKotlinTargetImpl::class.qualifiedName}: $name") + override val useDisambiguationClassifierAsSourceSetNamePrefix: Boolean = true override val overrideDisambiguationClassifierOnIdeImport: String? = null @@ -58,26 +65,36 @@ internal class ExternalKotlinTargetImpl internal constructor( override val runtimeElementsConfigurationName: String get() = runtimeElementsConfiguration.name + @InternalKotlinGradlePluginApi + override val kotlinComponents: Set = setOf(kotlinTargetComponent) + override val components: Set by lazy { - project.buildAdhocComponentsFromKotlinVariants(kotlinComponents) + logger.debug("Creating SoftwareComponent") + setOf(ExternalKotlinTargetSoftwareComponent(this)) } override val compilations: NamedDomainObjectContainer by lazy { project.container(ExternalDecoratedKotlinCompilation::class.java) } + @Suppress("unchecked_cast") + private val mavenPublicationActions = project.objects.domainObjectSet(Action::class.java) + as DomainObjectSet> + override fun mavenPublication(action: Action) { - TODO("Not yet implemented") + mavenPublicationActions.add(action) } - override val preset: Nothing? = null - - override fun getAttributes(): AttributeContainer { - TODO("Not yet implemented") + @InternalKotlinGradlePluginApi + override fun onPublicationCreated(publication: MavenPublication) { + mavenPublicationActions.all { action -> action.execute(publication) } } + private val attributeContainer = HierarchyAttributeContainer(parent = null) + + override fun getAttributes(): AttributeContainer = attributeContainer + internal fun onCreated() { artifactsTask - components } -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetSoftwareComponent.kt new file mode 100644 index 00000000000..b16c785c075 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetSoftwareComponent.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.mpp.external + +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.component.ComponentWithCoordinates +import org.gradle.api.component.ComponentWithVariants +import org.gradle.api.component.SoftwareComponent +import org.gradle.api.component.SoftwareComponentFactory +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.gradle.api.internal.project.ProjectInternal +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.tooling.core.UnsafeApi + +internal fun ExternalKotlinTargetSoftwareComponent( + target: ExternalKotlinTargetImpl, +): ExternalKotlinTargetSoftwareComponent { + val softwareComponentFactory = (target.project as ProjectInternal).services.get(SoftwareComponentFactory::class.java) + val adhocSoftwareComponent = softwareComponentFactory.adhoc(target.targetName) + + adhocSoftwareComponent.addVariantsFromConfiguration(target.apiElementsPublishedConfiguration) { details -> + details.mapToMavenScope("compile") + } + + adhocSoftwareComponent.addVariantsFromConfiguration(target.runtimeElementsPublishedConfiguration) { details -> + details.mapToMavenScope("runtime") + } + + @OptIn(UnsafeApi::class) + return ExternalKotlinTargetSoftwareComponent( + target.project.multiplatformExtension, + adhocSoftwareComponent as SoftwareComponentInternal, + target.kotlinTargetComponent + ) +} + +internal class ExternalKotlinTargetSoftwareComponent @UnsafeApi constructor( + private val multiplatformExtension: KotlinMultiplatformExtension, + private val adhocSoftwareComponent: SoftwareComponentInternal, + private val kotlinTargetComponent: ExternalKotlinTargetComponent +) : ComponentWithCoordinates, ComponentWithVariants, SoftwareComponentInternal { + override fun getName(): String = adhocSoftwareComponent.name + override fun getUsages(): Set = adhocSoftwareComponent.usages + override fun getVariants(): Set = multiplatformExtension.metadata().components + override fun getCoordinates(): ModuleVersionIdentifier = kotlinTargetComponent.coordinates +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt index a6d2d445cb9..dc47bbe9e52 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt @@ -7,12 +7,14 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.external +import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.Category import org.gradle.api.attributes.Usage import org.gradle.jvm.tasks.Jar import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.markConsumable import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName @@ -26,6 +28,16 @@ fun KotlinMultiplatformExtension.createExter val apiElementsConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "apiElements")) val runtimeElementsConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "runtimeElements")) + val apiElementsPublishedConfiguration = + project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "apiElements-published")) + + val runtimeElementsPublishedConfiguration = + project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "runtimeElements-published")) + + val kotlinTargetComponent = ExternalKotlinTargetComponent( + ExternalKotlinTargetComponent.TargetProvider.byTargetName(this, descriptor.targetName) + ) + val artifactsTaskLocator = ExternalKotlinTargetImpl.ArtifactsTaskLocator { target -> target.project.locateOrRegisterTask(lowerCamelCaseName(descriptor.targetName, "jar")) } @@ -34,25 +46,28 @@ fun KotlinMultiplatformExtension.createExter project = project, targetName = descriptor.targetName, platformType = descriptor.platformType, + publishable = true, defaultConfiguration = defaultConfiguration, apiElementsConfiguration = apiElementsConfiguration, runtimeElementsConfiguration = runtimeElementsConfiguration, - publishable = true, - kotlinComponents = emptySet(), + apiElementsPublishedConfiguration = apiElementsPublishedConfiguration, + runtimeElementsPublishedConfiguration = runtimeElementsPublishedConfiguration, + kotlinTargetComponent = kotlinTargetComponent, artifactsTaskLocator = artifactsTaskLocator ) - apiElementsConfiguration.isCanBeConsumed = true - apiElementsConfiguration.isCanBeResolved = false - apiElementsConfiguration.usesPlatformOf(target) - apiElementsConfiguration.attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(target)) - apiElementsConfiguration.attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.LIBRARY)) + target.setupApiElements(apiElementsConfiguration) + target.setupApiElements(apiElementsPublishedConfiguration) + target.setupRuntimeElements(runtimeElementsConfiguration) + target.setupRuntimeElements(runtimeElementsPublishedConfiguration) + apiElementsConfiguration.markConsumable() + runtimeElementsConfiguration.markConsumable() - runtimeElementsConfiguration.isCanBeConsumed = true - runtimeElementsConfiguration.isCanBeResolved = false - runtimeElementsConfiguration.usesPlatformOf(target) - runtimeElementsConfiguration.attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerRuntimeUsage(target)) - runtimeElementsConfiguration.attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.LIBRARY)) + /* Those configurations can not be resolved but also not consumed (not suitable for project to proejct dependencies */ + apiElementsPublishedConfiguration.isCanBeConsumed = false + apiElementsPublishedConfiguration.isCanBeResolved = false + runtimeElementsPublishedConfiguration.isCanBeResolved = false + runtimeElementsPublishedConfiguration.isCanBeConsumed = false val decorated = descriptor.targetFactory.create(DecoratedExternalKotlinTarget.Delegate(target)) target.onCreated() @@ -60,6 +75,8 @@ fun KotlinMultiplatformExtension.createExter descriptor.configure?.invoke(decorated) descriptor.apiElements.configure?.invoke(decorated, apiElementsConfiguration) descriptor.runtimeElements.configure?.invoke(decorated, runtimeElementsConfiguration) + descriptor.apiElementsPublished.configure?.invoke(decorated, apiElementsPublishedConfiguration) + descriptor.runtimeElementsPublished.configure?.invoke(decorated, runtimeElementsPublishedConfiguration) targets.add(decorated) decorated.logger.info("Created ${descriptor.platformType} target") @@ -72,3 +89,15 @@ fun KotlinMultiplatformExtension.createExter ): T { return createExternalKotlinTarget(ExternalKotlinTargetDescriptor(descriptor)) } + +private fun ExternalKotlinTargetImpl.setupApiElements(configuration: Configuration) { + configuration.usesPlatformOf(this) + configuration.attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(this)) + configuration.attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.LIBRARY)) +} + +private fun ExternalKotlinTargetImpl.setupRuntimeElements(configuration: Configuration) { + configuration.usesPlatformOf(this) + configuration.attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerRuntimeUsage(this)) + configuration.attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.LIBRARY)) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt index 45234f1c458..81c21c2b869 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt @@ -6,7 +6,9 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util import org.gradle.api.Project -import org.gradle.api.artifacts.* +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.DependencySet +import org.gradle.api.artifacts.PublishArtifactSet import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.AttributeContainer import org.gradle.api.capabilities.Capability @@ -97,4 +99,15 @@ internal fun copyAttributes(from: AttributeContainer, to: AttributeContainer, ke } internal inline fun Project.listProperty(noinline itemsProvider: () -> Iterable) = - objects.listProperty(T::class.java).apply { set(provider(itemsProvider)) } \ No newline at end of file + objects.listProperty(T::class.java).apply { set(provider(itemsProvider)) } + + +internal fun Configuration.markConsumable(): Configuration = apply { + this.isCanBeConsumed = true + this.isCanBeResolved = false +} + +internal fun Configuration.markResolvable(): Configuration = apply { + this.isCanBeConsumed = false + this.isCanBeResolved = true +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt index 9801a572324..27c79948e77 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt @@ -7,15 +7,20 @@ package org.jetbrains.kotlin.gradle.externalTargetApi +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin import org.jetbrains.kotlin.gradle.android.androidTargetPrototype -import org.jetbrains.kotlin.gradle.androidApplication +import org.jetbrains.kotlin.gradle.androidLibrary import org.jetbrains.kotlin.gradle.assumeAndroidSdkAvailable import org.jetbrains.kotlin.gradle.buildProjectWithMPP import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.kpm.idea.mavenCentralCacheRedirector +import org.jetbrains.kotlin.gradle.utils.getByType import org.junit.Test import kotlin.test.BeforeTest import kotlin.test.assertEquals +import kotlin.test.fail class ExternalAndroidTargetPrototypeSmokeTest { @@ -27,7 +32,7 @@ class ExternalAndroidTargetPrototypeSmokeTest { @Test fun `apply prototype - evaluate - compilations exist`() { val project = buildProjectWithMPP() - project.androidApplication { compileSdk = 31 } + project.androidLibrary { compileSdk = 31 } val androidTargetPrototype = project.multiplatformExtension.androidTargetPrototype() project.evaluate() @@ -40,7 +45,7 @@ class ExternalAndroidTargetPrototypeSmokeTest { @Test fun `apply prototype - evaluate - configurations can be resolved`() { val project = buildProjectWithMPP() - project.androidApplication { compileSdk = 31 } + project.androidLibrary { compileSdk = 31 } val androidTargetPrototype = project.multiplatformExtension.androidTargetPrototype() project.repositories.mavenLocal() @@ -52,4 +57,19 @@ class ExternalAndroidTargetPrototypeSmokeTest { compilation.runtimeDependencyFiles?.files } } + + @Test + fun `apply prototype - with maven publish plugin - publication exists`() { + val project = buildProjectWithMPP() + project.androidLibrary { compileSdk = 31 } + project.plugins.apply(MavenPublishPlugin::class.java) + project.multiplatformExtension.androidTargetPrototype() + project.evaluate() + + val publishing = project.extensions.getByType() + val androidPublication = publishing.publications.getByName("android") as MavenPublication + if (androidPublication.artifacts.size != 1) fail("Expected one artifact. Found ${androidPublication.artifacts}") + val aarArtifact = androidPublication.artifacts.first() + assertEquals("aar", aarArtifact.extension) + } } \ No newline at end of file