Skip to content

Commit 2cea69a

Browse files
committed
Re-add Kotlin and Scala
1 parent bddf816 commit 2cea69a

File tree

50 files changed

+3645
-17
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3645
-17
lines changed

.github/workflows/build.yaml

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,20 @@ jobs:
3737
**/build/reports/
3838
**/build/test-results/
3939
40-
# test-sbt:
41-
# runs-on: ubuntu-latest
42-
# steps:
43-
# - uses: actions/checkout@v4
44-
# - name: Set up JDK 21
45-
# uses: actions/setup-java@v4
46-
# with:
47-
# cache: 'sbt'
48-
# java-version: 21
49-
# distribution: 'temurin'
50-
# - name: Setup Gradle
51-
# uses: gradle/actions/setup-gradle@v3
52-
# - name: Setup sbt
53-
# uses: sbt/setup-sbt@v1
54-
# - name: Build and test
55-
# run: sbt -v publishLocalGradleDependencies ++test
56-
# working-directory: ./tasks-scala
40+
test-sbt:
41+
runs-on: ubuntu-latest
42+
steps:
43+
- uses: actions/checkout@v4
44+
- name: Set up JDK 21
45+
uses: actions/setup-java@v4
46+
with:
47+
cache: 'sbt'
48+
java-version: 21
49+
distribution: 'temurin'
50+
- name: Setup Gradle
51+
uses: gradle/actions/setup-gradle@v3
52+
- name: Setup sbt
53+
uses: sbt/setup-sbt@v1
54+
- name: Build and test
55+
run: sbt -v publishLocalGradleDependencies ++test
56+
working-directory: ./tasks-scala

build.gradle.kts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,32 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
33
val projectVersion = property("project.version").toString()
44

55
plugins {
6+
id("org.jetbrains.dokka")
67
id("com.github.ben-manes.versions")
78
}
89

910
repositories {
1011
mavenCentral()
1112
}
1213

14+
buildscript {
15+
dependencies {
16+
classpath("org.jetbrains.dokka:dokka-base:2.0.0")
17+
// classpath("org.jetbrains.dokka:kotlin-as-java-plugin:2.0.0")
18+
}
19+
}
20+
21+
//dokka {
22+
// dokkaPublications.html {
23+
// outputDirectory.set(rootDir.resolve("build/dokka"))
24+
// outputDirectory.set(file("build/dokka"))
25+
// }
26+
//}
27+
28+
tasks.dokkaHtmlMultiModule {
29+
outputDirectory.set(file("build/dokka"))
30+
}
31+
1332
tasks.named<DependencyUpdatesTask>("dependencyUpdates").configure {
1433
fun isNonStable(version: String): Boolean {
1534
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.uppercase().contains(it) }

gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ kotlin.code.style=official
22

33
# TO BE modified whenever a new version is released
44
project.version=0.0.3
5+
6+
# https://kotlinlang.org/docs/dokka-migration.html#sync-your-project-with-gradle
7+
org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers

settings.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
rootProject.name = "tasks"
22

33
include("tasks-jvm")
4+
include("tasks-kotlin")
5+
include("tasks-kotlin-coroutines")
6+
include("tasks-scala")
47

58
pluginManagement {
69
repositories {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
public final class org/funfix/tasks/kotlin/CoroutinesJvmKt {
2+
public static final fun fromSuspended (Lorg/funfix/tasks/kotlin/Task$Companion;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
3+
public static synthetic fun fromSuspended$default (Lorg/funfix/tasks/kotlin/Task$Companion;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
4+
public static final fun runSuspended (Lorg/funfix/tasks/jvm/Task;Ljava/util/concurrent/Executor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
5+
public static synthetic fun runSuspended$default (Lorg/funfix/tasks/jvm/Task;Ljava/util/concurrent/Executor;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
6+
public static final fun runSuspended-A-R0woo (Lorg/funfix/tasks/jvm/Task;Ljava/util/concurrent/Executor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
7+
public static synthetic fun runSuspended-A-R0woo$default (Lorg/funfix/tasks/jvm/Task;Ljava/util/concurrent/Executor;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
8+
}
9+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
2+
3+
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
4+
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
5+
6+
plugins {
7+
id("tasks.kmp-project")
8+
}
9+
10+
mavenPublishing {
11+
pom {
12+
name = "Tasks / Kotlin Coroutines"
13+
description = "Integration with Kotlin's Coroutines"
14+
}
15+
}
16+
17+
kotlin {
18+
sourceSets {
19+
val commonMain by getting {
20+
compilerOptions {
21+
explicitApi = ExplicitApiMode.Strict
22+
allWarningsAsErrors = true
23+
}
24+
25+
dependencies {
26+
implementation(project(":tasks-kotlin"))
27+
implementation(libs.kotlinx.coroutines.core)
28+
}
29+
}
30+
31+
val commonTest by getting {
32+
dependencies {
33+
implementation(libs.kotlin.test)
34+
implementation(libs.kotlinx.coroutines.test)
35+
}
36+
}
37+
38+
val jvmMain by getting {
39+
compilerOptions {
40+
explicitApi = ExplicitApiMode.Strict
41+
allWarningsAsErrors = true
42+
}
43+
44+
dependencies {
45+
implementation(project(":tasks-jvm"))
46+
implementation(project(":tasks-kotlin"))
47+
implementation(libs.kotlinx.coroutines.core)
48+
}
49+
}
50+
51+
val jvmTest by getting {
52+
dependencies {
53+
implementation(libs.kotlin.test)
54+
implementation(libs.kotlinx.coroutines.test)
55+
}
56+
}
57+
58+
val jsMain by getting {
59+
compilerOptions {
60+
explicitApi = ExplicitApiMode.Strict
61+
allWarningsAsErrors = true
62+
}
63+
64+
dependencies {
65+
implementation(project(":tasks-kotlin"))
66+
implementation(libs.kotlinx.coroutines.core)
67+
}
68+
}
69+
}
70+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.funfix.tasks.kotlin
2+
3+
import kotlin.coroutines.CoroutineContext
4+
import kotlin.coroutines.EmptyCoroutineContext
5+
6+
/**
7+
* Similar with `runBlocking`, however this is a "suspended" function,
8+
* to be executed in the context of [kotlinx.coroutines].
9+
*
10+
* NOTES:
11+
* - The [CoroutineDispatcher], made available via the "coroutine context", is
12+
* used to execute the task, being passed to the task's implementation as an
13+
* `Executor`.
14+
* - The coroutine's cancellation protocol cooperates with that of [Task],
15+
* so cancelling the coroutine will also cancel the task (including the
16+
* possibility for back-pressuring on the fiber's completion after
17+
* cancellation).
18+
*
19+
* @param executor is an override of the `Executor` to be used for executing
20+
* the task. If `null`, the `Executor` will be derived from the
21+
* `CoroutineDispatcher`
22+
*/
23+
public expect suspend fun <T> Task<T>.runSuspended(
24+
executor: Executor? = null
25+
): T
26+
27+
/**
28+
* See documentation for [Task.runSuspended].
29+
*/
30+
public expect suspend fun <T> PlatformTask<out T>.runSuspended(
31+
executor: Executor? = null
32+
): T
33+
34+
/**
35+
* Creates a [Task] from a suspended block of code.
36+
*/
37+
public expect suspend fun <T> Task.Companion.fromSuspended(
38+
coroutineContext: CoroutineContext = EmptyCoroutineContext,
39+
block: suspend () -> T
40+
): Task<T>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.funfix.tasks.kotlin
2+
3+
import kotlinx.coroutines.CoroutineDispatcher
4+
import kotlinx.coroutines.Dispatchers
5+
import kotlin.coroutines.ContinuationInterceptor
6+
import kotlin.coroutines.coroutineContext
7+
8+
/**
9+
* Internal API: gets the current [CoroutineDispatcher] from the coroutine context.
10+
*/
11+
internal suspend fun currentDispatcher(): CoroutineDispatcher {
12+
// Access the coroutineContext to get the ContinuationInterceptor
13+
val continuationInterceptor = coroutineContext[ContinuationInterceptor]
14+
return continuationInterceptor as? CoroutineDispatcher ?: Dispatchers.Default
15+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
@file:OptIn(DelicateCoroutinesApi::class)
2+
3+
package org.funfix.tasks.kotlin
4+
5+
import kotlinx.coroutines.CancellableContinuation
6+
import kotlinx.coroutines.CoroutineDispatcher
7+
import kotlinx.coroutines.DelicateCoroutinesApi
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.GlobalScope
10+
import kotlinx.coroutines.launch
11+
import kotlinx.coroutines.suspendCancellableCoroutine
12+
import kotlin.coroutines.CoroutineContext
13+
import kotlin.coroutines.EmptyCoroutineContext
14+
import kotlin.coroutines.cancellation.CancellationException
15+
import kotlin.coroutines.resumeWithException
16+
17+
public actual suspend fun <T> PlatformTask<out T>.runSuspended(
18+
executor: Executor?
19+
): T = run {
20+
val executorOrDefault = executor ?: buildExecutor(currentDispatcher())
21+
suspendCancellableCoroutine { cont ->
22+
val contCallback = cont.asCompletionCallback()
23+
try {
24+
val token = this.invoke(executorOrDefault, contCallback)
25+
cont.invokeOnCancellation {
26+
token.cancel()
27+
}
28+
} catch (e: Throwable) {
29+
UncaughtExceptionHandler.rethrowIfFatal(e)
30+
contCallback(Outcome.Failure(e))
31+
}
32+
}
33+
}
34+
35+
internal fun buildExecutor(dispatcher: CoroutineDispatcher): Executor =
36+
DispatcherExecutor(dispatcher)
37+
38+
internal fun buildCoroutineDispatcher(
39+
@Suppress("UNUSED_PARAMETER") executor: Executor
40+
): CoroutineDispatcher =
41+
// Building this CoroutineDispatcher from an Executor is problematic, and there's no
42+
// point in even trying on top of JS engines.
43+
Dispatchers.Default
44+
45+
private class DispatcherExecutor(val dispatcher: CoroutineDispatcher) : Executor {
46+
override fun execute(command: Runnable) {
47+
if (dispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
48+
dispatcher.dispatch(
49+
EmptyCoroutineContext,
50+
kotlinx.coroutines.Runnable { command.run() }
51+
)
52+
} else {
53+
command.run()
54+
}
55+
}
56+
57+
override fun toString(): String =
58+
dispatcher.toString()
59+
}
60+
61+
internal fun <T> CancellableContinuation<T>.asCompletionCallback(): Callback<T> {
62+
var isActive = true
63+
return { outcome ->
64+
if (outcome is Outcome.Failure) {
65+
UncaughtExceptionHandler.rethrowIfFatal(outcome.exception)
66+
}
67+
if (isActive) {
68+
isActive = false
69+
when (outcome) {
70+
is Outcome.Success ->
71+
resume(outcome.value) { _, _, _ ->
72+
// on cancellation?
73+
}
74+
is Outcome.Failure ->
75+
resumeWithException(outcome.exception)
76+
is Outcome.Cancellation ->
77+
resumeWithException(kotlinx.coroutines.CancellationException())
78+
}
79+
} else if (outcome is Outcome.Failure) {
80+
UncaughtExceptionHandler.logOrRethrow(outcome.exception)
81+
}
82+
}
83+
}
84+
85+
/**
86+
* Creates a [Task] from a suspended block of code.
87+
*/
88+
public actual suspend fun <T> Task.Companion.fromSuspended(
89+
coroutineContext: CoroutineContext,
90+
block: suspend () -> T
91+
): Task<T> =
92+
Task.fromAsync { executor, callback ->
93+
val job = GlobalScope.launch(
94+
buildCoroutineDispatcher(executor) + coroutineContext
95+
) {
96+
try {
97+
val r = block()
98+
callback(Outcome.Success(r))
99+
} catch (e: Throwable) {
100+
UncaughtExceptionHandler.rethrowIfFatal(e)
101+
when (e) {
102+
is CancellationException, is TaskCancellationException ->
103+
callback(Outcome.Cancellation)
104+
else ->
105+
callback(Outcome.Failure(e))
106+
}
107+
}
108+
}
109+
Cancellable {
110+
job.cancel()
111+
}
112+
}
113+
114+
public actual suspend fun <T> Task<T>.runSuspended(executor: Executor?): T =
115+
asPlatform.runSuspended(executor)

0 commit comments

Comments
 (0)