Fallback for publishing with no Gradle metadata
The following parts of the publications are affected:
* root publication
* with Gradle metadata, it is published and references the
other publications using the `available-at` mechanism
* without metadata opt-in, it is not published
* Kotlin metadata variant
* with Gradle metadata, it is added as an additional variant to the
platform-specific publications to allow the IDE to discover it
* without metadata opt-in, it is added as a dependency to the platform-
specific modules (like in old MPP)
This commit is contained in:
+112
@@ -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,
|
||||
"<groupId>com.example</groupId>",
|
||||
"<artifactId>sample-lib-metadata</artifactId>",
|
||||
"<version>1.0</version>"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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()
|
||||
|
||||
+8
-1
@@ -1,7 +1,14 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.example:sample-lib:1.0'
|
||||
}
|
||||
}
|
||||
|
||||
mainClassName = 'A'
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
import com.example.lib.CommonKt;
|
||||
|
||||
public class A {
|
||||
public void useKotlinClass() {
|
||||
public static void main(String[] args) {
|
||||
CommonKt.id(123);
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,5 +1,8 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
dependencies {
|
||||
compile 'com.example:sample-lib:1.0'
|
||||
}
|
||||
}
|
||||
|
||||
mainClassName = 'com.example.app.JvmAppKt'
|
||||
+3
@@ -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<KotlinTarget>
|
||||
internal set
|
||||
|
||||
internal var isGradleMetadataAvailable: Boolean = false
|
||||
}
|
||||
+4
-2
@@ -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<Project> =
|
||||
KotlinMultiplatformPlugin(
|
||||
buildOutputCleanupRegistry, fileResolver,
|
||||
instantiator, kotlinPluginVersion
|
||||
instantiator, kotlinPluginVersion, featurePreviews
|
||||
)
|
||||
|
||||
override val projectExtensionClass: KClass<out KotlinMultiplatformExtension>
|
||||
|
||||
+17
-3
@@ -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<Project> {
|
||||
|
||||
private class TargetFromPresetExtension(val targetsContainer: NamedDomainObjectCollection<KotlinTarget>) {
|
||||
@@ -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
|
||||
|
||||
+8
-5
@@ -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) {
|
||||
|
||||
+34
-10
@@ -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<UsageContext> {
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user