diff --git a/CHANGES.md b/CHANGES.md index 4fa05cb122..a1e3953351 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,25 @@ # Change log for kotlinx.coroutines +## Version 1.3.8 + +### New experimental features + +* Added `Flow.transformWhile operator` (#2065). +* Replaced `scanReduce` with `runningReduce` to be consistent with the Kotlin standard library (#2139). + +### Bug fixes and improvements + +* Improve user experience for the upcoming coroutines debugger (#2093, #2118, #2131). +* Debugger no longer retains strong references to the running coroutines (#2129). +* Fixed race in `Flow.asPublisher` (#2109). +* Fixed `ensureActive` to work in the empty context case to fix `IllegalStateException` when using flow from `suspend fun main` (#2044). +* Fixed a problem with `AbortFlowException` in the `Flow.first` operator to avoid erroneous `NoSuchElementException` (#2051). +* Fixed JVM dependency on Android annotations (#2075). +* Removed keep rules mentioning `kotlinx.coroutines.android` from core module (#2061 by @mkj-gram). +* Corrected some docs and examples (#2062, #2071, #2076, #2107, #2098, #2127, #2078, #2135). +* Improved the docs and guide on flow cancellation (#2043). +* Updated Gradle version to `6.3` (it only affects multiplatform artifacts in this release). + ## Version 1.3.7 * Fixed problem that triggered Android Lint failure (#2004). diff --git a/README.md b/README.md index 795616c88d..9f8bae65ba 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) -[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.3.7) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.3.7) +[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.3.8) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.3.8) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. This is a companion version for Kotlin `1.3.71` release. @@ -84,7 +84,7 @@ Add dependencies (you can also add other modules that you need): org.jetbrains.kotlinx kotlinx-coroutines-core - 1.3.7 + 1.3.8 ``` @@ -102,7 +102,7 @@ Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8' } ``` @@ -128,7 +128,7 @@ Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8") } ``` @@ -147,7 +147,7 @@ Make sure that you have either `jcenter()` or `mavenCentral()` in the list of re Core modules of `kotlinx.coroutines` are also available for [Kotlin/JS](#js) and [Kotlin/Native](#native). In common code that should get compiled for different platforms, add dependency to -[`kotlinx-coroutines-core-common`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-common/1.3.7/jar) +[`kotlinx-coroutines-core-common`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-common/1.3.8/jar) (follow the link to get the dependency declaration snippet). ### Android @@ -156,7 +156,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android) module as dependency when using `kotlinx.coroutines` on Android: ```groovy -implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7' +implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8' ``` This gives you access to Android [Dispatchers.Main] @@ -172,7 +172,7 @@ For more details see ["Optimization" section for Android](ui/kotlinx-coroutines- ### JS [Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) version of `kotlinx.coroutines` is published as -[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.3.7/jar) +[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.3.8/jar) (follow the link to get the dependency declaration snippet). You can also use [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) package via NPM. @@ -180,7 +180,7 @@ You can also use [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotli ### Native [Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) version of `kotlinx.coroutines` is published as -[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.3.7/jar) +[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.3.8/jar) (follow the link to get the dependency declaration snippet). Only single-threaded code (JS-style) on Kotlin/Native is currently supported. diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt new file mode 100644 index 0000000000..fd3d3cdb96 --- /dev/null +++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package benchmarks.flow + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.internal.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit + +@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Fork(value = 1) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +open class TakeWhileBenchmark { + @Param("1", "10", "100", "1000") + private var size: Int = 0 + + private suspend inline fun Flow.consume() = + filter { it % 2L != 0L } + .map { it * it }.count() + + @Benchmark + fun baseline() = runBlocking { + (0L until size).asFlow().consume() + } + + @Benchmark + fun takeWhileDirect() = runBlocking { + (0L..Long.MAX_VALUE).asFlow().takeWhileDirect { it < size }.consume() + } + + @Benchmark + fun takeWhileViaCollectWhile() = runBlocking { + (0L..Long.MAX_VALUE).asFlow().takeWhileViaCollectWhile { it < size }.consume() + } + + // Direct implementation by checking predicate and throwing AbortFlowException + private fun Flow.takeWhileDirect(predicate: suspend (T) -> Boolean): Flow = unsafeFlow { + try { + collect { value -> + if (predicate(value)) emit(value) + else throw AbortFlowException(this) + } + } catch (e: AbortFlowException) { + e.checkOwnership(owner = this) + } + } + + // Essentially the same code, but reusing the logic via collectWhile function + private fun Flow.takeWhileViaCollectWhile(predicate: suspend (T) -> Boolean): Flow = unsafeFlow { + // This return is needed to work around a bug in JS BE: KT-39227 + return@unsafeFlow collectWhile { value -> + if (predicate(value)) { + emit(value) + true + } else { + false + } + } + } +} diff --git a/build.gradle b/build.gradle index bc4dd36afc..a758393b6c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ import org.jetbrains.kotlin.konan.target.HostManager +import org.gradle.util.VersionNumber apply plugin: 'jdk-convention' apply from: rootProject.file("gradle/experimental.gradle") @@ -79,6 +80,11 @@ buildscript { import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +// Hierarchical project structures are not fully supported in 1.3.7x MPP, enable conditionally for 1.4.x +if (VersionNumber.parse(kotlin_version) > VersionNumber.parse("1.3.79")) { + ext.set("kotlin.mpp.enableGranularSourceSetsMetadata", "true") +} + // todo:KLUDGE: This is needed to workaround dependency resolution between Java and MPP modules def configureKotlinJvmPlatform(configuration) { configuration.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm) @@ -126,6 +132,7 @@ apiValidation { if (build_snapshot_train) { ignoredProjects.remove("site") ignoredProjects.remove("example-frontend-js") + ignoredProjects.add("kotlinx-coroutines-core") } ignoredPackages += "kotlinx.coroutines.internal" } @@ -192,6 +199,8 @@ if (build_snapshot_train) { exclude '**/*definitely/not/kotlinx*' // Disable because of KT-11567 in 1.4 exclude '**/*CasesPublicAPITest*' + // Kotlin + exclude '**/*PrecompiledDebugProbesTest*' } } diff --git a/coroutines-guide.md b/coroutines-guide.md index 0b7b842acb..4b3c09c40f 100644 --- a/coroutines-guide.md +++ b/coroutines-guide.md @@ -47,7 +47,7 @@ The main coroutines guide has moved to the [docs folder](docs/coroutines-guide.m * [Suspending functions](docs/flow.md#suspending-functions) * [Flows](docs/flow.md#flows) * [Flows are cold](docs/flow.md#flows-are-cold) - * [Flow cancellation](docs/flow.md#flow-cancellation) + * [Flow cancellation basics](docs/flow.md#flow-cancellation-basics) * [Flow builders](docs/flow.md#flow-builders) * [Intermediate flow operators](docs/flow.md#intermediate-flow-operators) * [Transform operator](docs/flow.md#transform-operator) @@ -79,6 +79,8 @@ The main coroutines guide has moved to the [docs folder](docs/coroutines-guide.m * [Successful completion](docs/flow.md#successful-completion) * [Imperative versus declarative](docs/flow.md#imperative-versus-declarative) * [Launching flow](docs/flow.md#launching-flow) + * [Flow cancellation checks](docs/flow.md#flow-cancellation-checks) + * [Making busy flow cancellable](docs/flow.md#making-busy-flow-cancellable) * [Flow and Reactive Streams](docs/flow.md#flow-and-reactive-streams) * [Channels](docs/channels.md#channels) diff --git a/docs/basics.md b/docs/basics.md index f171c2c29e..cb64328676 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -338,7 +338,7 @@ import kotlinx.coroutines.* fun main() = runBlocking { repeat(100_000) { // launch a lot of coroutines launch { - delay(1000L) + delay(5000L) print(".") } } @@ -351,7 +351,7 @@ fun main() = runBlocking { -It launches 100K coroutines and, after a second, each coroutine prints a dot. +It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot. Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error) diff --git a/docs/flow.md b/docs/flow.md index 0405e80df4..143f9e9300 100644 --- a/docs/flow.md +++ b/docs/flow.md @@ -10,7 +10,7 @@ * [Suspending functions](#suspending-functions) * [Flows](#flows) * [Flows are cold](#flows-are-cold) - * [Flow cancellation](#flow-cancellation) + * [Flow cancellation basics](#flow-cancellation-basics) * [Flow builders](#flow-builders) * [Intermediate flow operators](#intermediate-flow-operators) * [Transform operator](#transform-operator) @@ -42,6 +42,8 @@ * [Successful completion](#successful-completion) * [Imperative versus declarative](#imperative-versus-declarative) * [Launching flow](#launching-flow) + * [Flow cancellation checks](#flow-cancellation-checks) + * [Making busy flow cancellable](#making-busy-flow-cancellable) * [Flow and Reactive Streams](#flow-and-reactive-streams) @@ -267,12 +269,10 @@ This is a key reason the `simple` function (which returns a flow) is not marked By itself, `simple()` call returns quickly and does not wait for anything. The flow starts every time it is collected, that is why we see "Flow started" when we call `collect` again. -### Flow cancellation - -Flow adheres to the general cooperative cancellation of coroutines. However, flow infrastructure does not introduce -additional cancellation points. It is fully transparent for cancellation. As usual, flow collection can be -cancelled when the flow is suspended in a cancellable suspending function (like [delay]), and cannot be cancelled otherwise. +### Flow cancellation basics +Flow adheres to the general cooperative cancellation of coroutines. As usual, flow collection can be +cancelled when the flow is suspended in a cancellable suspending function (like [delay]). The following example shows how the flow gets cancelled on a timeout when running in a [withTimeoutOrNull] block and stops executing its code: @@ -316,6 +316,8 @@ Done +See [Flow cancellation checks](#flow-cancellation-checks) section for more details. + ### Flow builders The `flow { ... }` builder from the previous examples is the most basic one. There are other builders for @@ -695,7 +697,7 @@ This code produces the following exception: ```text Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated: Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323], - but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, DefaultDispatcher]. + but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default]. Please refer to 'flow' documentation or use 'flowOn' instead at ... ``` @@ -1777,6 +1779,127 @@ as cancellation and structured concurrency serve this purpose. Note that [launchIn] also returns a [Job], which can be used to [cancel][Job.cancel] the corresponding flow collection coroutine only without cancelling the whole scope or to [join][Job.join] it. +### Flow cancellation checks + +For convenience, the [flow] builder performs additional [ensureActive] checks for cancellation on each emitted value. +It means that a busy loop emitting from a `flow { ... }` is cancellable: + +
+ +```kotlin +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* + +//sampleStart +fun foo(): Flow = flow { + for (i in 1..5) { + println("Emitting $i") + emit(i) + } +} + +fun main() = runBlocking { + foo().collect { value -> + if (value == 3) cancel() + println(value) + } +} +//sampleEnd +``` + +
+ +> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-37.kt). + +We get only numbers up to 3 and a [CancellationException] after trying to emit number 4: + +```text +Emitting 1 +1 +Emitting 2 +2 +Emitting 3 +3 +Emitting 4 +Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c +``` + + + +However, most other flow operators do not do additional cancellation checks on their own for performance reasons. +For example, if you use [IntRange.asFlow] extension to write the same busy loop and don't suspend anywhere, +then there are no checks for cancellation: + +
+ +```kotlin +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* + +//sampleStart +fun main() = runBlocking { + (1..5).asFlow().collect { value -> + if (value == 3) cancel() + println(value) + } +} +//sampleEnd +``` + +
+ +> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-38.kt). + +All numbers from 1 to 5 are collected and cancellation gets detected only before return from `runBlocking`: + +```text +1 +2 +3 +4 +5 +Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@3327bd23 +``` + + + +#### Making busy flow cancellable + +In the case where you have a busy loop with coroutines you must explicitly check for cancellation. +You can add `.onEach { currentCoroutineContext().ensureActive() }`, but there is a ready-to-use +[cancellable] operator provided to do that: + +
+ +```kotlin +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* + +//sampleStart +fun main() = runBlocking { + (1..5).asFlow().cancellable().collect { value -> + if (value == 3) cancel() + println(value) + } +} +//sampleEnd +``` + +
+ +> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-39.kt). + +With the `cancellable` operator only the numbers from 1 to 3 are collected: + +```text +1 +2 +3 +Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@5ec0a365 +``` + + + ### Flow and Reactive Streams For those who are familiar with [Reactive Streams](https://www.reactive-streams.org/) or reactive frameworks such as RxJava and project Reactor, @@ -1813,6 +1936,8 @@ Integration modules include conversions from and to `Flow`, integration with Rea [Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html [Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html [Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html +[ensureActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html +[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html [Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html [flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html @@ -1845,4 +1970,6 @@ Integration modules include conversions from and to `Flow`, integration with Rea [catch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/catch.html [onCompletion]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-completion.html [launchIn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html +[IntRange.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/kotlin.ranges.-int-range/as-flow.html +[cancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/cancellable.html diff --git a/gradle.properties b/gradle.properties index 0a45ecd693..6a1ae653f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ # # Kotlin -version=1.3.7-SNAPSHOT +version=1.3.8-SNAPSHOT group=org.jetbrains.kotlinx kotlin_version=1.3.71 @@ -51,3 +51,12 @@ jekyll_version=4.0 # JS IR baceknd sometimes crashes with out-of-memory # TODO: Remove once KT-37187 is fixed org.gradle.jvmargs=-Xmx2g + +# Workaround for Bintray treating .sha512 files as artifacts +# https://github.com/gradle/gradle/issues/11412 +systemProp.org.gradle.internal.publish.checksums.insecure=true + +# This is commented out, and the property is set conditionally in build.gradle, because 1.3.71 doesn't work with it. +# Once this property is set by default in new versions or 1.3.71 is dropped, either uncomment or remove this line. +#kotlin.mpp.enableGranularSourceSetsMetadata=true +kotlin.mpp.enableCompatibilityMetadataVariant=true \ No newline at end of file diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle index 2b00d6fc82..93d371a21f 100644 --- a/gradle/compile-js-multiplatform.gradle +++ b/gradle/compile-js-multiplatform.gradle @@ -5,15 +5,22 @@ apply from: rootProject.file('gradle/node-js.gradle') kotlin { - targets { - fromPreset(presets.js, 'js') { - // Enable built-in test runner only for IR target. - // These runners don't support changing js module name change. - if (js.hasProperty("irTarget") && irTarget != null) { - irTarget.nodejs() - irTarget.compilations['main']?.dependencies { - api "org.jetbrains.kotlinx:atomicfu-js:$atomicfu_version" - } + js { + // In 1.3.7x js() has not member `moduleName` + // In 1.4.x it has and allow to safety set compiler output file name and does not break test integration + if (it.hasProperty("moduleName")) { + moduleName = project.name + } + + // In 1.3.7x js() has not member `irTarget` + // In 1.4.x it has in `both` and `legacy` mode and js() is of type `KotlinJsTarget` + // `irTarget` is non-null in `both` mode + // and contains appropriate `irTarget` with type `KotlinJsIrTarget` + // `irTarget` is null in `legacy` mode + if (it.hasProperty("irTarget") && it.irTarget != null) { + irTarget.nodejs() + irTarget.compilations['main']?.dependencies { + api "org.jetbrains.kotlinx:atomicfu-js:$atomicfu_version" } } } @@ -32,7 +39,15 @@ kotlin { // When source sets are configured apply from: rootProject.file('gradle/test-mocha-js.gradle') -compileKotlinJs { +def compileJsLegacy = tasks.hasProperty("compileKotlinJsLegacy") + ? compileKotlinJsLegacy + : compileKotlinJs + +def compileTestJsLegacy = tasks.hasProperty("compileTestKotlinJsLegacy") + ? compileTestKotlinJsLegacy + : compileTestKotlinJs + +compileJsLegacy.configure { kotlinOptions.metaInfo = true kotlinOptions.sourceMap = true kotlinOptions.moduleKind = 'umd' @@ -44,20 +59,20 @@ compileKotlinJs { } } -compileTestKotlinJs { +compileTestJsLegacy.configure { kotlinOptions.metaInfo = true kotlinOptions.sourceMap = true kotlinOptions.moduleKind = 'umd' } -task populateNodeModules(type: Copy, dependsOn: compileTestKotlinJs) { +task populateNodeModules(type: Copy, dependsOn: compileTestJsLegacy) { // we must copy output that is transformed by atomicfu - from(kotlin.targets.js.compilations.main.output.allOutputs) + from(kotlin.js().compilations.main.output.allOutputs) into "$node.nodeModulesDir/node_modules" - def configuration = configurations.hasProperty("legacyjsTestRuntimeClasspath") - ? configurations.legacyjsTestRuntimeClasspath + def configuration = configurations.hasProperty("jsLegacyTestRuntimeClasspath") + ? configurations.jsLegacyTestRuntimeClasspath : configurations.jsTestRuntimeClasspath from(files { diff --git a/gradle/publish-bintray.gradle b/gradle/publish-bintray.gradle index ee9337f8c8..b36c79763d 100644 --- a/gradle/publish-bintray.gradle +++ b/gradle/publish-bintray.gradle @@ -1,3 +1,5 @@ +import org.gradle.util.VersionNumber + /* * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @@ -11,6 +13,7 @@ apply plugin: 'maven-publish' def isMultiplatform = project.name == "kotlinx-coroutines-core" def isBom = project.name == "kotlinx-coroutines-bom" +def isKotlin137x = VersionNumber.parse(kotlin_version) <= VersionNumber.parse("1.3.79") if (!isBom) { apply plugin: "com.github.johnrengelman.shadow" @@ -64,8 +67,8 @@ publishing { publications.all { MavenCentralKt.configureMavenCentralMetadata(pom, project) - // add empty javadocs (no need for MPP root publication which publishes only pom file) - if (it.name != 'kotlinMultiplatform' && !isBom) { + // add empty javadocs + if (!isBom && it.name != "kotlinMultiplatform") { it.artifact(javadocJar) } @@ -73,27 +76,42 @@ publishing { def type = it.name switch (type) { case 'kotlinMultiplatform': - it.artifactId = "$project.name-native" + // With Kotlin 1.4 & HMPP, the root module should have no suffix in the ID, but for compatibility with + // the consumers who can't read Gradle module metadata, we publish the JVM artifacts in it, too + it.artifactId = isKotlin137x ? "$project.name-native" : project.name + if (!isKotlin137x) { + apply from: "$rootDir/gradle/publish-mpp-root-module-in-platform.gradle" + publishPlatformArtifactsInRootModule(publications["jvm"]) + } break case 'metadata': - it.artifactId = "$project.name-common" + // As the old -common dependencies will fail to resolve with Gradle module metadata, rename the module + // to '*-metadata' so that the resolution failure are more clear + it.artifactId = isKotlin137x ? "$project.name-common" : "$project.name-metadata" break case 'jvm': - it.artifactId = "$project.name" + it.artifactId = isKotlin137x ? project.name : "$project.name-jvm" break case 'js': case 'native': it.artifactId = "$project.name-$type" break } - - // disable metadata everywhere, but in native and js modules - if (type == 'maven' || type == 'metadata' || type == 'jvm') { - moduleDescriptorGenerator = null + // Hierarchical project structures are not fully supported in 1.3.7x MPP + if (isKotlin137x) { + // disable metadata everywhere, but in native and js modules + if (type == 'maven' || type == 'metadata' || type == 'jvm') { + moduleDescriptorGenerator = null + } } + } } +tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication"}.configureEach { + dependsOn(tasks["generatePomFileForJvmPublication"]) +} + task publishDevelopSnapshot() { def branch = System.getenv('currentBranch') if (branch == "develop") { diff --git a/gradle/publish-mpp-root-module-in-platform.gradle b/gradle/publish-mpp-root-module-in-platform.gradle new file mode 100644 index 0000000000..8bc0b502db --- /dev/null +++ b/gradle/publish-mpp-root-module-in-platform.gradle @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2020 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + + +/* + * Publish the platform JAR and POM so that consumers who depend on this module and can't read Gradle module metadata + * can still get the platform artifact and transitive dependencies from the POM. + * + * See the full rationale here https://youtrack.jetbrains.com/issue/KMM-237#focus=streamItem-27-4115233.0-0 + */ +project.ext.publishPlatformArtifactsInRootModule = { platformPublication -> + def platformPomBuilder = null + + platformPublication.pom.withXml { platformPomBuilder = asString() } + + publishing.publications.kotlinMultiplatform { + platformPublication.artifacts.forEach { + artifact(it) + } + + pom.withXml { + def pomStringBuilder = asString() + pomStringBuilder.setLength(0) + // The platform POM needs its artifact ID replaced with the artifact ID of the root module: + def platformPomString = platformPomBuilder.toString() + platformPomString.eachLine { line -> + if (!line.contains("