diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1e0537b95bdd..6587e9cbceb0a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ compose-snapshot-version = "1.8.0-SNAPSHOT" ant = "1.10.14" kotlin-for-gradle-plugins-compilation = "2.0.20-RC" intellij-asm = "9.0" +diff-utils = "4.12" # Wasm specific wasmedge = "0.14.0" @@ -99,6 +100,7 @@ xerces = { module = "xerces:xercesImpl", version.ref = "xerces" } ant = { module = "org.apache.ant:ant", version.ref = "ant" } intellij-asm = { module = "org.jetbrains.intellij.deps:asm-all", version.ref = "intellij-asm" } +diff-utils = { module = "io.github.java-diff-utils:java-diff-utils", version.ref = "diff-utils" } [plugins] jetbrains-ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "jetbrains-ideaExt" } diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle71Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle71Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle71Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle74Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle74Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle74Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle75Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle75Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle75Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle76Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle76Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle76Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle80Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle80Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle80Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle81Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle81Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle81Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle82Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle82Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle82Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle85Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle85Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Gradle85Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Jar.deprecations b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Jar.deprecations new file mode 100644 index 0000000000000..a17c6ddf3be21 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/asm-deprecation/Jar.deprecations @@ -0,0 +1,29 @@ +The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin: +package org.jetbrains.kotlin +package org.jetbrains.kotlin.build.report +package org.jetbrains.kotlin.build.report.metrics +package org.jetbrains.kotlin.cli.common +package org.jetbrains.kotlin.cli.common.arguments +package org.jetbrains.kotlin.cli.common.environment +package org.jetbrains.kotlin.cli.common.fir +package org.jetbrains.kotlin.cli.common.messages +package org.jetbrains.kotlin.cli.common.modules +package org.jetbrains.kotlin.cli.common.output +package org.jetbrains.kotlin.cli.common.profiling +package org.jetbrains.kotlin.cli.common.repl +package org.jetbrains.kotlin.cli.jvm +package org.jetbrains.kotlin.cli.jvm.compiler +package org.jetbrains.kotlin.cli.jvm.compiler.pipeline +package org.jetbrains.kotlin.cli.jvm.config +package org.jetbrains.kotlin.cli.jvm.plugins +package org.jetbrains.kotlin.cli.metadata +package org.jetbrains.kotlin.cli.plugins +package org.jetbrains.kotlin.compiler.plugin +package org.jetbrains.kotlin.compilerRunner +package org.jetbrains.kotlin.compilerRunner.btapi +package org.jetbrains.kotlin.config +package org.jetbrains.kotlin.incremental +package org.jetbrains.kotlin.incremental.components +package org.jetbrains.kotlin.incremental.js +package org.jetbrains.kotlin.incremental.storage +package org.jetbrains.kotlin.util.capitalizeDecapitalize diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index 534aaf79cd24a..faaa5ad9a099d 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -6,6 +6,7 @@ plugins { id("gradle-plugin-common-configuration") id("org.jetbrains.kotlinx.binary-compatibility-validator") id("android-sdk-provisioner") + id("asm-deprecating-transformer") } repositories { @@ -266,6 +267,57 @@ tasks { */ pivotVersion = KotlinMetadataPivotVersion(1, 6, 0) } + asmDeprecation { + val exclusions = listOf( + "org.jetbrains.kotlin.gradle.**", // part of the plugin + "org.jetbrains.kotlin.project.model.**", // part of the plugin + "org.jetbrains.kotlin.statistics.**", // part of the plugin + "org.jetbrains.kotlin.swiftexport.ExperimentalSwiftExportDsl", // part of the plugin + "org.jetbrains.kotlin.tooling.**", // part of the plugin + "org.jetbrains.kotlin.org.**", // already shadowed dependencies + "org.jetbrains.kotlin.com.**", // already shadowed dependencies + "org.jetbrains.kotlin.it.unimi.**", // already shadowed dependencies + "org.jetbrains.kotlin.internal.**", // already internal package + ) + val deprecationMessage = """ + You're using a Kotlin compiler class bundled into KGP for its internal needs. + This is discouraged and will not be supported in future releases. + The class in this artifact is scheduled for removal in Kotlin 2.2. Please define dependency on it in an alternative way. + See https://kotl.in/gradle/internal-compiler-symbols for more details + """.trimIndent() + deprecateClassesByPattern("org.jetbrains.kotlin.**", deprecationMessage, exclusions) + } + } + GradlePluginVariant.values().forEach { variant -> + val sourceSet = sourceSets.getByName(variant.sourceSetName) + val taskSuffix = sourceSet.jarTaskName.capitalize() + val shadowJarTaskName = "$EMBEDDABLE_COMPILER_TASK_NAME$taskSuffix" + asmDeprecation { + val dumpTask = registerDumpDeprecationsTask(shadowJarTaskName, taskSuffix) + val dumpAllTask = getOrCreateTask("dumpDeprecations") { + dependsOn(dumpTask) + } + val expectedFileDoesNotExistMessage = """ + The file with expected deprecations for the compiler modules bundled into KGP does not exist. + Run ./gradlew ${project.path}:${dumpTask.name} first to create it. + You may also use ./gradlew ${project.path}:${dumpAllTask.name} to dump deprecations of all fat jars. + Context: https://youtrack.jetbrains.com/issue/KT-70251 + """.trimIndent() + val checkFailureMessage = """ + Expected deprecations applied to the compiler modules bundled into KGP does not match with the actually applied ones. + Run ./gradlew ${project.path}:${dumpTask.name} to see the difference. + You may also use ./gradlew ${project.path}:${dumpAllTask.name} to dump deprecations of all fat jars. + Use INFO level log for the exact deprecated classes set. + Either commit the difference or adjust the package relocation rules in ${buildFile.absolutePath} + Please be sure to leave a comment explaining any changes related to this failure clear enough. + Context: https://youtrack.jetbrains.com/issue/KT-70251 + """.trimIndent() + val checkTask = + registerCheckDeprecationsTask(shadowJarTaskName, taskSuffix, expectedFileDoesNotExistMessage, checkFailureMessage) + named("check") { + dependsOn(checkTask) + } + } } } diff --git a/repo/codebase-tests/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt b/repo/codebase-tests/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt index 9e73d2e5c33d4..198e68095eb68 100644 --- a/repo/codebase-tests/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt +++ b/repo/codebase-tests/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt @@ -140,6 +140,7 @@ class CodeConformanceTest : TestCase() { "repo/gradle-build-conventions/generators/build/generated-sources", "repo/gradle-build-conventions/compiler-tests-convention/build/generated-sources", "repo/gradle-build-conventions/android-sdk-provisioner/build/generated-sources", + "repo/gradle-build-conventions/asm-deprecating-transformer/build/generated-sources", ".gradle/expanded", ) ) diff --git a/repo/gradle-build-conventions/asm-deprecating-transformer/README.md b/repo/gradle-build-conventions/asm-deprecating-transformer/README.md new file mode 100644 index 0000000000000..a8f95e06cb714 --- /dev/null +++ b/repo/gradle-build-conventions/asm-deprecating-transformer/README.md @@ -0,0 +1,71 @@ +# asm-deprecating-transformer + +## Description + +This plugin allows you to deprecate symbols bundled into a fat JAR based on specified filters. This can be particularly useful for hiding bundled dependencies that cannot be relocated for certain reasons. + +* Use with caution and include migration guides in the deprecation message if possible. +* Be mindful of potential Fully Qualified Name (FQN) clashes in the classpath and issues related to classpath ordering. +* This transformer was initially designed to solve a particular problem (KT-70251) and may not fit general needs yet. + +## Usage + +Here is an example of how to apply this transformer to the `shadowJar` task in a project: + +```kotlin +plugins { + id("asm-deprecating-transformer") +} + +// ... + +tasks.named("shadowJar") { + asmDeprecation { + deprecateClassesByPattern( + pattern = "org.example.**", + deprecationMessage = "Deprecated by asm-deprecating-transformer", + exclusions = listOf("org.example.api.**"), + ) + } +} +``` + +Given an original shadow JAR with the following structure: + +``` +com/example/App +org/example/Tree +org/example/api/Fruit +org/example/fruit/Apple +org/example/fruit/Banana +``` + +These classes will be marked as deprecated: + +``` +com/example/App +org/example/Tree <- +org/example/api/Fruit +org/example/fruit/Apple <- +org/example/fruit/Banana <- +``` + +In addition to deprecating classes, you can dump a list of packages containing deprecated classes and verify it against the actually deprecated ones: + +```kotlin +asmDeprecation { + registerDumpDeprecationsTask( + shadowJarTaskName = "shadowJar", + suffix = "Jar", // the task name is "dumpDeprecationsFor" + suffix + ) + val checkTask = registerCheckDeprecationsTask( + shadowJarTaskName = "shadowJar", + suffix = "Jar", // the task name is "checkDeprecationsFor" + suffix + expectedFileDoesNotExistMessage = "Run dumpDeprecationsForJar to create initial dump", + checkFailureMessage = "Actually deprecated classes do not match expected ones. Please take a look.", + ) + named("check") { + dependsOn(checkTask) + } +} +``` \ No newline at end of file diff --git a/repo/gradle-build-conventions/asm-deprecating-transformer/build.gradle.kts b/repo/gradle-build-conventions/asm-deprecating-transformer/build.gradle.kts new file mode 100644 index 0000000000000..9c9088eb49f61 --- /dev/null +++ b/repo/gradle-build-conventions/asm-deprecating-transformer/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + `kotlin-dsl` + id("org.jetbrains.kotlin.jvm") +} + +repositories { + mavenCentral() + gradlePluginPortal() + maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") { + content { + includeGroupByRegex("org\\.jetbrains\\.intellij\\.deps(\\..+)?") + } + } +} + +kotlin { + jvmToolchain(8) + + compilerOptions { + allWarningsAsErrors.set(true) + optIn.add("kotlin.ExperimentalStdlibApi") + freeCompilerArgs.add("-Xsuppress-version-warnings") + } +} + +dependencies { + compileOnly(kotlin("stdlib", embeddedKotlinVersion)) + implementation(libs.intellij.asm) + implementation(libs.kotlinx.metadataJvm) + implementation(libs.diff.utils) + compileOnly(libs.shadow.gradlePlugin) +} diff --git a/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/asm-deprecating-transformer.gradle.kts b/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/asm-deprecating-transformer.gradle.kts new file mode 100644 index 0000000000000..f8e525d52c702 --- /dev/null +++ b/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/asm-deprecating-transformer.gradle.kts @@ -0,0 +1,3 @@ +import org.jetbrains.kotlin.build.asm.AsmDeprecationExtension + +extensions.create("asmDeprecation") \ No newline at end of file diff --git a/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/org/jetbrains/kotlin/build/asm/AsmDeprecationExtension.kt b/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/org/jetbrains/kotlin/build/asm/AsmDeprecationExtension.kt new file mode 100644 index 0000000000000..9c43002913d81 --- /dev/null +++ b/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/org/jetbrains/kotlin/build/asm/AsmDeprecationExtension.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.build.asm + +import com.github.difflib.DiffUtils +import com.github.difflib.UnifiedDiffUtils +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.ArchiveOperations +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.TaskContainer +import org.gradle.api.tasks.util.PatternSet +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.support.serviceOf +import org.jetbrains.org.objectweb.asm.ClassReader +import org.jetbrains.org.objectweb.asm.ClassWriter +import java.io.File +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +/** + * Allows configuring ad-hoc deprecation of classes included in fat jar generated by a [ShadowJar] task. + * Context: https://youtrack.jetbrains.com/issue/KT-70251 + */ +abstract class AsmDeprecationExtension { + /** + * Transforms class FQN-like pattern to file names pattern + * Examples: + * * `org.example.**` -> `org/example/**/*.class` + * * `org.example.Class` -> `org/example/Class.class` + */ + private fun String.transformPattern() = "${replace('.', '/').replace("**", "**/*")}.class" + + fun ShadowJar.deprecateClassesByPattern(pattern: String, deprecationMessage: String, exclusions: List = emptyList()) { + val patternSet = PatternSet() + patternSet.include(pattern.transformPattern()) + patternSet.exclude(exclusions.map { it.transformPattern() }) + val spec = patternSet.asSpec + val archiveOperations = project.serviceOf() + val deprecationList = project.provider { + val outputFile = archiveFile.get().asFile + outputFile.parentFile.resolve("${outputFile.name}$PATH_SUFFIX") + } + inputs.property("pattern", pattern) + inputs.property("deprecationMessage", deprecationMessage) + for ((index, exclusion) in exclusions.withIndex()) { + inputs.property("exclusion$index", exclusion) + } + outputs.file(deprecationList) + doLast { + val intermediateZipFilePath = temporaryDir.resolve("${UUID.randomUUID()}.${archiveExtension.get()}") + ZipOutputStream(intermediateZipFilePath.outputStream()).use { intermediateZipFile -> + val deprecatedPackages = sortedSetOf() + archiveOperations.zipTree(archiveFile.get().asFile).visit { + if (name.endsWith(".class") && spec.isSatisfiedBy(this)) { + val classReader = ClassReader(file.inputStream()) + val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) + val classVisitor = DeprecatingClassTransformer(classWriter, deprecationMessage) { className -> + require(className.contains('.')) { + "Deprecating classes in the default (unnamed) package is not supported. Tried to deprecate $className" + } + logger.info("Deprecating class $className") + deprecatedPackages.add(className.substringBeforeLast(".")) + } + classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) + val newBytes = classWriter.toByteArray() + val newEntry = ZipEntry(path) + intermediateZipFile.putNextEntry(newEntry) + intermediateZipFile.write(newBytes) + intermediateZipFile.closeEntry() + } else { + val newEntry = ZipEntry(if (isDirectory) "$path/" else path) + intermediateZipFile.putNextEntry(newEntry) + if (!isDirectory) { + file.inputStream().use { + it.copyTo(intermediateZipFile) + } + } + intermediateZipFile.closeEntry() + } + } + deprecationList.get().outputStream().bufferedWriter().use { deprecationListWriter -> + deprecationListWriter.appendLine("The following packages are (non-transitively) deprecated by the `asm-deprecating-transformer` plugin:") + deprecatedPackages.forEach { packageName -> + deprecationListWriter.appendLine("package $packageName") + } + } + } + intermediateZipFilePath.renameTo(archiveFile.get().asFile) + } + } + + fun TaskContainer.registerDumpDeprecationsTask(shadowJarTaskName: String, suffix: String) = + register("dumpDeprecationsFor${suffix}") { + val shadowJarTask = named(shadowJarTaskName) + val actualDeprecations = + shadowJarTask.map { it.archiveFile.get().asFile.parentFile.resolve("${it.archiveFile.get().asFile.name}$PATH_SUFFIX") } + from(actualDeprecations) + into(project.layout.projectDirectory.dir(BUILD_DIRECTORY)) + rename { + "$suffix$PATH_SUFFIX" + } + } + + fun TaskContainer.registerCheckDeprecationsTask( + shadowJarTaskName: String, + suffix: String, + expectedFileDoesNotExistMessage: String, + checkFailureMessage: String, + ) = + register("checkDeprecationsFor${suffix}") { + val shadowJarTask = named(shadowJarTaskName) + val actualDeprecations = + shadowJarTask.map { it.archiveFile.get().asFile.parentFile.resolve("${it.archiveFile.get().asFile.name}$PATH_SUFFIX") } + val expectedDeprecations = project.layout.projectDirectory.file("$BUILD_DIRECTORY/$suffix$PATH_SUFFIX") + inputs.file(actualDeprecations) + // `files` to check it manually and give an actionable failure message. + // otherwise Gradle will complain that the input file does not exist + inputs.files(expectedDeprecations) + doFirst { + val expectedFile = expectedDeprecations.asFile + if (!expectedFile.exists()) { + throw GradleException(expectedFileDoesNotExistMessage) + } + val actualFile = actualDeprecations.get() + val diff = getDiff(expectedFile, actualFile) + if (diff != null) { + throw GradleException("$checkFailureMessage\nDifference:\n$diff") + } + } + } + + companion object { + private const val PATH_SUFFIX = ".deprecations" + private const val BUILD_DIRECTORY = "asm-deprecation" + + private fun getDiff(expectedFile: File, actualFile: File): String? { + val expectedLines = expectedFile.readText().lines() + val actualLines = actualFile.readText().lines() + + if (expectedLines == actualLines) + return null + + val patch = DiffUtils.diff(expectedLines, actualLines) + val diff = + UnifiedDiffUtils.generateUnifiedDiff(expectedFile.absolutePath, actualFile.absolutePath, expectedLines, patch, 3) + return diff.joinToString("\n") + } + } +} \ No newline at end of file diff --git a/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/org/jetbrains/kotlin/build/asm/DeprecatingClassTransformer.kt b/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/org/jetbrains/kotlin/build/asm/DeprecatingClassTransformer.kt new file mode 100644 index 0000000000000..8d8f34bec1168 --- /dev/null +++ b/repo/gradle-build-conventions/asm-deprecating-transformer/src/main/kotlin/org/jetbrains/kotlin/build/asm/DeprecatingClassTransformer.kt @@ -0,0 +1,140 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.build.asm + +import kotlinx.metadata.hasAnnotations +import kotlinx.metadata.jvm.KotlinClassMetadata +import org.jetbrains.org.objectweb.asm.AnnotationVisitor +import org.jetbrains.org.objectweb.asm.ClassVisitor +import org.jetbrains.org.objectweb.asm.ClassWriter +import org.jetbrains.org.objectweb.asm.Opcodes + +/** + * ASM JVM class transformer that marks each visited class as @kotlin.Deprecated with the specified [deprecationMessage] + * Context: https://youtrack.jetbrains.com/issue/KT-70251 + */ +class DeprecatingClassTransformer( + cv: ClassWriter, + private val deprecationMessage: String, + private val processedClassCallback: (className: String) -> Unit = {}, +) : ClassVisitor(Opcodes.API_VERSION, cv) { + private var isAlreadyDeprecated = false + + override fun visit( + version: Int, access: Int, name: String, signature: String?, superName: String?, + interfaces: Array?, + ) { + processedClassCallback(name.replace('/', '.')) + val deprecatedAccess = access or Opcodes.ACC_DEPRECATED + super.visit(version, deprecatedAccess, name, signature, superName, interfaces) + } + + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { + when (desc) { + DEPRECATED_ANNOTATION_DESC -> isAlreadyDeprecated = true + METADATA_ANNOTATION_DESC -> return MetadataAnnotationVisitor() + } + return super.visitAnnotation(desc, visible) + } + + override fun visitEnd() { + super.visitEnd() + if (!isAlreadyDeprecated) { + // don't deprecate and don't override the message if the class is already deprecated + val deprecatedAnnotation = super.visitAnnotation(DEPRECATED_ANNOTATION_DESC, true) + deprecatedAnnotation.visit("message", deprecationMessage) + deprecatedAnnotation.visitEnd() + } + } + + /** + * Reads the existing [kotlin.Metadata] and overrides it. + * Does not define the super annotation visitor and does not contain calls to `super`, + * because we don't need to write the existing annotation as is. + * + * The kotlin.Metadata of the supported versions won't change and has only primitive and array values, + * thus we override only [visit] and [visitArray]. + */ + private inner class MetadataAnnotationVisitor : AnnotationVisitor(Opcodes.API_VERSION) { + private val values = mutableMapOf() + + override fun visit(name: String, value: Any?) { + if (value != null) { + values[name] = value + } + } + + override fun visitArray(name: String): AnnotationVisitor { + return object : AnnotationVisitor(Opcodes.API_VERSION) { + private val list = mutableListOf() + + override fun visit(name: String?, value: Any?) { + if (value != null) { + list.add(value as String) + } + } + + override fun visitEnd() { + values[name] = list.toTypedArray() + } + } + } + + private fun writeAnnotation(metadata: Metadata) { + cv.visitAnnotation(METADATA_ANNOTATION_DESC, true).apply { + visit("k", metadata.kind) + visitArray("mv").apply { + metadata.metadataVersion.forEach { visit(null, it) } + visitEnd() + } + visitArray("d1").apply { + metadata.data1.forEach { visit(null, it) } + visitEnd() + } + visitArray("d2").apply { + metadata.data2.forEach { visit(null, it) } + visitEnd() + } + if (metadata.extraString.isNotEmpty()) { + visit("xs", metadata.extraString) + } + if (metadata.packageName.isNotEmpty()) { + visit("pn", metadata.packageName) + } + visit("xi", metadata.extraInt) + visitEnd() + } + } + + @Suppress("UNCHECKED_CAST") + override fun visitEnd() { + val header = Metadata( + kind = values["k"] as Int? ?: 1, + metadataVersion = values["mv"] as IntArray? ?: intArrayOf(), + data1 = values["d1"] as Array? ?: emptyArray(), + data2 = values["d2"] as Array? ?: emptyArray(), + extraString = values["xs"] as String? ?: "", + packageName = values["pn"] as String? ?: "", + extraInt = values["xi"] as Int? ?: 0 + ) + + val metadata = KotlinClassMetadata.readStrict(header) + if (metadata is KotlinClassMetadata.Class) { + // we cover only regular classes/interfaces currently and not file facades or other types of classes + // top-level symbols are hidden by removing .kotlin_module files in `KotlinModuleMetadataVersionBasedSkippingTransformer` + val kClass = metadata.kmClass + kClass.hasAnnotations = true + } + + writeAnnotation(metadata.write()) + } + } + + companion object { + private const val DEPRECATED_ANNOTATION_DESC = "Lkotlin/Deprecated;" + private const val METADATA_ANNOTATION_DESC = "Lkotlin/Metadata;" + } +} \ No newline at end of file diff --git a/repo/gradle-build-conventions/settings.gradle.kts b/repo/gradle-build-conventions/settings.gradle.kts index ff38e3f88a5a6..ac7d67e20f451 100644 --- a/repo/gradle-build-conventions/settings.gradle.kts +++ b/repo/gradle-build-conventions/settings.gradle.kts @@ -31,3 +31,4 @@ include(":prepare-deps") include(":generators") include(":compiler-tests-convention") include(":android-sdk-provisioner") +include(":asm-deprecating-transformer") diff --git a/settings.gradle b/settings.gradle index c6a079c4d26ca..956f285cda64c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,12 @@ pluginManagement { apply from: 'repo/scripts/kotlin-bootstrap.settings.gradle.kts' repositories { + maven { + url "https://packages.jetbrains.team/maven/p/ij/intellij-dependencies" + content { + includeGroupByRegex("org\\.jetbrains\\.intellij\\.deps(\\..+)?") + } + } maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-dependencies" } google() mavenCentral()