diff --git a/README.md b/README.md index bbd8726b42..0e47a6df46 100644 --- a/README.md +++ b/README.md @@ -203,8 +203,9 @@ to Gradle (in Preferences -> Build, Execution, Deployment -> Build Tools -> Grad ### Requirements -* JDK >= 1.8 referred to by the `JAVA_HOME` environment variable. JDK must include JavaFX. +* JDK >= 11 referred to by the `JAVA_HOME` environment variable. * JDK 1.6 referred to by the `JDK_16` environment variable. It is okay to have `JDK_16` pointing to `JAVA_HOME` for external contributions. +* JDK 1.8 referred to by the `JDK_18` environment variable. Only used by nightly stress-tests. It is okay to have `JDK_16` pointing to `JAVA_HOME` for external contributions. ## Contributions and releases diff --git a/build.gradle b/build.gradle index e03179dddb..85d60cca51 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * 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 @@ -68,6 +68,7 @@ buildscript { classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version" classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version" + classpath "org.openjfx:javafx-plugin:$javafx_plugin_version" classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$binary_compatibility_validator_version" // JMH plugins @@ -261,8 +262,26 @@ configure(subprojects.findAll { !unpublished.contains(it.name) }) { // Report Kotlin compiler version when building project println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION") +// --------------- Publish only from under JDK11+ --------------- +task checkJdkForPublish { + doFirst { + String javaVersion = System.properties["java.version"] + int i = javaVersion.indexOf('.') + int javaVersionMajor = (i < 0 ? javaVersion : javaVersion.substring(0, i)).toInteger() + if (javaVersionMajor < 11) { + throw new GradleException("Project can be build for publishing only under JDK 11+, but found ${javaVersion}") + } + } +} + // --------------- Configure sub-projects that are published --------------- -task deploy(dependsOn: getTasksByName("publish", true) + getTasksByName("publishNpm", true)) +def publishTasks = getTasksByName("publish", true) + getTasksByName("publishNpm", true) + +publishTasks.each { + it.dependsOn checkJdkForPublish +} + +task deploy(dependsOn: publishTasks) apply plugin: 'base' diff --git a/gradle.properties b/gradle.properties index b693ededcd..bb3e946b8e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # -# Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. +# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. # # Kotlin @@ -17,8 +17,16 @@ byte_buddy_version=1.9.3 reactor_vesion=3.2.5.RELEASE reactive_streams_version=1.0.2 rxjava2_version=2.2.8 +javafx_version=11.0.2 +javafx_plugin_version=0.0.8 binary_compatibility_validator_version=0.1.1 +# Android versions +android_version=4.1.1.4 +android_support_version=26.1.0 +robolectric_version=4.0.2 +baksmali_version=2.2.7 + # JS gradle_node_version=1.2.0 node_version=8.9.3 diff --git a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt index 3f3c69cdb2..c0b7f50134 100644 --- a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt +++ b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines.debug @@ -20,7 +20,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { verifyDump( "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@50284dc4, state: RUNNING\n" + - "\tat sun.misc.Unsafe.park(Native Method)\n" + + "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" + "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" + @@ -75,7 +75,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { verifyDump( "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" + - "\tat sun.misc.Unsafe.park(Native Method)\n" + + "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" + "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" + @@ -116,7 +116,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { verifyDump( "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" + - "\tat sun.misc.Unsafe.park(Native Method)\n" + + "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" + "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" + diff --git a/kotlinx-coroutines-debug/test/StracktraceUtils.kt b/kotlinx-coroutines-debug/test/StracktraceUtils.kt index 2600e4a572..de31ac10a4 100644 --- a/kotlinx-coroutines-debug/test/StracktraceUtils.kt +++ b/kotlinx-coroutines-debug/test/StracktraceUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines.debug @@ -11,6 +11,9 @@ public fun String.trimStackTrace(): String = trimIndent() .replace(Regex(":[0-9]+"), "") .replace(Regex("#[0-9]+"), "") + .replace(Regex("(?<=\tat )[^\n]*/"), "") + .replace(Regex("\t"), "") + .replace("sun.misc.Unsafe.park", "jdk.internal.misc.Unsafe.park") // JDK8->JDK11 .applyBackspace() public fun String.applyBackspace(): String { @@ -30,16 +33,12 @@ public fun String.applyBackspace(): String { public fun verifyStackTrace(e: Throwable, traces: List) { val stacktrace = toStackTrace(e) + val trimmedStackTrace = stacktrace.trimStackTrace() traces.forEach { - val expectedLines = it.trimStackTrace().split("\n") - for (i in 0 until expectedLines.size) { - traces.forEach { - assertTrue( - stacktrace.trimStackTrace().contains(it.trimStackTrace()), - "\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace" - ) - } - } + assertTrue( + trimmedStackTrace.contains(it.trimStackTrace()), + "\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace" + ) } val causes = stacktrace.count("Caused by") diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle index 83b8dcd7d9..d724cd90d1 100644 --- a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle +++ b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ repositories { @@ -7,10 +7,9 @@ repositories { } dependencies { - testImplementation 'com.google.android:android:4.1.1.4' - testImplementation 'com.android.support:support-annotations:26.1.0' - testImplementation 'com.google.android:android:4.1.1.4' - testImplementation 'org.robolectric:robolectric:4.0-alpha-3' + testImplementation "com.google.android:android:$android_version" + testImplementation "com.android.support:support-annotations:$android_support_version" + testImplementation "org.robolectric:robolectric:$robolectric_version" testImplementation project(":kotlinx-coroutines-test") testImplementation project(":kotlinx-coroutines-android") } diff --git a/ui/kotlinx-coroutines-android/build.gradle b/ui/kotlinx-coroutines-android/build.gradle index b3075f780e..c05881eb6c 100644 --- a/ui/kotlinx-coroutines-android/build.gradle +++ b/ui/kotlinx-coroutines-android/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ repositories { @@ -18,12 +18,12 @@ configurations { } dependencies { - compileOnly 'com.google.android:android:4.1.1.4' - compileOnly 'com.android.support:support-annotations:26.1.0' + compileOnly "com.google.android:android:$android_version" + compileOnly "com.android.support:support-annotations:$android_support_version" - testImplementation 'com.google.android:android:4.1.1.4' - testImplementation 'org.robolectric:robolectric:4.0-alpha-3' - testImplementation 'org.smali:baksmali:2.2.7' + testImplementation "com.google.android:android:$android_version" + testImplementation "org.robolectric:robolectric:$robolectric_version" + testImplementation "org.smali:baksmali:$baksmali_version" // TODO Replace with a 1.6.x version once released to maven.google.com. r8 'com.android.tools:r8:a7ce65837bec81c62261bf0adac73d9c09d32af2' diff --git a/ui/kotlinx-coroutines-javafx/build.gradle b/ui/kotlinx-coroutines-javafx/build.gradle index 3b17101f53..9d1c128239 100644 --- a/ui/kotlinx-coroutines-javafx/build.gradle +++ b/ui/kotlinx-coroutines-javafx/build.gradle @@ -1,4 +1,41 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +static int javaVersionMajor() { + String javaVersion = System.properties["java.version"] + int i = javaVersion.indexOf('.') + return (i < 0 ? javaVersion : javaVersion.substring(0, i)).toInteger() +} + +// JDK11+ does not bundle JavaFx and the plugin for JavaFx support is compiled with class file version 55.0 (JDK 11) +if (javaVersionMajor() >= 11) { + apply plugin: 'org.openjfx.javafxplugin' + + javafx { + version = javafx_version + modules = ['javafx.controls'] + configuration = 'compile' + } +} + +task checkJdk8() { + // only fail w/o JDK_18 when actually trying to test, not during project setup phase + doLast { + if (!System.env.JDK_18) { + throw new GradleException("JDK_18 environment variable is not defined. " + + "Can't run JDK 8 compatibility tests. " + + "Please ensure JDK 8 is installed and that JDK_18 points to it.") + } + } +} + +task jdk8Test(type: Test, dependsOn: [compileTestKotlin, checkJdk8]) { + classpath = files { test.classpath } + testClassesDirs = files { test.testClassesDirs } + executable = "$System.env.JDK_18/bin/java" +} + +// Run these tests only during nightly stress test +jdk8Test.onlyIf { project.properties['stressTest'] != null } +build.dependsOn jdk8Test diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt index 4d7571c29f..e1e2d246b5 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt @@ -11,6 +11,7 @@ import javafx.util.* import kotlinx.coroutines.* import kotlinx.coroutines.internal.* import kotlinx.coroutines.javafx.JavaFx.delay +import java.lang.UnsupportedOperationException import java.lang.reflect.* import java.util.concurrent.* import kotlin.coroutines.* @@ -115,6 +116,7 @@ private class PulseTimer : AnimationTimer() { } } +/** @return [true] if initialized successfully, and [false] if no display is detected */ internal fun initPlatform(): Boolean = PlatformInitializer.success // Lazily try to initialize JavaFx platform just once @@ -122,34 +124,34 @@ private object PlatformInitializer { val success = run { /* * Try to instantiate JavaFx platform in a way which works - * both on Java 8 and Java 11 and does not produce "illegal reflective access": - * - * 1) Try to invoke javafx.application.Platform.startup if this class is - * present in a classpath. - * 2) If it is not successful and does not because it is already started, - * fallback to PlatformImpl. - * - * Ignore exception anyway in case of unexpected changes in API, in that case - * user will have to instantiate it manually. + * both on Java 8 and Java 11 and does not produce "illegal reflective access". */ - val runnable = Runnable {} - runCatching { - // Invoke public API if it is present - Class.forName("javafx.application.Platform") - .getMethod("startup", java.lang.Runnable::class.java) - .invoke(null, runnable) - }.recoverCatching { exception -> - // Recover -> check re-initialization - val cause = exception.cause - if (exception is InvocationTargetException && cause is IllegalStateException - && "Toolkit already initialized" == cause.message) { - // Toolkit is already initialized -> success, return - Unit - } else { // Fallback to Java 8 API - Class.forName("com.sun.javafx.application.PlatformImpl") + try { + val runnable = Runnable {} + // Invoke the public API if it is present. + runCatching { + Class.forName("javafx.application.Platform") + .getMethod("startup", java.lang.Runnable::class.java) + }.map { method -> + method.invoke(null, runnable) + return@run true + } + // If we are here, it means the public API is not present. Try the private API. + Class.forName("com.sun.javafx.application.PlatformImpl") .getMethod("startup", java.lang.Runnable::class.java) .invoke(null, runnable) + true + } catch (exception: InvocationTargetException) { + // Can only happen as a result of [Method.invoke]. + val cause = exception.cause!! + when { + // Maybe the problem is that JavaFX is already initialized? Everything is good then. + cause is IllegalStateException && "Toolkit already initialized" == cause.message -> true + // If the problem is the headless environment, it is okay. + cause is UnsupportedOperationException && "Unable to open DISPLAY" == cause.message -> false + // Otherwise, the exception demonstrates an anomaly. + else -> throw cause } - }.isSuccess + } } } diff --git a/ui/kotlinx-coroutines-swing/build.gradle b/ui/kotlinx-coroutines-swing/build.gradle index 31761abe10..ad8bef0e2f 100644 --- a/ui/kotlinx-coroutines-swing/build.gradle +++ b/ui/kotlinx-coroutines-swing/build.gradle @@ -1,7 +1,7 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ dependencies { testCompile project(':kotlinx-coroutines-jdk8') -} \ No newline at end of file +} diff --git a/ui/kotlinx-coroutines-swing/test/SwingTest.kt b/ui/kotlinx-coroutines-swing/test/SwingTest.kt index 8b41b494cc..cbed5bf1e9 100644 --- a/ui/kotlinx-coroutines-swing/test/SwingTest.kt +++ b/ui/kotlinx-coroutines-swing/test/SwingTest.kt @@ -1,10 +1,9 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines.swing -import javafx.application.* import kotlinx.coroutines.* import org.junit.* import org.junit.Test @@ -81,7 +80,7 @@ class SwingTest : TestBase() { join(component) } - private suspend fun join(component: SwingTest.SwingComponent) { + private suspend fun join(component: SwingComponent) { component.coroutineContext[Job]!!.join() }