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:
Sergey Igushkin
2018-09-18 13:16:53 +03:00
parent b388f7fde0
commit 05301e37d8
9 changed files with 191 additions and 23 deletions
@@ -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()
@@ -1,7 +1,14 @@
plugins {
id 'java'
id 'application'
}
repositories {
jcenter()
}
dependencies {
implementation 'com.example:sample-lib:1.0'
}
}
mainClassName = 'A'
@@ -6,7 +6,7 @@
import com.example.lib.CommonKt;
public class A {
public void useKotlinClass() {
public static void main(String[] args) {
CommonKt.id(123);
}
}
@@ -1,5 +1,8 @@
apply plugin: 'kotlin'
apply plugin: 'application'
dependencies {
compile 'com.example:sample-lib:1.0'
}
}
mainClassName = 'com.example.app.JvmAppKt'
@@ -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
}
@@ -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>
@@ -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
@@ -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) {
@@ -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()
}