blob: 691a3dcae300a7bd513b32ed5ea1c4d9e7c74359 [file] [log] [blame]
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.apache.tools.zip.ZipEntry
import org.apache.tools.zip.ZipOutputStream
import org.gradle.jvm.tasks.Jar
import java.io.InputStreamReader
import java.util.zip.ZipFile
evaluationDependsOn(":kotlin-analysis-api")
val signingKey: String? by project
val signingPassword: String? by project
val kotlinBaseVersion: String by project
val aaKotlinBaseVersion: String by project
val aaIntellijVersion: String by project
val aaCoroutinesVersion: String by project
plugins {
kotlin("jvm")
id("com.gradleup.shadow")
`maven-publish`
signing
}
val packedJars by configurations.creating
dependencies {
packedJars(project(":kotlin-analysis-api", "shadow")) { isTransitive = false }
}
tasks.withType(Jar::class.java).configureEach {
archiveClassifier.set("real")
}
val prefixesToRelocate = listOf(
"com.fasterxml.",
"org.codehaus.",
"com.github.benmanes.caffeine.",
"com.google.common.",
"com.google.devtools.ksp.common.",
"com.google.errorprone.",
"com.google.gwt.",
"com.google.j2objc.",
"com.google.thirdparty.",
"com.intellij.",
"com.sun.jna.",
"gnu.trove.",
"io.opentelemetry.api.",
"it.unimi.dsi.",
"javaslang.",
"javax.inject.",
"javax.annotation.",
"kotlinx.collections.immutable.",
"kotlinx.serialization.",
"org.apache.log4j.",
"org.checkerframework.",
"org.intellij.",
"org.jetbrains.",
"org.jdom.",
"org.picocontainer.",
"one.util.",
"net.jpountz.",
"net.rubygrapefruit.",
"FirNativeForwardDeclarationGetClassCallChecker",
).map {
Pair(it, "ksp." + it)
}
class AAServiceTransformer : Transformer {
private val entries = HashMap<String, String>()
// Names of extension points needs to be relocated, because ShadowJar does that, too.
private val regex = Regex("\"((org\\.jetbrains\\.kotlin\\.|com\\.intellij\\.).+)\"")
override fun canTransformResource(element: FileTreeElement): Boolean {
return element.name.startsWith("META-INF/analysis-api/") ||
element.name == "META-INF/extensions/compiler.xml"
}
override fun getName(): String {
return "AAServiceTransformer"
}
override fun hasTransformedResource(): Boolean {
return entries.size > 0
}
override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
fun putOneEntry(path: String, content: String) {
val entry = ZipEntry(path)
entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time)
os.putNextEntry(entry)
os.write(content.toByteArray())
os.closeEntry()
}
entries.forEach { path, original ->
val patched = original.replace(regex, "\"ksp.$1\"")
when {
path.startsWith("META-INF/analysis-api/") -> {
val more = patched.replace(
"\"/META-INF/extensions/compiler.xml\"",
"\"/META-INF/extensions/ksp_compiler.xml\""
)
putOneEntry(path, more)
}
path == "META-INF/extensions/compiler.xml" -> {
// Keep compiler.xml the same, and patch ksp_compiler.xml.
putOneEntry(path, original)
putOneEntry("META-INF/extensions/ksp_compiler.xml", patched)
}
else -> {
putOneEntry(path, patched)
}
}
}
}
override fun transform(context: TransformerContext) {
val path = context.path
val content = InputStreamReader(context.`is`).readText()
entries[path] = content
}
}
tasks.withType(ShadowJar::class.java).configureEach {
archiveClassifier.set("")
// ShadowJar picks up the `compile` configuration by default and pulls stdlib in.
// Therefore, specifying another configuration instead.
configurations = listOf(packedJars)
prefixesToRelocate.forEach { (f, t) ->
relocate(f, t) {
// Do not rename this hardcoded string in XmlReader.
exclude("com.intellij.projectService")
}
}
mergeServiceFiles()
exclude("META-INF/compiler.version")
exclude("META-INF/*.kotlin_module")
this.transform(AAServiceTransformer())
// All bundled dependencies should be renamed.
doLast {
val violatingFiles = mutableListOf<String>()
archiveFile.get().asFile.let {
for (e in ZipFile(it).entries()) {
if (e.name.endsWith(".class") and !validPackages.contains(e.name))
violatingFiles.add(e.name)
}
}
if (violatingFiles.isNotEmpty()) {
error(
"Detected unrelocated classes that may cause conflicts: " +
violatingFiles.joinToString(System.lineSeparator())
)
}
}
}
val prefixesToRelocateStripped = prefixesToRelocate.map {
Pair(it.first.trim('.'), it.second.trim('.'))
}
// TODO: match with Trie
fun String.replaceWithKsp() =
prefixesToRelocateStripped.fold(this) { acc, (f, t) ->
acc.replace("package $f", "package $t")
.replace("import $f", "import $t")
}
val depSourceDir: Provider<Directory> = layout.buildDirectory.dir("source-jar")
val validPaths = prefixesToRelocate.map {
it.second.split('.').filter { it.isNotEmpty() }.joinToString("/")
} + listOf(
"com/google/devtools/ksp",
"META-INF",
"ksp/FirNativeForwardDeclarationGetClassCallChecker.class",
)
class Trie(paths: List<String>) {
class TrieNode(val key: String)
private val terminals = mutableSetOf<TrieNode>()
private val m = mutableMapOf<Pair<TrieNode?, String>, TrieNode>().apply {
paths.forEach { path ->
var p: TrieNode? = null
for (d in path.split("/")) {
p = getOrPut(Pair(p, d)) { TrieNode(d) }
}
terminals.add(p!!)
}
}
fun contains(s: String): Boolean {
var p: TrieNode? = null
for (d in s.split("/")) {
p = m.get(Pair(p, d))?.also {
if (it in terminals)
return true
} ?: return false
}
return true
}
}
val validPackages = Trie(validPaths)
val copyDeps = tasks.register<Copy>("copyDeps") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
project(":kotlin-analysis-api").configurations.getByName("depSourceJars").resolve().forEach {
from(zipTree(it))
}
from(project(":common-util").sourceSets.main.get().allSource)
into(depSourceDir.map { it.dir("ksp") })
}
val sourcesJar = tasks.register<Jar>("sourcesJar") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set("sources")
from(project(":kotlin-analysis-api").sourceSets.main.get().allSource)
from(copyDeps)
filter { it.replaceWithKsp() }
}
val javadocJar = tasks.register<Jar>("javadocJar") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set("javadoc")
from(project(":kotlin-analysis-api").tasks["dokkaJavadocJar"])
}
publishing {
publications {
create<MavenPublication>("shadow") {
artifactId = "symbol-processing-aa-embeddable"
artifact(javadocJar)
artifact(sourcesJar)
artifact(tasks.shadowJar)
pom {
name.set("com.google.devtools.ksp:symbol-processing-aa-embeddable")
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)
addDependency("com.google.devtools.ksp", "symbol-processing-common-deps", version)
}
}
}
}
}
}
signing {
isRequired = hasProperty("signingKey")
useInMemoryPgpKeys(signingKey, signingPassword)
sign(extensions.getByType<PublishingExtension>().publications)
}
abstract class WriteVersionSrcTask : DefaultTask() {
@get:Input
abstract val kotlinVersion: Property<String>
@get:OutputDirectory
abstract val outputResDir: DirectoryProperty
@TaskAction
fun generate() {
val metaInfDir = outputResDir.get().asFile.resolve("META-INF")
metaInfDir.mkdirs()
File(metaInfDir, "ksp.compiler.version").writeText(kotlinVersion.get())
}
}
val writeVersionSrcTask = tasks.register<WriteVersionSrcTask>(
"generateKSPVersions"
) {
kotlinVersion = aaKotlinBaseVersion
outputResDir = layout.buildDirectory.dir("generated/ksp-versions/META-INF")
}
kotlin {
sourceSets {
main {
resources.srcDir(writeVersionSrcTask)
}
}
}