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 f9571cadee2..e15ac44f6a8 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
@@ -439,6 +439,66 @@ class NewMultiplatformIT : BaseGradleIT() {
}
}
+ @Test
+ fun testPublishWithoutGradleMetadata() {
+ val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")
+
+ with (libProject) {
+ setupWorkingDir()
+ projectDir.resolve("settings.gradle").modify { it.replace("enableFeaturePreview", "// enableFeaturePreview") }
+
+ build("publish") {
+ assertSuccessful()
+ val groupRepoDir = "repo/com/example"
+
+ // No root publication:
+ assertNoSuchFile("$groupRepoDir/sample-lib")
+
+ // Check that the platform publications have the metadata dependency
+ listOf("jvm6", "nodejs", "wasm32", nativeHostTargetName.toLowerCase()).forEach { targetAppendix ->
+ val targetPomPath = "$groupRepoDir/sample-lib-$targetAppendix/1.0/sample-lib-$targetAppendix-1.0.pom"
+ assertFileContains(
+ targetPomPath,
+ "com.example",
+ "sample-lib-metadata",
+ "1.0"
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testDependenciesOnMppLibraryPartsWithNoMetadata() {
+ val repoDir = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
+ setupWorkingDir()
+ projectDir.resolve("settings.gradle").modify { it.replace("enableFeaturePreview", "// enableFeaturePreview") }
+ build("publish") { assertSuccessful() }
+ projectDir.resolve("repo")
+ }
+
+ with(Project("sample-app", gradleVersion, "new-mpp-lib-and-app")) {
+ setupWorkingDir()
+ gradleBuildScript().modify {
+ it.replace("implementation 'com.example:sample-lib:1.0'", "implementation 'com.example:sample-lib-metadata:1.0'") + "\n" + """
+ repositories { maven { url '${repoDir.toURI()}' } }
+
+ dependencies {
+ allJvmImplementation 'com.example:sample-lib-jvm6:1.0'
+ nodeJsMainImplementation 'com.example:sample-lib-nodejs:1.0'
+ wasm32MainImplementation 'com.example:sample-lib-wasm32:1.0'
+ ${nativeHostTargetName}MainImplementation 'com.example:sample-lib-${nativeHostTargetName.toLowerCase()}:1.0'
+ }
+ """.trimIndent()
+ }
+
+ build("assemble") {
+ assertSuccessful()
+ assertTasksExecuted(listOf("Jvm6", "NodeJs", "Wasm32", nativeHostTargetName.capitalize()).map { ":compileKotlin$it" })
+ }
+ }
+ }
+
@Test
fun testPublishingOnlySupportedNativeTargets() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
val (publishedVariant, nonPublishedVariant) = when {
@@ -462,6 +522,58 @@ class NewMultiplatformIT : BaseGradleIT() {
}
}
+ @Test
+ fun testNonMppConsumersOfLibraryPublishedWithNoMetadataOptIn() {
+ val repoDir = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
+ setupWorkingDir()
+ projectDir.resolve("settings.gradle").modify { it.replace("enableFeaturePreview", "// enableFeaturePreview") }
+ build("publish") { assertSuccessful() }
+ projectDir.resolve("repo")
+ }
+
+ with(Project("sample-old-style-app", gradleVersion, "new-mpp-lib-and-app")) {
+ setupWorkingDir()
+ gradleBuildScript().appendText("\nallprojects { repositories { maven { url '${repoDir.toURI()}' } } }")
+ gradleBuildScript("app-common").modify { it.replace("com.example:sample-lib:", "com.example:sample-lib-metadata:") }
+ gradleBuildScript("app-jvm").modify { it.replace("com.example:sample-lib:", "com.example:sample-lib-jvm6:") }
+ gradleBuildScript("app-js").modify { it.replace("com.example:sample-lib:", "com.example:sample-lib-nodejs:") }
+
+ build("assemble", "run") {
+ assertSuccessful()
+ assertTasksExecuted(":app-common:compileKotlinCommon", ":app-jvm:compileKotlin", ":app-jvm:run", ":app-js:compileKotlin2Js")
+ }
+
+ // Then run again without even reading the metadata from the repo:
+ projectDir.resolve("settings.gradle").modify { it.replace("enableFeaturePreview('GRADLE_METADATA')", "") }
+
+ build("assemble", "run", "--rerun-tasks") {
+ assertSuccessful()
+ assertTasksExecuted(":app-common:compileKotlinCommon", ":app-jvm:compileKotlin", ":app-jvm:run", ":app-js:compileKotlin2Js")
+ }
+ }
+
+ with(Project("sample-app-without-kotlin", gradleVersion, "new-mpp-lib-and-app")) {
+ setupWorkingDir()
+ gradleBuildScript().modify {
+ it.replace("com.example:sample-lib:1.0", "com.example:sample-lib-jvm6:1.0") + "\n" + """
+ repositories { maven { url '${repoDir.toURI()}' } }
+ """.trimIndent()
+ }
+
+ build("run") {
+ assertSuccessful()
+ assertTasksExecuted(":compileJava", ":run")
+ }
+
+ // Then run again without even reading the metadata from the repo:
+ projectDir.resolve("settings.gradle").modify { it.replace("enableFeaturePreview('GRADLE_METADATA')", "") }
+ build("run", "--rerun-tasks") {
+ assertSuccessful()
+ assertTasksExecuted(":compileJava", ":run")
+ }
+ }
+ }
+
@Test
fun testOptionalExpectations() = with(Project("new-mpp-lib-with-tests", gradleVersion)) {
setupWorkingDir()
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/build.gradle
index 1fba99ae41c..10758e3cc01 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/build.gradle
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/build.gradle
@@ -1,7 +1,14 @@
plugins {
id 'java'
+ id 'application'
+}
+
+repositories {
+ jcenter()
}
dependencies {
implementation 'com.example:sample-lib:1.0'
-}
\ No newline at end of file
+}
+
+mainClassName = 'A'
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/src/main/java/A.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/src/main/java/A.java
index bfc074b5232..3145d9f9997 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/src/main/java/A.java
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/src/main/java/A.java
@@ -6,7 +6,7 @@
import com.example.lib.CommonKt;
public class A {
- public void useKotlinClass() {
+ public static void main(String[] args) {
CommonKt.id(123);
}
}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-jvm/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-jvm/build.gradle
index 11006e79e1a..b0f14c7a72c 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-jvm/build.gradle
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-old-style-app/app-jvm/build.gradle
@@ -1,5 +1,8 @@
apply plugin: 'kotlin'
+apply plugin: 'application'
dependencies {
compile 'com.example:sample-lib:1.0'
-}
\ No newline at end of file
+}
+
+mainClassName = 'com.example.app.JvmAppKt'
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt
index bcdad6b052e..9459a523c4e 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle.dsl
+import org.gradle.api.InvalidUserCodeException
import org.gradle.api.NamedDomainObjectCollection
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset
@@ -15,4 +16,6 @@ open class KotlinMultiplatformExtension : KotlinProjectExtension() {
lateinit var targets: NamedDomainObjectCollection
internal set
+
+ internal var isGradleMetadataAvailable: Boolean = false
}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
index 9cd13b38a70..424f335b3cf 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
+import org.gradle.api.internal.FeaturePreviews
import org.gradle.api.internal.file.FileResolver
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
@@ -132,12 +133,13 @@ open class Kotlin2JsPluginWrapper @Inject constructor(
open class KotlinMultiplatformPluginWrapper @Inject constructor(
fileResolver: FileResolver,
private val instantiator: Instantiator,
- private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry
+ private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry,
+ private val featurePreviews: FeaturePreviews
): KotlinBasePluginWrapper(fileResolver) {
override fun getPlugin(project: Project, kotlinGradleBuildServices: KotlinGradleBuildServices): Plugin =
KotlinMultiplatformPlugin(
buildOutputCleanupRegistry, fileResolver,
- instantiator, kotlinPluginVersion
+ instantiator, kotlinPluginVersion, featurePreviews
)
override val projectExtensionClass: KClass
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 cf9acf7b77f..31e0a35d77b 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
@@ -11,12 +11,14 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
+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.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal
+import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
import org.gradle.internal.cleanup.BuildOutputCleanupRegistry
import org.gradle.internal.reflect.Instantiator
import org.gradle.jvm.tasks.Jar
@@ -34,7 +36,8 @@ class KotlinMultiplatformPlugin(
private val buildOutputCleanupRegistry: BuildOutputCleanupRegistry,
private val fileResolver: FileResolver,
private val instantiator: Instantiator,
- private val kotlinPluginVersion: String
+ private val kotlinPluginVersion: String,
+ private val featurePreviews: FeaturePreviews // TODO get rid of this internal API usage once we don't need it
) : Plugin {
private class TargetFromPresetExtension(val targetsContainer: NamedDomainObjectCollection) {
@@ -64,6 +67,11 @@ class KotlinMultiplatformPlugin(
presets = project.container(KotlinTargetPreset::class.java)
addExtension("presets", presets)
+
+ isGradleMetadataAvailable =
+ featurePreviews.activeFeatures.find { it.name == "GRADLE_METADATA" }?.let { metadataFeature ->
+ featurePreviews.isFeatureEnabled(metadataFeature)
+ } ?: true // the feature entry will be gone once the feature is stable
}
setupDefaultPresets(project)
@@ -98,8 +106,9 @@ class KotlinMultiplatformPlugin(
val kotlinSoftwareComponent = KotlinSoftwareComponent(project, "kotlin", targets)
project.extensions.configure(PublishingExtension::class.java) { publishing ->
- // The root publication.
- publishing.publications.create("kotlinMultiplatform", MavenPublication::class.java).apply {
+
+ // The root publication that references the platform specific publications as its variants:
+ val rootPublication = publishing.publications.create("kotlinMultiplatform", MavenPublication::class.java).apply {
from(kotlinSoftwareComponent)
(this as MavenPublicationInternal).publishWithOriginalFileName()
artifactId = project.name
@@ -107,6 +116,11 @@ class KotlinMultiplatformPlugin(
version = project.version.toString()
}
+ // Publish the root publication only if Gradle metadata publishing is enabled:
+ project.tasks.withType(AbstractPublishToMaven::class.java).all { publishTask ->
+ publishTask.onlyIf { publishTask.publication != rootPublication || project.multiplatformExtension!!.isGradleMetadataAvailable }
+ }
+
// Create separate publications for all publishable targets
targets.matching { it.publishable }.all { target ->
val variant = target.component as KotlinVariant
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 1a33c74c663..d7fd89719c4 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
@@ -6,15 +6,16 @@
package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Project
-import org.gradle.api.artifacts.*
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.DependencyConstraint
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.artifacts.PublishArtifact
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.attributes.Usage
import org.gradle.api.capabilities.Capability
-import org.gradle.api.component.ComponentWithCoordinates
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.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
import org.jetbrains.kotlin.gradle.plugin.usageByName
@@ -40,11 +41,13 @@ object NativeUsage {
}
internal class KotlinPlatformUsageContext(
- val project: Project,
val kotlinTarget: KotlinTarget,
private val usage: Usage,
- val dependencyConfigurationName: String
+ val dependencyConfigurationName: String,
+ private val publishWithGradleMetadata: Boolean
) : UsageContext {
+ private val project: Project get() = kotlinTarget.project
+
override fun getUsage(): Usage = usage
override fun getName(): String = kotlinTarget.targetName + when (dependencyConfigurationName) {
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 244aeda1cf7..64f6bd4269a 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
@@ -19,6 +19,8 @@ import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.util.ConfigureUtil
import org.gradle.util.WrapUtil
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@@ -49,28 +51,50 @@ abstract class AbstractKotlinTarget (
get() = true
override val component: KotlinTargetComponent by lazy {
- if (isGradleVersionAtLeast(4, 7))
- KotlinVariantWithCoordinates(this)
- else
+ if (isGradleVersionAtLeast(4, 7)) {
+ createKotlinTargetComponent()
+ } else {
KotlinVariant(this)
+ }
+ }
+
+ private fun createKotlinTargetComponent(): KotlinTargetComponent {
+ val kotlinExtension = project.kotlinExtension as? KotlinMultiplatformExtension
+ ?: return KotlinVariantWithCoordinates(this)
+
+ if (targetName == KotlinMultiplatformPlugin.METADATA_TARGET_NAME)
+ return KotlinVariantWithCoordinates(this)
+
+ val separateMetadataTarget = kotlinExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME)
+
+ return if (kotlinExtension.isGradleMetadataAvailable) {
+ KotlinVariantWithMetadataVariant(this, 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)
+ }
}
override fun createUsageContexts(): 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);
// These Java usages should not be replaced with the custom Kotlin usages.
-
return listOfNotNull(
KotlinPlatformUsageContext(
- project, this, project.usageByName(JAVA_API),
- apiElementsConfigurationName
+ this, project.usageByName(JAVA_API),
+ apiElementsConfigurationName, publishWithKotlinMetadata
),
- if (compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) is KotlinCompilationToRunnableFiles)
+ if (compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) is KotlinCompilationToRunnableFiles) {
KotlinPlatformUsageContext(
- project, this, project.usageByName(JAVA_RUNTIME_JARS),
- runtimeElementsConfigurationName
+ this, project.usageByName(JAVA_RUNTIME_JARS),
+ runtimeElementsConfigurationName, publishWithKotlinMetadata
)
- else null
+ } else null
).toSet()
}