Skip to content

Commit f3527c9

Browse files
authored
Merge kotlinx-coroutines-core and kotlinx-coroutines-jdk8 modules (#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 #3268
1 parent 1656a0d commit f3527c9

24 files changed

+128
-115
lines changed

README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ suspend fun main() = coroutineScope {
3737
* [core/jvm](kotlinx-coroutines-core/jvm/) — additional core features available on Kotlin/JVM:
3838
* [Dispatchers.IO] dispatcher for blocking coroutines;
3939
* [Executor.asCoroutineDispatcher][asCoroutineDispatcher] extension, custom thread pools, and more.
40+
* Integrations with `CompletableFuture` and JVM-specific extensions.
4041
* [core/js](kotlinx-coroutines-core/js/) — additional core features available on Kotlin/JS:
4142
* Integration with `Promise` via [Promise.await] and [promise] builder;
4243
* Integration with `Window` via [Window.asCoroutineDispatcher], etc.
@@ -56,7 +57,7 @@ suspend fun main() = coroutineScope {
5657
* [ui](ui/README.md) — modules that provide coroutine dispatchers for various single-threaded UI libraries:
5758
* Android, JavaFX, and Swing.
5859
* [integration](integration/README.md) — modules that provide integration with various asynchronous callback- and future-based libraries:
59-
* JDK8 [CompletionStage.await], Guava [ListenableFuture.await], and Google Play Services [Task.await];
60+
* Guava [ListenableFuture.await], and Google Play Services [Task.await];
6061
* SLF4J MDC integration via [MDCContext].
6162

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

260261
<!--- MODULE kotlinx-coroutines-jdk8 -->
261262
<!--- INDEX kotlinx.coroutines.future -->
262-
263-
[CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
264-
265263
<!--- MODULE kotlinx-coroutines-guava -->
266264
<!--- INDEX kotlinx.coroutines.guava -->
267265

build.gradle

+4-2
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ def core_docs_url = "https://kotlinlang.org/api/kotlinx.coroutines/$coreModule/"
223223
def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/htmlPartial/package-list"
224224
apply plugin: "org.jetbrains.dokka"
225225

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

233235
configure(subprojects.findAll { !unpublished.contains(it.name) }) {
234236
if (it.name != "kotlinx-coroutines-bom") {
235-
if (it.name != coreModule) {
237+
if (it.name != coreModule && it.name != jdk8ObsoleteModule) {
236238
tasks.withType(DokkaTaskPartial.class) {
237239
dokkaSourceSets.configureEach {
238240
externalDocumentationLink {

buildSrc/src/main/kotlin/Projects.kt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ fun Project.version(target: String): String =
88
property("${target}_version") as String
99

1010
val coreModule = "kotlinx-coroutines-core"
11+
val jdk8ObsoleteModule = "kotlinx-coroutines-jdk8"
1112
val testModule = "kotlinx-coroutines-test"
1213

1314
val multiplatform = setOf(coreModule, testModule)

buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ configure(subprojects) {
1717
signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature")
1818
signature("org.codehaus.mojo.signature:java17:1.0@signature")
1919
}
20+
21+
if (project.name == coreModule) {
22+
// Specific files so nothing from core is accidentally skipped
23+
tasks.withType<AnimalSniffer>().configureEach {
24+
exclude("**/future/FutureKt*")
25+
exclude("**/future/ContinuationHandler*")
26+
exclude("**/future/CompletableFutureCoroutine*")
27+
28+
exclude("**/stream/StreamKt*")
29+
exclude("**/stream/StreamFlow*")
30+
31+
exclude("**/time/TimeKt*")
32+
}
33+
}
2034
}
2135
}
2236

integration-testing/build.gradle

+5-5
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ dependencies {
2222
}
2323

2424
sourceSets {
25-
// Test that relies on Guava to reflectively check all Throwable subclasses in coroutines
26-
withGuavaTest {
25+
// An assortment of tests for behavior of the core coroutines module on JVM
26+
jvmCoreTest {
2727
kotlin
2828
compileClasspath += sourceSets.test.runtimeClasspath
2929
runtimeClasspath += sourceSets.test.runtimeClasspath
@@ -86,9 +86,9 @@ compileDebugAgentTestKotlin {
8686
}
8787
}
8888

89-
task withGuavaTest(type: Test) {
89+
task jvmCoreTest(type: Test) {
9090
environment "version", coroutines_version
91-
def sourceSet = sourceSets.withGuavaTest
91+
def sourceSet = sourceSets.jvmCoreTest
9292
testClassesDirs = sourceSet.output.classesDirs
9393
classpath = sourceSet.runtimeClasspath
9494
}
@@ -128,5 +128,5 @@ compileTestKotlin {
128128
}
129129

130130
check {
131-
dependsOn([withGuavaTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
131+
dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
132132
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
package kotlinx.coroutines
5+
6+
import kotlinx.coroutines.future.*
7+
import org.junit.Test
8+
import kotlin.test.*
9+
10+
/*
11+
* Integration test that ensures signatures from both the jdk8 and the core source sets of the kotlinx-coroutines-core subproject are used.
12+
*/
13+
class Jdk8InCoreIntegration {
14+
15+
@Test
16+
fun testFuture() = runBlocking<Unit> {
17+
val future = future { yield(); 42 }
18+
future.whenComplete { r, _ -> assertEquals(42, r) }
19+
assertEquals(42, future.await())
20+
}
21+
}

integration/README.md

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ Module name below corresponds to the artifact name in Maven/Gradle.
55

66
## Modules
77

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

3-
Integration with JDK8 [CompletableFuture] (Android API level 24).
4-
5-
Coroutine builders:
6-
7-
| **Name** | **Result** | **Scope** | **Description**
8-
| -------- | ------------------- | ---------------- | ---------------
9-
| [future] | [CompletableFuture] | [CoroutineScope] | Returns a single value with the future result
10-
11-
Extension functions:
12-
13-
| **Name** | **Description**
14-
| -------- | ---------------
15-
| [CompletionStage.await][java.util.concurrent.CompletionStage.await] | Awaits for completion of the completion stage
16-
| [CompletionStage.asDeferred][java.util.concurrent.CompletionStage.asDeferred] | Converts completion stage to an instance of [Deferred]
17-
| [Deferred.asCompletableFuture][kotlinx.coroutines.Deferred.asCompletableFuture] | Converts a deferred value to the future
18-
19-
## Example
20-
21-
Given the following functions defined in some Java API:
22-
23-
```java
24-
public CompletableFuture<Image> loadImageAsync(String name); // starts async image loading
25-
public Image combineImages(Image image1, Image image2); // synchronously combines two images using some algorithm
26-
```
27-
28-
We can consume this API from Kotlin coroutine to load two images and combine then asynchronously.
29-
The resulting function returns `CompletableFuture<Image>` for ease of use back from Java.
30-
31-
```kotlin
32-
fun combineImagesAsync(name1: String, name2: String): CompletableFuture<Image> = future {
33-
val future1 = loadImageAsync(name1) // start loading first image
34-
val future2 = loadImageAsync(name2) // start loading second image
35-
combineImages(future1.await(), future2.await()) // wait for both, combine, and return result
36-
}
37-
```
38-
39-
Note that this module should be used only for integration with existing Java APIs based on `CompletableFuture`.
40-
Writing pure-Kotlin code that uses `CompletableFuture` is highly not recommended, since the resulting APIs based
41-
on the futures are quite error-prone. See the discussion on
42-
[Asynchronous Programming Styles](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#asynchronous-programming-styles)
43-
for details on general problems pertaining to any future-based API and keep in mind that `CompletableFuture` exposes
44-
a _blocking_ method
45-
[get](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html#get--)
46-
that makes it especially bad choice for coroutine-based Kotlin code.
47-
48-
# Package kotlinx.coroutines.future
49-
50-
Integration with JDK8 [CompletableFuture] (Android API level 24).
51-
52-
[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html
53-
54-
<!--- MODULE kotlinx-coroutines-core -->
55-
<!--- INDEX kotlinx.coroutines -->
56-
57-
[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
58-
[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
59-
60-
<!--- MODULE kotlinx-coroutines-jdk8 -->
61-
<!--- INDEX kotlinx.coroutines.future -->
62-
63-
[future]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/future.html
64-
[java.util.concurrent.CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
65-
[java.util.concurrent.CompletionStage.asDeferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-deferred.html
66-
[kotlinx.coroutines.Deferred.asCompletableFuture]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-completable-future.html
67-
68-
<!--- END -->
3+
Stub module for backwards compatibility. Since 1.7.0, this module was merged with core.
Original file line numberDiff line numberDiff line change
@@ -1,22 +0,0 @@
1-
public final class kotlinx/coroutines/future/FutureKt {
2-
public static final fun asCompletableFuture (Lkotlinx/coroutines/Deferred;)Ljava/util/concurrent/CompletableFuture;
3-
public static final fun asCompletableFuture (Lkotlinx/coroutines/Job;)Ljava/util/concurrent/CompletableFuture;
4-
public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred;
5-
public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
6-
public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture;
7-
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;
8-
}
9-
10-
public final class kotlinx/coroutines/stream/StreamKt {
11-
public static final fun consumeAsFlow (Ljava/util/stream/Stream;)Lkotlinx/coroutines/flow/Flow;
12-
}
13-
14-
public final class kotlinx/coroutines/time/TimeKt {
15-
public static final fun debounce (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
16-
public static final fun delay (Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
17-
public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;Ljava/time/Duration;Lkotlin/jvm/functions/Function1;)V
18-
public static final fun sample (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
19-
public static final fun withTimeout (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
20-
public static final fun withTimeoutOrNull (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
21-
}
22-

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

+22
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,15 @@ public final class kotlinx/coroutines/flow/internal/SendingCollector : kotlinx/c
11761176
public fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
11771177
}
11781178

1179+
public final class kotlinx/coroutines/future/FutureKt {
1180+
public static final fun asCompletableFuture (Lkotlinx/coroutines/Deferred;)Ljava/util/concurrent/CompletableFuture;
1181+
public static final fun asCompletableFuture (Lkotlinx/coroutines/Job;)Ljava/util/concurrent/CompletableFuture;
1182+
public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred;
1183+
public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1184+
public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture;
1185+
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;
1186+
}
1187+
11791188
public final class kotlinx/coroutines/intrinsics/CancellableKt {
11801189
public static final fun startCoroutineCancellable (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
11811190
}
@@ -1292,6 +1301,10 @@ public final class kotlinx/coroutines/selects/WhileSelectKt {
12921301
public static final fun whileSelect (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
12931302
}
12941303

1304+
public final class kotlinx/coroutines/stream/StreamKt {
1305+
public static final fun consumeAsFlow (Ljava/util/stream/Stream;)Lkotlinx/coroutines/flow/Flow;
1306+
}
1307+
12951308
public abstract interface class kotlinx/coroutines/sync/Mutex {
12961309
public abstract fun getOnLock ()Lkotlinx/coroutines/selects/SelectClause2;
12971310
public abstract fun holdsLock (Ljava/lang/Object;)Z
@@ -1327,3 +1340,12 @@ public final class kotlinx/coroutines/sync/SemaphoreKt {
13271340
public static final fun withPermit (Lkotlinx/coroutines/sync/Semaphore;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
13281341
}
13291342

1343+
public final class kotlinx/coroutines/time/TimeKt {
1344+
public static final fun debounce (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
1345+
public static final fun delay (Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1346+
public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;Ljava/time/Duration;Lkotlin/jvm/functions/Function1;)V
1347+
public static final fun sample (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow;
1348+
public static final fun withTimeout (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1349+
public static final fun withTimeoutOrNull (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1350+
}
1351+

kotlinx-coroutines-core/build.gradle

+55-12
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,50 @@ apply from: rootProject.file('gradle/publish.gradle')
1919
/* ==========================================================================
2020
Configure source sets structure for kotlinx-coroutines-core:
2121
22-
TARGETS SOURCE SETS
23-
------- ----------------------------------------------
22+
TARGETS SOURCE SETS
23+
------- ----------------------------------------------
2424
2525
js -----------------------------------------------------+
2626
|
2727
V
28-
jvm -------------------------------> concurrent ---> common
29-
^
30-
ios \ |
31-
macos | ---> nativeDarwin ---> native --+
28+
jvmCore\ --------> jvm ---------> concurrent -------> common
29+
jdk8 / ^
30+
|
31+
ios \ |
32+
macos | ---> nativeDarwin ---> native ---+
3233
tvos | ^
3334
watchos / |
3435
|
3536
linux \ ---> nativeOther -------+
3637
mingw /
3738
38-
========================================================================== */
39+
40+
Explanation of JVM source sets structure:
41+
42+
The overall structure is just a hack to support the scenario we are interested in:
43+
44+
* We would like to have two source-sets "core" and "jdk8"
45+
* "jdk8" is allowed to use API from Java 8 and from "core"
46+
* "core" is prohibited to use any API from "jdk8"
47+
* It is okay to have tests in a single test source-set
48+
* And we want to publish a **single** artifact kotlinx-coroutines-core.jar that contains classes from both source-sets
49+
* Current limitation: only classes from "core" are checked with animal-sniffer
50+
51+
For that, we have following compilations:
52+
* jvmMain compilation: [jvmCoreMain, jdk8Main]
53+
* jvmCore compilation: [commonMain]
54+
* jdk8 compilation: [commonMain, jvmCoreMain]
55+
56+
Theoretically, "jvmCore" could've been "jvmMain", it is not for technical reasons,
57+
here is the explanation from Seb:
58+
59+
"""
60+
The jvmCore is theoretically not necessary. All code for jdk6 compatibility can be in jvmMain and jdk8 dependent code can be in jdk8Main.
61+
Effectively there is no reason for ever putting code into jvmCoreMain.
62+
However, when creating a new compilation, we have to take care of creating a defaultSourceSet. Without creating the jvmCoreMain source set,
63+
the creation of the compilation fails. That is the only reason for this source set.
64+
"""
65+
========================================================================== */
3966

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

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

6996
/* ========================================================================== */
7097

98+
7199
/*
72100
* All platform plugins and configuration magic happens here instead of build.gradle
73101
* because JMV-only projects depend on core, thus core should always be initialized before configuration.
74102
*/
75103
kotlin {
76-
sourceSets.forEach {
77-
SourceSetsKt.configureMultiplatform(it)
78-
}
79-
80104
/*
81105
* Configure two test runs:
82106
* 1) New memory model, Main thread
@@ -104,13 +128,32 @@ kotlin {
104128
}
105129
}
106130

131+
def jvmMain = sourceSets.jvmMain
132+
def jvmCoreMain = sourceSets.create('jvmCoreMain')
133+
def jdk8Main = sourceSets.create('jdk8Main')
134+
jvmCoreMain.dependsOn(jvmMain)
135+
jdk8Main.dependsOn(jvmMain)
136+
137+
sourceSets.forEach {
138+
SourceSetsKt.configureMultiplatform(it)
139+
}
140+
107141
jvm {
142+
def main = compilations.main
143+
main.source(jvmCoreMain)
144+
main.source(jdk8Main)
145+
146+
/* Create compilation for jvmCore to prove that jvmMain does not rely on jdk8 */
147+
compilations.create('CoreMain') {
148+
/* jvmCore is automatically matched as 'defaultSourceSet' for the compilation, due to its name */
149+
tasks.getByName('check').dependsOn(compileKotlinTaskProvider)
150+
}
151+
108152
// For animal sniffer
109153
withJava()
110154
}
111155
}
112156

113-
114157
configurations {
115158
configureKotlinJvmPlatform(kotlinCompilerPluginClasspath)
116159
}
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/*
2-
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5-
package future
5+
package kotlinx.coroutines.future
66

77
import kotlinx.coroutines.*
88
import kotlinx.coroutines.future.*

0 commit comments

Comments
 (0)