| import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar | |
| import com.google.devtools.ksp.RelativizingInternalPathProvider | |
| import com.google.devtools.ksp.RelativizingPathProvider | |
| import java.io.ByteArrayOutputStream | |
| import java.io.File | |
| import java.util.jar.JarFile | |
| import java.util.zip.ZipEntry | |
| import java.util.zip.ZipFile | |
| import java.util.zip.ZipOutputStream | |
| import org.jetbrains.org.objectweb.asm.ClassReader | |
| import org.jetbrains.org.objectweb.asm.ClassWriter | |
| import org.jetbrains.org.objectweb.asm.commons.ClassRemapper | |
| import org.jetbrains.org.objectweb.asm.commons.Remapper | |
| description = "Kotlin Symbol Processing implementation using Kotlin Analysis API" | |
| val signingKey: String? by project | |
| val signingPassword: String? by project | |
| val kotlinBaseVersion = libs.versions.kotlin.base.get() | |
| val libsForTesting: Configuration by configurations.creating | |
| val libsForTestingCommon: Configuration by configurations.creating | |
| val aaKotlinBaseVersion = libs.versions.aa.kotlin.base.get() | |
| val aaIntellijVersion = libs.versions.aa.intellij.get() | |
| val aaCoroutinesVersion = libs.versions.aa.coroutines.get() | |
| plugins { | |
| kotlin("jvm") | |
| id("org.jetbrains.dokka") | |
| id("com.gradleup.shadow") | |
| `maven-publish` | |
| signing | |
| } | |
| val depSourceJars: Configuration by configurations.creating | |
| val depJarsForCheck: Configuration by configurations.creating | |
| val compilerJar: Configuration by configurations.creating | |
| val originalLog4j: Configuration by configurations.creating | |
| val filteredLog4j = | |
| tasks.register<Jar>("filteredLog4j") { | |
| from(originalLog4j.map { zipTree(it) }) { | |
| exclude("org/apache/log4j/jdbc/**") | |
| } | |
| archiveFileName.set("log4j-filtered.jar") | |
| } | |
| val intellijOriginal: Configuration by configurations.creating { | |
| isTransitive = false | |
| } | |
| /** | |
| * Rewrites any use of `kotlinx/coroutines/internal/intellij/IntellijCoroutines` to | |
| * `com/intellij/util/IntelliJCoroutinesFacade` in dependencies declared in the `intellijOriginal` | |
| * configuration. | |
| */ | |
| abstract class TransformIntellijDeps : DefaultTask() { | |
| @get:InputFiles abstract val inputJars: ConfigurableFileCollection | |
| @get:OutputDirectory abstract val outputDir: DirectoryProperty | |
| @TaskAction | |
| fun transform() { | |
| val remapper = | |
| object : Remapper() { | |
| override fun map(internalName: String): String { | |
| if (internalName == "kotlinx/coroutines/internal/intellij/IntellijCoroutines") { | |
| return "com/intellij/util/IntelliJCoroutinesFacade" | |
| } | |
| return super.map(internalName) | |
| } | |
| } | |
| outputDir.get().asFile.apply { | |
| if (exists()) deleteRecursively() | |
| mkdirs() | |
| } | |
| inputJars.forEach { jarFile -> | |
| val outputFile = outputDir.file(jarFile.name).get().asFile | |
| ZipOutputStream(outputFile.outputStream()).use { zos -> | |
| ZipFile(jarFile).use { zip -> | |
| for (entry in zip.entries()) { | |
| if (entry.isDirectory) { | |
| zos.putNextEntry(ZipEntry(entry.name)) | |
| zos.closeEntry() | |
| continue | |
| } | |
| val bytes = zip.getInputStream(entry).readBytes() | |
| val newBytes = | |
| if (entry.name.endsWith(".class")) { | |
| val cr = ClassReader(bytes) | |
| val cw = ClassWriter(0) | |
| val cv = ClassRemapper(cw, remapper) | |
| cr.accept(cv, 0) | |
| cw.toByteArray() | |
| } else { | |
| bytes | |
| } | |
| zos.putNextEntry(ZipEntry(entry.name)) | |
| zos.write(newBytes) | |
| zos.closeEntry() | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| val transformedIntellijDeps = | |
| tasks.register<TransformIntellijDeps>("transformedIntellijDeps") { | |
| inputJars.from(intellijOriginal) | |
| outputDir.set(layout.buildDirectory.dir("transformedIntellijDeps")) | |
| } | |
| dependencies { | |
| listOf( | |
| "com.jetbrains.intellij.platform:util-rt", | |
| "com.jetbrains.intellij.platform:util-class-loader", | |
| "com.jetbrains.intellij.platform:util-text-matching", | |
| "com.jetbrains.intellij.platform:util", | |
| "com.jetbrains.intellij.platform:util-base", | |
| "com.jetbrains.intellij.platform:util-coroutines", | |
| "com.jetbrains.intellij.platform:util-xml-dom", | |
| "com.jetbrains.intellij.platform:core", | |
| "com.jetbrains.intellij.platform:core-impl", | |
| "com.jetbrains.intellij.platform:extensions", | |
| "com.jetbrains.intellij.platform:diagnostic", | |
| "com.jetbrains.intellij.platform:diagnostic-telemetry", | |
| "com.jetbrains.intellij.java:java-frontback-psi", | |
| "com.jetbrains.intellij.java:java-frontback-psi-impl", | |
| "com.jetbrains.intellij.java:java-psi", | |
| "com.jetbrains.intellij.java:java-psi-impl", | |
| ) | |
| .forEach { | |
| intellijOriginal("$it:$aaIntellijVersion") | |
| depSourceJars("$it:$aaIntellijVersion:sources") { isTransitive = false } | |
| } | |
| implementation(files(transformedIntellijDeps.map { it.outputDir.asFileTree })) | |
| listOf( | |
| "org.jetbrains.kotlin:analysis-api-k2-for-ide", | |
| "org.jetbrains.kotlin:analysis-api-for-ide", | |
| "org.jetbrains.kotlin:low-level-api-fir-for-ide", | |
| "org.jetbrains.kotlin:analysis-api-platform-interface-for-ide", | |
| "org.jetbrains.kotlin:symbol-light-classes-for-ide", | |
| "org.jetbrains.kotlin:analysis-api-standalone-for-ide", | |
| "org.jetbrains.kotlin:analysis-api-impl-base-for-ide", | |
| "org.jetbrains.kotlin:kotlin-compiler-common-for-ide", | |
| "org.jetbrains.kotlin:kotlin-compiler-fir-for-ide", | |
| "org.jetbrains.kotlin:kotlin-compiler-fe10-for-ide", | |
| "org.jetbrains.kotlin:kotlin-compiler-ir-for-ide", | |
| ) | |
| .forEach { | |
| implementation("$it:$aaKotlinBaseVersion") { isTransitive = false } | |
| depSourceJars("$it:$aaKotlinBaseVersion:sources") { isTransitive = false } | |
| } | |
| implementation(libs.aa.collectionsImmutable) | |
| implementation(libs.kotlinx.serialization.json) | |
| compileOnly(kotlin("stdlib", aaKotlinBaseVersion)) | |
| implementation(libs.aa.guava) | |
| implementation(libs.aa.streamex) | |
| implementation(libs.aa.asm) | |
| implementation(libs.aa.stax2) { isTransitive = false } | |
| implementation(libs.aa.aalto.xml) { isTransitive = false } | |
| implementation(libs.aa.caffeine) | |
| implementation(libs.aa.jna) { isTransitive = false } | |
| implementation(libs.aa.jnaPlatform) { isTransitive = false } | |
| implementation(libs.aa.trove4j) { isTransitive = false } | |
| originalLog4j(libs.aa.log4j) { isTransitive = false } | |
| implementation(files(filteredLog4j)) | |
| implementation(libs.aa.jdom) { isTransitive = false } | |
| implementation(libs.aa.javaxInject) | |
| implementation(libs.aa.lz4) { isTransitive = false } | |
| compileOnly(libs.kotlinx.coroutines.coreJvm) | |
| compileOnly(libs.kotlinx.coroutines.core) | |
| implementation( | |
| libs.aa.fastutil | |
| ) { | |
| isTransitive = false | |
| } | |
| implementation(libs.aa.annotations) | |
| implementation(libs.aa.opentelemetry) { isTransitive = false } | |
| compileOnly(project(":common-deps")) | |
| implementation(project(":api")) | |
| implementation(project(":common-util")) | |
| testImplementation(kotlin("stdlib", aaKotlinBaseVersion)) | |
| testImplementation(libs.junit4) | |
| testImplementation(libs.junit.jupiter.api) | |
| testRuntimeOnly(libs.junit.jupiter.engine) | |
| testRuntimeOnly(libs.junit.jupiter.params) | |
| testRuntimeOnly(libs.junit.platform.suite) | |
| testImplementation("org.jetbrains.kotlin:kotlin-compiler:$aaKotlinBaseVersion") | |
| testImplementation( | |
| "org.jetbrains.kotlin:kotlin-compiler-internal-test-framework:$aaKotlinBaseVersion" | |
| ) | |
| testImplementation(project(":common-deps")) | |
| testImplementation(project(":test-utils")) | |
| testImplementation("org.jetbrains.kotlin:analysis-api-test-framework:$aaKotlinBaseVersion") | |
| testImplementation(libs.kotlinx.coroutines.coreJvm) | |
| testImplementation(libs.kotlinx.coroutines.core) | |
| // See AbstractKSPTest's init block if you change any of the `libsForTesting` or | |
| // `libsForTestingCommon` dependencies. | |
| libsForTesting(kotlin("stdlib", aaKotlinBaseVersion)) | |
| libsForTesting(kotlin("test", aaKotlinBaseVersion)) | |
| libsForTesting(kotlin("script-runtime", aaKotlinBaseVersion)) | |
| libsForTestingCommon(kotlin("stdlib-common", aaKotlinBaseVersion)) | |
| depJarsForCheck("org.jetbrains.kotlin:kotlin-stdlib:$kotlinBaseVersion") | |
| depJarsForCheck("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$aaCoroutinesVersion") | |
| depJarsForCheck("org.jetbrains.kotlinx:kotlinx-coroutines-core:$aaCoroutinesVersion") | |
| depJarsForCheck(project(":api")) | |
| depJarsForCheck(project(":common-deps")) | |
| compilerJar("org.jetbrains.kotlin:kotlin-compiler-common-for-ide:$aaKotlinBaseVersion") | |
| } | |
| sourceSets.main { | |
| java.srcDirs("src/main/kotlin") | |
| } | |
| fun Project.javaPluginExtension(): JavaPluginExtension = the() | |
| val JavaPluginExtension.testSourceSet: SourceSet | |
| get() = sourceSets.getByName("test") | |
| val Project.testSourceSet: SourceSet | |
| get() = javaPluginExtension().testSourceSet | |
| repositories { | |
| flatDir { | |
| dirs("${project.rootDir}/third_party/prebuilt/repo/") | |
| } | |
| maven("https://redirector.kotlinlang.org/maven/kotlin-ide-plugin-dependencies") | |
| maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") | |
| } | |
| tasks.withType<org.gradle.jvm.tasks.Jar> { | |
| archiveClassifier.set("real") | |
| } | |
| tasks.withType<ShadowJar>().configureEach { | |
| dependencies { | |
| exclude(project(":api")) | |
| } | |
| exclude("kotlin/**") | |
| exclude("kotlinx/coroutines/**") | |
| archiveClassifier.set("") | |
| mergeServiceFiles() | |
| } | |
| abstract class ValidateShadowJar : DefaultTask() { | |
| @get:Inject abstract val execOperations: ExecOperations | |
| @get:InputFile abstract val inputFile: RegularFileProperty | |
| @get:InputFiles abstract val classpath: ConfigurableFileCollection | |
| @get:InputFile abstract val baselineFile: RegularFileProperty | |
| @get:OutputFile abstract val outputFile: RegularFileProperty | |
| @TaskAction | |
| fun validate() { | |
| val jarJar = inputFile.get().asFile | |
| val depJars = classpath.files | |
| val stdout = ByteArrayOutputStream() | |
| val execResult = execOperations.exec { | |
| isIgnoreExitValue = true // Prevent throwing | |
| executable = "jdeps" | |
| args = | |
| listOf( | |
| "--multi-release", | |
| "base", | |
| "--missing-deps", | |
| "-cp", | |
| depJars.joinToString(File.pathSeparator), | |
| jarJar.path, | |
| ) | |
| standardOutput = stdout | |
| } | |
| if (execResult.exitValue != 0) { | |
| throw Exception("Unable to run jdeps") | |
| } | |
| val actualOutput = stdout.toString() | |
| outputFile.get().asFile.writeText(actualOutput) | |
| val expectedOutput = baselineFile.get().asFile.readText() | |
| // Compare line by line to avoid CRLF and LF mismatches | |
| if (actualOutput.lines() != expectedOutput.lines()) { | |
| throw Exception( | |
| """ | |
| jdeps missing dependencies output has changed. | |
| Compare expected ${baselineFile.get().asFile.absolutePath} with | |
| actual ${outputFile.get().asFile.absolutePath}. | |
| """ | |
| .trimIndent() | |
| ) | |
| } | |
| JarFile(jarJar).use { jarFile -> | |
| jarFile.entries().asSequence().forEach { | |
| if (it.name.contains("org/apache/log4j/jdbc")) { | |
| throw Exception( | |
| """ | |
| Validation failed: Found unexpected package 'org/apache/log4j/jdbc' in the shadow JAR. | |
| """ | |
| .trimIndent() | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| val validateShadowJar = | |
| tasks.register<ValidateShadowJar>("validateShadowJar") { | |
| inputFile.set(tasks.shadowJar.flatMap { it.archiveFile }) | |
| classpath.from(depJarsForCheck.incoming.artifactView {}.files) | |
| baselineFile.set(layout.projectDirectory.file("shadow-validation-baseline.txt")) | |
| outputFile.set(layout.buildDirectory.file("validateShadowJar.txt")) | |
| } | |
| tasks.named("check").configure { | |
| dependsOn(validateShadowJar) | |
| } | |
| val sourcesJar = | |
| tasks.register<Jar>("sourcesJar") { | |
| duplicatesStrategy = DuplicatesStrategy.EXCLUDE | |
| archiveClassifier.set("sources") | |
| from(sourceSets.main.map { it.allSource }) | |
| from(project(":common-util").sourceSets.main.get().allSource) | |
| depSourceJars.resolve().forEach { | |
| from(zipTree(it)) | |
| } | |
| } | |
| val dokkaJavadocJar = | |
| tasks.register<Jar>("dokkaJavadocJar") { | |
| archiveClassifier.set("javadoc") | |
| from(tasks.dokkaJavadoc.flatMap { it.outputDirectory }) | |
| } | |
| publishing { | |
| publications { | |
| create<MavenPublication>("shadow") { | |
| artifactId = "symbol-processing-aa" | |
| artifact(tasks.shadowJar) | |
| artifact(dokkaJavadocJar) | |
| artifact(sourcesJar) | |
| pom { | |
| name.set("com.google.devtools.ksp:symbol-processing-aa") | |
| description.set("KSP implementation on Kotlin Analysis API") | |
| withXml { | |
| fun groovy.util.Node.addDependency( | |
| groupId: String, | |
| artifactId: String, | |
| version: String, | |
| scope: String = "runtime", | |
| ) { | |
| appendNode("dependency").apply { | |
| appendNode("groupId", groupId) | |
| appendNode("artifactId", artifactId) | |
| appendNode("version", version) | |
| appendNode("scope", scope) | |
| } | |
| } | |
| asNode().appendNode("dependencies").apply { | |
| addDependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlinBaseVersion) | |
| addDependency( | |
| "org.jetbrains.kotlinx", | |
| "kotlinx-coroutines-core-jvm", | |
| aaCoroutinesVersion, | |
| ) | |
| addDependency( | |
| "com.google.devtools.ksp", | |
| "symbol-processing-api", | |
| version, | |
| "compile", | |
| ) | |
| addDependency( | |
| "com.google.devtools.ksp", | |
| "symbol-processing-common-deps", | |
| version, | |
| "compile", | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| signing { | |
| isRequired = hasProperty("signingKey") | |
| useInMemoryPgpKeys(signingKey, signingPassword) | |
| sign(extensions.getByType<PublishingExtension>().publications) | |
| } | |
| kotlin { | |
| compilerOptions { | |
| freeCompilerArgs.add("-Xcontext-parameters") | |
| } | |
| } | |
| val copyLibsForTesting = | |
| tasks.register<Copy>("copyLibsForTesting") { | |
| from(configurations["libsForTesting"]) | |
| into("dist/kotlinc/lib") | |
| val escaped = Regex.escape(aaKotlinBaseVersion) | |
| rename("(.+)-$escaped\\.jar", "$1.jar") | |
| } | |
| val copyLibsForTestingCommon = | |
| tasks.register<Copy>("copyLibsForTestingCommon") { | |
| from(configurations["libsForTestingCommon"]) | |
| into("dist/common") | |
| val escaped = Regex.escape(aaKotlinBaseVersion) | |
| rename("(.+)-$escaped\\.jar", "$1.jar") | |
| } | |
| tasks.test { | |
| dependsOn(copyLibsForTesting) | |
| dependsOn(copyLibsForTestingCommon) | |
| maxHeapSize = "2g" | |
| useJUnitPlatform() | |
| systemProperty("idea.is.unit.test", "true") | |
| systemProperty("java.awt.headless", "true") | |
| environment("NO_FS_ROOTS_ACCESS_CHECK", "true") | |
| testLogging { | |
| events("passed", "skipped", "failed") | |
| } | |
| val ideaHomeDir = | |
| layout.buildDirectory.dir("tmp/ideaHome").get().asFile.apply { if (!exists()) mkdirs() } | |
| jvmArgumentProviders.add(RelativizingPathProvider("idea.home.path", ideaHomeDir)) | |
| jvmArgumentProviders.add(RelativizingInternalPathProvider("java.io.tmpdir", temporaryDir)) | |
| } |