Skip to content

Commit

Permalink
Build project using JDK 11 (Kotlin#1733)
Browse files Browse the repository at this point in the history
* Support build on both JDK 1.8 & 11, check for publishing under JDK 11
* Up the Robolectric version to support JDK11.
  According to robolectric/robolectric#4085, by 4.0.2
it should support JDK11.
  Tests do pass after setting the version to 4.0.2, but they fail for every version released after that up to 4.3.1.
  It is unclear what causes this. I commit this to check how it works on the build agents, as some comments in the issue imply that on MacOS this version, too, does not work with JDK11.
* Fix fully qualified names in stacktraces in tests:
  - With move to JDK11, the `park` method changed its fully qualified name.
* Add new sanitazing to verification of stacktraces:
  - Now stacktraces have additional substrings, separated by a slash:
    java-base/java.util.lang
  - They are stripped away.
  - Also, the placement of tabs has changed, and so the tabs are also completely removed.
* Refactor `verifyStackTrace`
  - It used to wrap the only loop where something happened in two other loops that did nothing. Now, only the innermost loop is left.
* Use a separate JavaFx dependency.
* Improve error handling for JavaFX initialization
  - Now, the JavaFX initialization may fail with an exception in case something went wrong.
  - The driver for this change was that the initialization started hanging in headless environments with transition to JDK 11.
  - Before, the initialization logic had a flaw. If a call to one API
failed, another API would be attempted. However, this approach is
problematic: if the first call failed with an exception for some
reason, it would leave JavaFX in a broken state where a flag would
imply that the system is being initialized. Subsequent calls would
then proceed to wait forever for the initialization to complete.
  - Now, exceptions are checked more carefully, ensuring that we only
fall back to the internal API in case the public one is unavailable
and not failed for some valid reason. This differentiation also
allows to more boldly rethrow exceptions upwards, being more or
less confident that these are relevant to the user.
* Additionally test JavaFX integration with JDK8

Co-authored-by: Dmitry Khalanskiy <[email protected]>
Co-authored-by: Roman Elizarov <[email protected]>
  • Loading branch information
elizarov and dkhalanskyjb committed Feb 14, 2020
1 parent 660c2d7 commit 1ac3dc2
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 57 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 20 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ buildscript {
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
classpath "org.jetbrains.kotlinx:kotlinx-knit:$knit_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
Expand Down Expand Up @@ -269,8 +270,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'

Expand Down
8 changes: 8 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,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
Expand Down
8 changes: 4 additions & 4 deletions kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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" +
Expand Down Expand Up @@ -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" +
Expand Down Expand Up @@ -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" +
Expand Down
19 changes: 9 additions & 10 deletions kotlinx-coroutines-debug/test/StracktraceUtils.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -30,16 +33,12 @@ public fun String.applyBackspace(): String {

public fun verifyStackTrace(e: Throwable, traces: List<String>) {
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")
Expand Down
7 changes: 3 additions & 4 deletions ui/kotlinx-coroutines-android/android-unit-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
*/

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")
}
12 changes: 6 additions & 6 deletions ui/kotlinx-coroutines-android/build.gradle
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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'
Expand Down
39 changes: 38 additions & 1 deletion ui/kotlinx-coroutines-javafx/build.gradle
Original file line number Diff line number Diff line change
@@ -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
52 changes: 27 additions & 25 deletions ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -115,41 +116,42 @@ 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
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
}
}
}
4 changes: 2 additions & 2 deletions ui/kotlinx-coroutines-swing/build.gradle
Original file line number Diff line number Diff line change
@@ -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')
}
}
5 changes: 2 additions & 3 deletions ui/kotlinx-coroutines-swing/test/SwingTest.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
}

Expand Down

0 comments on commit 1ac3dc2

Please sign in to comment.