diff --git a/build.gradle.kts b/build.gradle.kts index dc4a7f12f6..7b9248ca49 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -95,7 +95,19 @@ allprojects { } // needs to be before evaluationDependsOn due to weird Gradle ordering -apply(plugin = "animalsniffer-conventions") +configure(subprojects) { + fun Project.shouldSniff(): Boolean = + platformOf(project) == "jvm" && project.name !in unpublished && project.name !in sourceless + && project.name !in androidNonCompatibleProjects + // Skip JDK 8 projects or unpublished ones + if (shouldSniff()) { + if (isMultiplatform) { + apply(plugin = "animalsniffer-multiplatform-conventions") + } else { + apply(plugin = "animalsniffer-jvm-conventions") + } + } +} configure(subprojects.filter { !sourceless.contains(it.name) }) { if (isMultiplatform) { @@ -157,4 +169,3 @@ rootProject.registerTopLevelDeployTask() // Report Kotlin compiler version when building project println("Using Kotlin compiler version: ${KotlinCompilerVersion.VERSION}") - diff --git a/buildSrc/src/main/kotlin/AuxBuildConfiguration.kt b/buildSrc/src/main/kotlin/AuxBuildConfiguration.kt index 558f95c763..be3770e932 100644 --- a/buildSrc/src/main/kotlin/AuxBuildConfiguration.kt +++ b/buildSrc/src/main/kotlin/AuxBuildConfiguration.kt @@ -13,7 +13,6 @@ object AuxBuildConfiguration { @JvmStatic fun configure(rootProject: Project) { rootProject.allprojects { - workaroundForCleanTask() CacheRedirector.configure(this) workaroundForCoroutinesLeakageToClassPath() } @@ -27,18 +26,6 @@ object AuxBuildConfiguration { } } - private fun Project.workaroundForCleanTask() { - // the 'clean' task cannot delete expanded.lock file on Windows as it is still held by Gradle, failing the build - // Gradle issue: https://github.com/gradle/gradle/issues/25752 - tasks { - val clean by existing(Delete::class) { - setDelete(fileTree(layout.buildDirectory) { - exclude("tmp/.cache/expanded/expanded.lock") - }) - } - } - } - /* * 'kotlinx-coroutines-core' dependency leaks into test runtime classpath via 'kotlin-compiler-embeddable' * and conflicts with our own test/runtime incompatibilities (e.g. when class is moved from a main to test), diff --git a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts deleted file mode 100644 index 74693f9dca..0000000000 --- a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -import ru.vyarus.gradle.plugin.animalsniffer.* - -configure(subprojects) { - // Skip JDK 8 projects or unpublished ones - if (!shouldSniff()) return@configure - apply(plugin = "ru.vyarus.animalsniffer") - project.plugins.withType(JavaPlugin::class.java) { - configure { - sourceSets = listOf((project.extensions.getByName("sourceSets") as SourceSetContainer).getByName("main")) - } - val signature: Configuration by configurations - dependencies { - 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().configureEach { - exclude("**/future/FutureKt*") - exclude("**/future/ContinuationHandler*") - exclude("**/future/CompletableFutureCoroutine*") - - exclude("**/stream/StreamKt*") - exclude("**/stream/StreamFlow*") - - exclude("**/time/TimeKt*") - } - } - } -} - -fun Project.shouldSniff(): Boolean { - // Skip all non-JVM projects - if (platformOf(project) != "jvm") return false - val name = project.name - if (name in unpublished || name in sourceless || name in androidNonCompatibleProjects) return false - return true -} diff --git a/buildSrc/src/main/kotlin/animalsniffer-jvm-conventions.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-jvm-conventions.gradle.kts new file mode 100644 index 0000000000..5ebd5c7248 --- /dev/null +++ b/buildSrc/src/main/kotlin/animalsniffer-jvm-conventions.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("ru.vyarus.animalsniffer") +} + +project.plugins.withType(JavaPlugin::class.java) { + val signature: Configuration by configurations + dependencies { + signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature") + signature("org.codehaus.mojo.signature:java17:1.0@signature") + } +} diff --git a/buildSrc/src/main/kotlin/animalsniffer-multiplatform-conventions.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-multiplatform-conventions.gradle.kts new file mode 100644 index 0000000000..08b11d003d --- /dev/null +++ b/buildSrc/src/main/kotlin/animalsniffer-multiplatform-conventions.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("ru.vyarus.animalsniffer") +} + +project.plugins.withType(KotlinMultiplatformConventionsPlugin::class.java) { + val signature: Configuration by configurations + dependencies { + signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature") + signature("org.codehaus.mojo.signature:java17:1.0@signature") + } +} diff --git a/gradle.properties b/gradle.properties index 88b96ac426..d86c6c8edb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,7 +28,7 @@ jna_version=5.9.0 # Gradle jdk_toolchain_version=11 -animalsniffer_version=1.7.1 +animalsniffer_version=2.0.0 # Android versions android_version=4.1.1.4 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd49177..9bbc975c74 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e6aba2515d..2a6e21b2ba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip +distributionSha256Sum=fba8464465835e74f7270bbf43d6d8a8d7709ab0a43ce1aa3323f73e9aa0c612 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4269..faf93008b7 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..9b42019c79 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/kotlinx-coroutines-bom/build.gradle.kts b/kotlinx-coroutines-bom/build.gradle.kts index 3eb59266ee..b600e9c3c4 100644 --- a/kotlinx-coroutines-bom/build.gradle.kts +++ b/kotlinx-coroutines-bom/build.gradle.kts @@ -1,4 +1,5 @@ import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication +import java.util.Locale plugins { id("java-platform") @@ -38,7 +39,9 @@ publishing { forEach { pub -> pub as DefaultMavenPublication pub.unsetModuleDescriptorGenerator() - tasks.matching { it.name == "generateMetadataFileFor${pub.name.capitalize()}Publication" }.all { + tasks.matching { + it.name == "generateMetadataFileFor${ pub.name.replaceFirstChar { it.uppercaseChar() } }Publication" + }.all { onlyIf { false } } } diff --git a/kotlinx-coroutines-core/build.gradle.kts b/kotlinx-coroutines-core/build.gradle.kts index ce3250c150..8ddea4f5d3 100644 --- a/kotlinx-coroutines-core/build.gradle.kts +++ b/kotlinx-coroutines-core/build.gradle.kts @@ -1,11 +1,13 @@ import org.gradle.api.tasks.testing.* import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.targets.native.tasks.* import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.testing.* +import ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer plugins { kotlin("multiplatform") @@ -84,11 +86,6 @@ kotlin { } } } - - jvm { - // For animal sniffer - withJava() - } } private fun KotlinMultiplatformExtension.setupBenchmarkSourceSets(ss: NamedDomainObjectContainer) { @@ -293,3 +290,19 @@ artifacts { tasks.named("dokkaHtmlPartial") { dependsOn(jvmJar) } + +// Specific files so nothing from core is accidentally skipped +tasks.withType { + exclude("**/future/FutureKt*") + exclude("**/future/ContinuationHandler*") + exclude("**/future/CompletableFutureCoroutine*") + + exclude("**/stream/StreamKt*") + exclude("**/stream/StreamFlow*") + + exclude("**/time/TimeKt*") +} + +animalsniffer { + defaultTargets = setOf("jvmMain") +} diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt index bdfbe6dbbc..9bd48b1537 100644 --- a/kotlinx-coroutines-core/jvm/src/Executors.kt +++ b/kotlinx-coroutines-core/jvm/src/Executors.kt @@ -117,12 +117,13 @@ private class DispatcherExecutor(@JvmField val dispatcher: CoroutineDispatcher) internal class ExecutorCoroutineDispatcherImpl(override val executor: Executor) : ExecutorCoroutineDispatcher(), Delay { + /* + * Attempts to reflectively (to be Java 6 compatible) invoke + * ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy in order to cleanup + * internal scheduler queue on cancellation. + */ init { - /* Attempt to invoke ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy in order to clean up - * the internal scheduler queue on cancellation. */ - if (executor is ScheduledThreadPoolExecutor) { - executor.removeOnCancelPolicy = true - } + removeFutureOnCancel(executor) } override fun dispatch(context: CoroutineContext, block: Runnable) { diff --git a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt index ee35a3a011..db2cb0da39 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt @@ -1,6 +1,9 @@ package kotlinx.coroutines.internal +import java.lang.reflect.Method import java.util.* +import java.util.concurrent.Executor +import java.util.concurrent.ScheduledThreadPoolExecutor import kotlin.concurrent.withLock as withLockJvm internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock @@ -18,3 +21,22 @@ internal actual annotation class BenignDataRace() @Suppress("NOTHING_TO_INLINE") // So that R8 can completely remove ConcurrentKt class internal actual inline fun identitySet(expectedSize: Int): MutableSet = Collections.newSetFromMap(IdentityHashMap(expectedSize)) + +private val REMOVE_FUTURE_ON_CANCEL: Method? = try { + ScheduledThreadPoolExecutor::class.java.getMethod("setRemoveOnCancelPolicy", Boolean::class.java) +} catch (_: Throwable) { + null +} + +/* We can not simply call `setRemoveOnCancelPolicy`, even though the code would compile and tests would pass, + * because older Android versions don't support it. */ +@Suppress("NAME_SHADOWING") +internal fun removeFutureOnCancel(executor: Executor): Boolean { + try { + val executor = executor as? ScheduledThreadPoolExecutor ?: return false + (REMOVE_FUTURE_ON_CANCEL ?: return false).invoke(executor, true) + return true + } catch (_: Throwable) { + return false // failed to setRemoveOnCancelPolicy, assume it does not remove the future on cancel + } +} diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index fb06e6c177..5e29e6ae75 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -1,5 +1,4 @@ -import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.targets.js.dsl.* +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl kotlin { sourceSets { diff --git a/reactive/kotlinx-coroutines-rx2/build.gradle.kts b/reactive/kotlinx-coroutines-rx2/build.gradle.kts index 09562c4e11..bc7ac285a4 100644 --- a/reactive/kotlinx-coroutines-rx2/build.gradle.kts +++ b/reactive/kotlinx-coroutines-rx2/build.gradle.kts @@ -18,7 +18,7 @@ tasks.withType(DokkaTaskPartial::class) { val testNG by tasks.registering(Test::class) { useTestNG() - reports.html.outputLocation = file("$buildDir/reports/testng") + reports.html.outputLocation = file("${layout.buildDirectory.get()}/reports/testng") include("**/*ReactiveStreamTckTest.*") // Skip testNG when tests are filtered with --tests, otherwise it simply fails onlyIf { @@ -32,5 +32,5 @@ val testNG by tasks.registering(Test::class) { val test by tasks.getting(Test::class) { dependsOn(testNG) - reports.html.outputLocation = file("$buildDir/reports/junit") + reports.html.outputLocation = file("${layout.buildDirectory.get()}/reports/junit") } diff --git a/reactive/kotlinx-coroutines-rx3/build.gradle.kts b/reactive/kotlinx-coroutines-rx3/build.gradle.kts index 1b761b6fd4..f88d2ecb30 100644 --- a/reactive/kotlinx-coroutines-rx3/build.gradle.kts +++ b/reactive/kotlinx-coroutines-rx3/build.gradle.kts @@ -18,7 +18,7 @@ tasks.withType(DokkaTaskPartial::class) { val testNG by tasks.registering(Test::class) { useTestNG() - reports.html.outputLocation = file("$buildDir/reports/testng") + reports.html.outputLocation = file("${layout.buildDirectory.get()}/reports/testng") include("**/*ReactiveStreamTckTest.*") // Skip testNG when tests are filtered with --tests, otherwise it simply fails onlyIf { @@ -32,5 +32,5 @@ val testNG by tasks.registering(Test::class) { val test by tasks.getting(Test::class) { dependsOn(testNG) - reports.html.outputLocation = file("$buildDir/reports/junit") + reports.html.outputLocation = file("${layout.buildDirectory.get()}/reports/junit") }