From 562902b0dc2ce1af58e37ff203ed0f06675562dc Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 11 Jul 2022 10:48:44 +0200 Subject: [PATCH] Fix debug module publication with shadow plugin (#3357) Fixes #3345 Fixes #3334 --- gradle/publish.gradle | 6 +-- integration-testing/build.gradle | 25 ++++++++++++- .../kotlin/DynamicAttachDebugTest.kt | 29 +++++++++++++++ kotlinx-coroutines-debug/build.gradle | 37 +++++++++++++------ 4 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 integration-testing/src/debugDynamicAgentTest/kotlin/DynamicAttachDebugTest.kt diff --git a/gradle/publish.gradle b/gradle/publish.gradle index 00034bfa87..f3b1561dde 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -45,11 +45,7 @@ publishing { // Configure java publications for regular non-MPP modules publications { maven(MavenPublication) { - if (project.name == "kotlinx-coroutines-debug") { - project.shadow.component(it) - } else { - from components.java - } + from components.java artifact sourcesJar } } diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle index c23d35fbe7..985a40ed96 100644 --- a/integration-testing/build.gradle +++ b/integration-testing/build.gradle @@ -23,6 +23,7 @@ dependencies { } sourceSets { + // Test that relies on Guava to reflectively check all Throwable subclasses in coroutines withGuavaTest { kotlin compileClasspath += sourceSets.test.runtimeClasspath @@ -33,6 +34,7 @@ sourceSets { implementation 'com.google.guava:guava:31.1-jre' } } + // Checks correctness of Maven publication (JAR resources) and absence of atomicfu symbols mavenTest { kotlin compileClasspath += sourceSets.test.runtimeClasspath @@ -43,6 +45,7 @@ sourceSets { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" } } + // Checks that kotlinx-coroutines-debug can be used as -javaagent parameter debugAgentTest { kotlin compileClasspath += sourceSets.test.runtimeClasspath @@ -53,6 +56,20 @@ sourceSets { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version" } } + + // Checks that kotlinx-coroutines-debug agent can self-attach dynamically to JVM as standalone dependency + debugDynamicAgentTest { + kotlin + compileClasspath += sourceSets.test.runtimeClasspath + runtimeClasspath += sourceSets.test.runtimeClasspath + + dependencies { + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version" + } + } + + // Checks that kotlinx-coroutines-core can be used as -javaagent parameter coreAgentTest { kotlin compileClasspath += sourceSets.test.runtimeClasspath @@ -93,6 +110,12 @@ task debugAgentTest(type: Test) { systemProperties project.properties.subMap(["overwrite.probes"]) } +task debugDynamicAgentTest(type: Test) { + def sourceSet = sourceSets.debugDynamicAgentTest + testClassesDirs = sourceSet.output.classesDirs + classpath = sourceSet.runtimeClasspath +} + task coreAgentTest(type: Test) { def sourceSet = sourceSets.coreAgentTest def coroutinesCoreJar = sourceSet.runtimeClasspath.filter {it.name == "kotlinx-coroutines-core-jvm-${coroutines_version}.jar" }.singleFile @@ -106,5 +129,5 @@ compileTestKotlin { } check { - dependsOn([withGuavaTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build']) + dependsOn([withGuavaTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build']) } diff --git a/integration-testing/src/debugDynamicAgentTest/kotlin/DynamicAttachDebugTest.kt b/integration-testing/src/debugDynamicAgentTest/kotlin/DynamicAttachDebugTest.kt new file mode 100644 index 0000000000..ff9cac847c --- /dev/null +++ b/integration-testing/src/debugDynamicAgentTest/kotlin/DynamicAttachDebugTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +import org.junit.* +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.* +import org.junit.Test +import java.io.* +import java.lang.IllegalStateException + +class DynamicAttachDebugTest { + + @Test + fun testAgentDumpsCoroutines() = + DebugProbes.withDebugProbes { + runBlocking { + val baos = ByteArrayOutputStream() + DebugProbes.dumpCoroutines(PrintStream(baos)) + // if the agent works, then dumps should contain something, + // at least the fact that this test is running. + Assert.assertTrue(baos.toString().contains("testAgentDumpsCoroutines")) + } + } + + @Test(expected = IllegalStateException::class) + fun testAgentIsNotInstalled() { + DebugProbes.dumpCoroutines(PrintStream(ByteArrayOutputStream())) + } +} diff --git a/kotlinx-coroutines-debug/build.gradle b/kotlinx-coroutines-debug/build.gradle index a420004801..9165a41f9b 100644 --- a/kotlinx-coroutines-debug/build.gradle +++ b/kotlinx-coroutines-debug/build.gradle @@ -8,14 +8,6 @@ configurations { shadowDeps // shaded dependencies, not included into the resulting .pom file compileOnly.extendsFrom(shadowDeps) runtimeOnly.extendsFrom(shadowDeps) - - /* - * It is possible to extend a particular configuration with shadow, - * but in that case it changes dependency type to "runtime" and resolves it - * (so it cannot be further modified). Otherwise, shadow just ignores all dependencies. - */ - shadow.extendsFrom(api) // shadow - resulting configuration with shaded jar file - configureKotlinJvmPlatform(shadow) } dependencies { @@ -39,17 +31,38 @@ java { } jar { - manifest { - attributes "Premain-Class": "kotlinx.coroutines.debug.AgentPremain" - attributes "Can-Redefine-Classes": "true" + setEnabled(false) +} + +// This is a rough estimation of what shadow plugin has been doing with our default configuration prior to +// 1.6.2: https://github.com/johnrengelman/shadow/blob/1ff12fc816629ae5bc331fa3889c8ecfcaee7b27/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy#L72-L82 +// We just emulate it here for backwards compatibility +shadowJar.configure { + def classpath = project.objects.fileCollection().from { -> + project.configurations.findByName('runtimeClasspath') + } + doFirst { + manifest.attributes 'Class-Path': classpath.collect { "${it.name}" }.findAll { it }.join(' ') } } -shadowJar { +def shadowJarTask = shadowJar { classifier null // Shadow only byte buddy, do not package kotlin stdlib configurations = [project.configurations.shadowDeps] relocate('net.bytebuddy', 'kotlinx.coroutines.repackaged.net.bytebuddy') + + manifest { + attributes "Premain-Class": "kotlinx.coroutines.debug.AgentPremain" + attributes "Can-Redefine-Classes": "true" + } +} + +configurations { + artifacts { + add("apiElements", shadowJarTask) + add("runtimeElements", shadowJarTask) + } } def commonKoverExcludes =