Skip to content

Commit

Permalink
Merge kotlinx-coroutines-core and kotlinx-coroutines-jdk8 modules (Ko…
Browse files Browse the repository at this point in the history
…tlin#3415)

* Configure source sets and compilations for module merger
* Programmatically split the structure of compilation for the sake of separate compilation and dependencies
* Add new integration test
* Merge ABI signatures
* Exclude jdk8 from animal sniffer

Fixes Kotlin#3268
  • Loading branch information
qwwdfsad authored Oct 12, 2022
1 parent 1656a0d commit f3527c9
Show file tree
Hide file tree
Showing 24 changed files with 128 additions and 115 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ suspend fun main() = coroutineScope {
* [core/jvm](kotlinx-coroutines-core/jvm/) — additional core features available on Kotlin/JVM:
* [Dispatchers.IO] dispatcher for blocking coroutines;
* [Executor.asCoroutineDispatcher][asCoroutineDispatcher] extension, custom thread pools, and more.
* Integrations with `CompletableFuture` and JVM-specific extensions.
* [core/js](kotlinx-coroutines-core/js/) — additional core features available on Kotlin/JS:
* Integration with `Promise` via [Promise.await] and [promise] builder;
* Integration with `Window` via [Window.asCoroutineDispatcher], etc.
Expand All @@ -56,7 +57,7 @@ suspend fun main() = coroutineScope {
* [ui](ui/README.md) — modules that provide coroutine dispatchers for various single-threaded UI libraries:
* Android, JavaFX, and Swing.
* [integration](integration/README.md) — modules that provide integration with various asynchronous callback- and future-based libraries:
* JDK8 [CompletionStage.await], Guava [ListenableFuture.await], and Google Play Services [Task.await];
* Guava [ListenableFuture.await], and Google Play Services [Task.await];
* SLF4J MDC integration via [MDCContext].

## Documentation
Expand Down Expand Up @@ -259,9 +260,6 @@ See [Contributing Guidelines](CONTRIBUTING.md).

<!--- MODULE kotlinx-coroutines-jdk8 -->
<!--- INDEX kotlinx.coroutines.future -->

[CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html

<!--- MODULE kotlinx-coroutines-guava -->
<!--- INDEX kotlinx.coroutines.guava -->

Expand Down
6 changes: 4 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ def core_docs_url = "https://kotlinlang.org/api/kotlinx.coroutines/$coreModule/"
def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/htmlPartial/package-list"
apply plugin: "org.jetbrains.dokka"

configure(subprojects.findAll { !unpublished.contains(it.name) && it.name != coreModule }) {
configure(subprojects.findAll { !unpublished.contains(it.name)
&& it.name != coreModule
&& it.name != jdk8ObsoleteModule}) {
if (it.name != 'kotlinx-coroutines-bom') {
apply from: rootProject.file('gradle/dokka.gradle.kts')
}
Expand All @@ -232,7 +234,7 @@ configure(subprojects.findAll { !unpublished.contains(it.name) && it.name != cor

configure(subprojects.findAll { !unpublished.contains(it.name) }) {
if (it.name != "kotlinx-coroutines-bom") {
if (it.name != coreModule) {
if (it.name != coreModule && it.name != jdk8ObsoleteModule) {
tasks.withType(DokkaTaskPartial.class) {
dokkaSourceSets.configureEach {
externalDocumentationLink {
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Projects.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fun Project.version(target: String): String =
property("${target}_version") as String

val coreModule = "kotlinx-coroutines-core"
val jdk8ObsoleteModule = "kotlinx-coroutines-jdk8"
val testModule = "kotlinx-coroutines-test"

val multiplatform = setOf(coreModule, testModule)
Expand Down
14 changes: 14 additions & 0 deletions buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ configure(subprojects) {
signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature")
signature("org.codehaus.mojo.signature:java17:1.0@signature")
}

if (project.name == coreModule) {
// Specific files so nothing from core is accidentally skipped
tasks.withType<AnimalSniffer>().configureEach {
exclude("**/future/FutureKt*")
exclude("**/future/ContinuationHandler*")
exclude("**/future/CompletableFutureCoroutine*")

exclude("**/stream/StreamKt*")
exclude("**/stream/StreamFlow*")

exclude("**/time/TimeKt*")
}
}
}
}

Expand Down
10 changes: 5 additions & 5 deletions integration-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ dependencies {
}

sourceSets {
// Test that relies on Guava to reflectively check all Throwable subclasses in coroutines
withGuavaTest {
// An assortment of tests for behavior of the core coroutines module on JVM
jvmCoreTest {
kotlin
compileClasspath += sourceSets.test.runtimeClasspath
runtimeClasspath += sourceSets.test.runtimeClasspath
Expand Down Expand Up @@ -86,9 +86,9 @@ compileDebugAgentTestKotlin {
}
}

task withGuavaTest(type: Test) {
task jvmCoreTest(type: Test) {
environment "version", coroutines_version
def sourceSet = sourceSets.withGuavaTest
def sourceSet = sourceSets.jvmCoreTest
testClassesDirs = sourceSet.output.classesDirs
classpath = sourceSet.runtimeClasspath
}
Expand Down Expand Up @@ -128,5 +128,5 @@ compileTestKotlin {
}

check {
dependsOn([withGuavaTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines

import kotlinx.coroutines.future.*
import org.junit.Test
import kotlin.test.*

/*
* Integration test that ensures signatures from both the jdk8 and the core source sets of the kotlinx-coroutines-core subproject are used.
*/
class Jdk8InCoreIntegration {

@Test
fun testFuture() = runBlocking<Unit> {
val future = future { yield(); 42 }
future.whenComplete { r, _ -> assertEquals(42, r) }
assertEquals(42, future.await())
}
}
1 change: 0 additions & 1 deletion integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ Module name below corresponds to the artifact name in Maven/Gradle.

## Modules

* [kotlinx-coroutines-jdk8](kotlinx-coroutines-jdk8/README.md) -- integration with JDK8 `CompletableFuture` (Android API level 24).
* [kotlinx-coroutines-guava](kotlinx-coroutines-guava/README.md) -- integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained).
* [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j/README.md) -- integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html).
* [kotlinx-coroutines-play-services](kotlinx-coroutines-play-services) -- integration with Google Play Services [Tasks API](https://developers.google.com/android/guides/tasks).
Expand Down
69 changes: 2 additions & 67 deletions integration/kotlinx-coroutines-jdk8/README.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,3 @@
# Module kotlinx-coroutines-jdk8
# Stub module

Integration with JDK8 [CompletableFuture] (Android API level 24).

Coroutine builders:

| **Name** | **Result** | **Scope** | **Description**
| -------- | ------------------- | ---------------- | ---------------
| [future] | [CompletableFuture] | [CoroutineScope] | Returns a single value with the future result

Extension functions:

| **Name** | **Description**
| -------- | ---------------
| [CompletionStage.await][java.util.concurrent.CompletionStage.await] | Awaits for completion of the completion stage
| [CompletionStage.asDeferred][java.util.concurrent.CompletionStage.asDeferred] | Converts completion stage to an instance of [Deferred]
| [Deferred.asCompletableFuture][kotlinx.coroutines.Deferred.asCompletableFuture] | Converts a deferred value to the future

## Example

Given the following functions defined in some Java API:

```java
public CompletableFuture<Image> loadImageAsync(String name); // starts async image loading
public Image combineImages(Image image1, Image image2); // synchronously combines two images using some algorithm
```

We can consume this API from Kotlin coroutine to load two images and combine then asynchronously.
The resulting function returns `CompletableFuture<Image>` for ease of use back from Java.

```kotlin
fun combineImagesAsync(name1: String, name2: String): CompletableFuture<Image> = future {
val future1 = loadImageAsync(name1) // start loading first image
val future2 = loadImageAsync(name2) // start loading second image
combineImages(future1.await(), future2.await()) // wait for both, combine, and return result
}
```

Note that this module should be used only for integration with existing Java APIs based on `CompletableFuture`.
Writing pure-Kotlin code that uses `CompletableFuture` is highly not recommended, since the resulting APIs based
on the futures are quite error-prone. See the discussion on
[Asynchronous Programming Styles](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#asynchronous-programming-styles)
for details on general problems pertaining to any future-based API and keep in mind that `CompletableFuture` exposes
a _blocking_ method
[get](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html#get--)
that makes it especially bad choice for coroutine-based Kotlin code.

# Package kotlinx.coroutines.future

Integration with JDK8 [CompletableFuture] (Android API level 24).

[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html

<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->

[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html

<!--- MODULE kotlinx-coroutines-jdk8 -->
<!--- INDEX kotlinx.coroutines.future -->

[future]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/future.html
[java.util.concurrent.CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
[java.util.concurrent.CompletionStage.asDeferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-deferred.html
[kotlinx.coroutines.Deferred.asCompletableFuture]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-completable-future.html

<!--- END -->
Stub module for backwards compatibility. Since 1.7.0, this module was merged with core.
Original file line number Diff line number Diff line change
@@ -1,22 +0,0 @@
public final class kotlinx/coroutines/future/FutureKt {
public static final fun asCompletableFuture (Lkotlinx/coroutines/Deferred;)Ljava/util/concurrent/CompletableFuture;
public static final fun asCompletableFuture (Lkotlinx/coroutines/Job;)Ljava/util/concurrent/CompletableFuture;
public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred;
public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture;
public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture;
}

public final class kotlinx/coroutines/stream/StreamKt {
public static final fun consumeAsFlow (Ljava/util/stream/Stream;)Lkotlinx/coroutines/flow/Flow;
}

public final class kotlinx/coroutines/time/TimeKt {
public static final fun debounce (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
public static final fun delay (Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;Ljava/time/Duration;Lkotlin/jvm/functions/Function1;)V
public static final fun sample (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
public static final fun withTimeout (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun withTimeoutOrNull (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

22 changes: 22 additions & 0 deletions kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,15 @@ public final class kotlinx/coroutines/flow/internal/SendingCollector : kotlinx/c
public fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class kotlinx/coroutines/future/FutureKt {
public static final fun asCompletableFuture (Lkotlinx/coroutines/Deferred;)Ljava/util/concurrent/CompletableFuture;
public static final fun asCompletableFuture (Lkotlinx/coroutines/Job;)Ljava/util/concurrent/CompletableFuture;
public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred;
public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture;
public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture;
}

public final class kotlinx/coroutines/intrinsics/CancellableKt {
public static final fun startCoroutineCancellable (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
}
Expand Down Expand Up @@ -1292,6 +1301,10 @@ public final class kotlinx/coroutines/selects/WhileSelectKt {
public static final fun whileSelect (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class kotlinx/coroutines/stream/StreamKt {
public static final fun consumeAsFlow (Ljava/util/stream/Stream;)Lkotlinx/coroutines/flow/Flow;
}

public abstract interface class kotlinx/coroutines/sync/Mutex {
public abstract fun getOnLock ()Lkotlinx/coroutines/selects/SelectClause2;
public abstract fun holdsLock (Ljava/lang/Object;)Z
Expand Down Expand Up @@ -1327,3 +1340,12 @@ public final class kotlinx/coroutines/sync/SemaphoreKt {
public static final fun withPermit (Lkotlinx/coroutines/sync/Semaphore;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class kotlinx/coroutines/time/TimeKt {
public static final fun debounce (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
public static final fun delay (Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;Ljava/time/Duration;Lkotlin/jvm/functions/Function1;)V
public static final fun sample (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
public static final fun withTimeout (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun withTimeoutOrNull (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

67 changes: 55 additions & 12 deletions kotlinx-coroutines-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,50 @@ apply from: rootProject.file('gradle/publish.gradle')
/* ==========================================================================
Configure source sets structure for kotlinx-coroutines-core:
TARGETS SOURCE SETS
------- ----------------------------------------------
TARGETS SOURCE SETS
------- ----------------------------------------------
js -----------------------------------------------------+
|
V
jvm -------------------------------> concurrent ---> common
^
ios \ |
macos | ---> nativeDarwin ---> native --+
jvmCore\ --------> jvm ---------> concurrent -------> common
jdk8 / ^
|
ios \ |
macos | ---> nativeDarwin ---> native ---+
tvos | ^
watchos / |
|
linux \ ---> nativeOther -------+
mingw /
========================================================================== */
Explanation of JVM source sets structure:
The overall structure is just a hack to support the scenario we are interested in:
* We would like to have two source-sets "core" and "jdk8"
* "jdk8" is allowed to use API from Java 8 and from "core"
* "core" is prohibited to use any API from "jdk8"
* It is okay to have tests in a single test source-set
* And we want to publish a **single** artifact kotlinx-coroutines-core.jar that contains classes from both source-sets
* Current limitation: only classes from "core" are checked with animal-sniffer
For that, we have following compilations:
* jvmMain compilation: [jvmCoreMain, jdk8Main]
* jvmCore compilation: [commonMain]
* jdk8 compilation: [commonMain, jvmCoreMain]
Theoretically, "jvmCore" could've been "jvmMain", it is not for technical reasons,
here is the explanation from Seb:
"""
The jvmCore is theoretically not necessary. All code for jdk6 compatibility can be in jvmMain and jdk8 dependent code can be in jdk8Main.
Effectively there is no reason for ever putting code into jvmCoreMain.
However, when creating a new compilation, we have to take care of creating a defaultSourceSet. Without creating the jvmCoreMain source set,
the creation of the compilation fails. That is the only reason for this source set.
"""
========================================================================== */

project.ext.sourceSetSuffixes = ["Main", "Test"]

Expand Down Expand Up @@ -68,15 +95,12 @@ if (rootProject.ext.native_targets_enabled) {

/* ========================================================================== */


/*
* All platform plugins and configuration magic happens here instead of build.gradle
* because JMV-only projects depend on core, thus core should always be initialized before configuration.
*/
kotlin {
sourceSets.forEach {
SourceSetsKt.configureMultiplatform(it)
}

/*
* Configure two test runs:
* 1) New memory model, Main thread
Expand Down Expand Up @@ -104,13 +128,32 @@ kotlin {
}
}

def jvmMain = sourceSets.jvmMain
def jvmCoreMain = sourceSets.create('jvmCoreMain')
def jdk8Main = sourceSets.create('jdk8Main')
jvmCoreMain.dependsOn(jvmMain)
jdk8Main.dependsOn(jvmMain)

sourceSets.forEach {
SourceSetsKt.configureMultiplatform(it)
}

jvm {
def main = compilations.main
main.source(jvmCoreMain)
main.source(jdk8Main)

/* Create compilation for jvmCore to prove that jvmMain does not rely on jdk8 */
compilations.create('CoreMain') {
/* jvmCore is automatically matched as 'defaultSourceSet' for the compilation, due to its name */
tasks.getByName('check').dependsOn(compileKotlinTaskProvider)
}

// For animal sniffer
withJava()
}
}


configurations {
configureKotlinJvmPlatform(kotlinCompilerPluginClasspath)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package future
package kotlinx.coroutines.future

import kotlinx.coroutines.*
import kotlinx.coroutines.future.*
Expand Down

0 comments on commit f3527c9

Please sign in to comment.