From 05301e37d83b87c9355a35cfa4754188f9e725bd Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Tue, 18 Sep 2018 13:16:53 +0300 Subject: [PATCH] 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) --- .../kotlin/gradle/NewMultiplatformIT.kt | 112 ++++++++++++++++++ .../sample-app-without-kotlin/build.gradle | 9 +- .../src/main/java/A.java | 2 +- .../sample-old-style-app/app-jvm/build.gradle | 5 +- .../dsl/KotlinMultiplatformExtension.kt | 3 + .../gradle/plugin/KotlinPluginWrapper.kt | 6 +- .../plugin/mpp/KotlinMultiplatformPlugin.kt | 20 +++- .../plugin/mpp/KotlinSoftwareComponent.kt | 13 +- .../kotlin/gradle/plugin/mpp/kotlinTargets.kt | 44 +++++-- 9 files changed, 191 insertions(+), 23 deletions(-) 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() }