-
Notifications
You must be signed in to change notification settings - Fork 14
/
excludeKotlinDefaultImpls.kt
62 lines (55 loc) · 2.43 KB
/
excludeKotlinDefaultImpls.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package gg.essential.gradle.multiversion
import org.gradle.api.GradleException
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.ClassNode
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
/**
* Removes Kotlin's `$DefaultImpls` classes (and any references to them) from the given jar file as if the Kotlin code
* was compiled with `-Xjvm-default=all`.
*
* This is useful if you have a platform-independent "common" project containing the vast majority of your code but, to
* maintain backwards compatibility, you need to compile with `-Xjvm-default=all-compatibility` for some platforms.
* Ordinarily this would then leak the DefaultImpls to all platforms and you'll be stuck with `all-compatibility` for
* all of them (even when that would not have been required for modern versions).
*
* For such cases, this method allows you to strip the `$DefaultImpls` classes from a given platform-specific jar file:
* ```kotlin
* tasks.jar {
* if (platform.mcVersion >= 11400) {
* excludeKotlinDefaultImpls()
* }
* }
* ```
*/
fun AbstractArchiveTask.excludeKotlinDefaultImpls() {
doLast { excludeKotlinDefaultImpls(archiveFile.get().asFile.toPath()) }
}
/**
* See [excludeKotlinDefaultImpls].
*
* Modifies the given jar file in place.
*/
fun excludeKotlinDefaultImpls(jarPath: Path) {
FileSystems.newFileSystem(jarPath).use { fileSystem ->
val defaultImplsFiles = Files.walk(fileSystem.getPath("/")).use { stream ->
stream.filter { it.fileName?.toString()?.endsWith("\$DefaultImpls.class") == true }.toList()
}
for (defaultImplsFile in defaultImplsFiles) {
val baseFile = defaultImplsFile.resolveSibling(defaultImplsFile.fileName.toString().replace("\$DefaultImpls", ""))
if (Files.notExists(baseFile)) {
throw GradleException("Found $defaultImplsFile but no matching base class $baseFile.")
}
Files.write(baseFile, removeReferences(Files.readAllBytes(baseFile)))
Files.delete(defaultImplsFile)
}
}
}
private fun removeReferences(bytes: ByteArray): ByteArray {
val node = ClassNode().apply { ClassReader(bytes).accept(this, 0) }
node.innerClasses.removeIf { it.name.endsWith("\$DefaultImpls") }
return ClassWriter(0).apply { node.accept(this) }.toByteArray()
}