Skip to content

Commit cea8ed2

Browse files
authored
Rework shading to upgrade embedded ASM classes to Java 8 (#32)
* Remove shadow plugin * Add custom shader plugin * Upgrade ASM class-files to target Java 8 (with stack-maps for faster loading) * Remove unused XML patch * Support configurable relocations in shader
1 parent d11e89d commit cea8ed2

File tree

4 files changed

+105
-30
lines changed

4 files changed

+105
-30
lines changed

build.gradle.kts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
plugins {
22
java
3+
id("shader")
34
`maven-publish`
45
signing
5-
id("com.gradleup.shadow")
66
id("pl.allegro.tech.build.axion-release")
77
id("io.github.gradle-nexus.publish-plugin")
88
}
@@ -31,25 +31,33 @@ dependencies {
3131
embed(project(":utils"))
3232
embed(project(":class-inject"))
3333
embed(project(":class-match"))
34+
embed(libs.asm)
3435

35-
implementation(libs.asm)
36-
36+
// to satisfy javadoc
37+
compileOnly(libs.asm)
3738
compileOnly(libs.spotbugs.annotations)
3839
}
3940

40-
// collect all subproject output into a single jar
41-
fun allSources(): List<SourceDirectorySet> {
42-
return subprojects.filter { it.name != "testing" }.map { it.sourceSets.main.get().allSource }
41+
// relocate embedded copy of asm to avoid clashes on classpath
42+
shader {
43+
relocate("org/objectweb/" to "datadog/instrument/")
4344
}
45+
46+
// collect all subproject output into a single jar
4447
tasks.jar {
4548
dependsOn(embed)
4649
from(embed.map { zipTree(it) })
50+
// drop this file when embedding asm
51+
exclude("module-info.class")
52+
}
53+
fun allSources(): List<SourceDirectorySet> {
54+
return subprojects.filter { it.name != "testing" }.map { it.sourceSets.main.get().allSource }
4755
}
4856
tasks.javadoc {
4957
dependsOn(embed)
5058
setSource(allSources())
5159
exclude("datadog/instrument/glue", "datadog/instrument/utils/JVM.java")
52-
var javadocOptions = (options as StandardJavadocDocletOptions)
60+
val javadocOptions = (options as StandardJavadocDocletOptions)
5361
if (JavaVersion.current().isJava9Compatible) {
5462
javadocOptions.addBooleanOption("html5", true)
5563
}
@@ -61,16 +69,6 @@ tasks.named<Jar>("sourcesJar") {
6169
from(allSources())
6270
}
6371

64-
// produce "-all" jar with a shaded copy of ASM
65-
tasks.shadowJar {
66-
dependsOn(embed)
67-
from(embed.map { zipTree(it) })
68-
relocate("org.objectweb.asm", "datadog.instrument.asm")
69-
}
70-
tasks.assemble {
71-
dependsOn(tasks.shadowJar)
72-
}
73-
7472
publishing {
7573
publications {
7674
create<MavenPublication>("maven") {
@@ -97,16 +95,6 @@ publishing {
9795
developerConnection = "scm:[email protected]:datadog/dd-instrument-java.git"
9896
url = "https://github.com/datadog/dd-instrument-java"
9997
}
100-
withXml {
101-
// mark ASM dependency as optional
102-
var doc = asElement().ownerDocument
103-
var deps = doc.getElementsByTagName("dependency")
104-
for (i in 0 ..< deps.length) {
105-
var optional = doc.createElement("optional")
106-
optional.textContent = "true"
107-
deps.item(i).appendChild(optional)
108-
}
109-
}
11098
}
11199
}
112100
}

buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repositories {
99
dependencies {
1010
// needed to re-use the 'libs' version catalog between the main project and buildSrc
1111
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
12-
implementation(libs.shadow)
12+
implementation(libs.asm.commons)
1313
implementation(libs.spotless)
1414
implementation(libs.spotbugs)
1515
implementation(libs.axion.release)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import org.objectweb.asm.ClassReader
2+
import org.objectweb.asm.ClassVisitor
3+
import org.objectweb.asm.ClassWriter
4+
import org.objectweb.asm.Opcodes
5+
import org.objectweb.asm.commons.ClassRemapper
6+
import org.objectweb.asm.commons.Remapper
7+
import java.io.FilterReader
8+
import java.io.Reader
9+
import java.io.StringReader
10+
import java.nio.charset.StandardCharsets
11+
12+
// support configuration of relocations via extension
13+
extensions.create("shader", Shader::class)
14+
15+
// automatically apply shading to the main jar task
16+
tasks.named<Jar>("jar").configure {
17+
// prune away unused directories after relocation
18+
exclude { it.isDirectory }
19+
// apply relocations as we add resources to the jar
20+
eachFile(Shader())
21+
// this charset supports safe filtering of binary content, as chars map 1:1 to bytes
22+
filteringCharset = "ISO-8859-1"
23+
}
24+
25+
/** Applies configured relocations as resources are copied into the jar. */
26+
open class Shader : Action<FileCopyDetails> {
27+
companion object {
28+
var relocations: Map<String, String> = mapOf()
29+
}
30+
31+
// accepts relocations from build.gradle.kts
32+
fun relocate(entry: Pair<String, String>) {
33+
relocations += entry
34+
}
35+
36+
override fun execute(t: FileCopyDetails) {
37+
var resourcePath: String = t.path
38+
// relocate any class-file references
39+
if (resourcePath.endsWith(".class")) {
40+
t.filter(ShadeClass::class)
41+
}
42+
// relocate file-name for consistency
43+
relocations.forEach { (oldPath, newPath) ->
44+
resourcePath = resourcePath.replace(oldPath, newPath)
45+
}
46+
t.path = resourcePath
47+
}
48+
}
49+
50+
/** Filters class-files using remapper from asm-commons. */
51+
class ShadeClass(reader: Reader) : FilterReader(shade(reader)) {
52+
companion object {
53+
fun shade(reader: Reader): Reader {
54+
val remapper = object : Remapper(Opcodes.ASM9) {
55+
override fun map(internalName: String?): String? {
56+
var result: String? = internalName
57+
Shader.relocations.forEach { (oldPath, newPath) ->
58+
result = result?.replace(oldPath, newPath)
59+
}
60+
return result
61+
}
62+
63+
override fun mapValue(value: Any?): Any? {
64+
return if (value is String) {
65+
map(value)
66+
} else {
67+
super.mapValue(value)
68+
}
69+
}
70+
}
71+
// rebuild constant-pool from scratch and make sure every method has a stack-map
72+
val cw = ClassWriter(ClassWriter.COMPUTE_FRAMES)
73+
// apply relocation to class-file and upgrade pre-Java 8 class-files to Java 8
74+
val cr = ClassReader(reader.readText().toByteArray(StandardCharsets.ISO_8859_1))
75+
cr.accept(ClassRemapper(ToJava8(cw), remapper), 0)
76+
return StringReader(String(cw.toByteArray(), StandardCharsets.ISO_8859_1))
77+
}
78+
}
79+
}
80+
81+
/** Upgrades pre-Java 8 class-files to Java 8, to allow faster verification at runtime. */
82+
class ToJava8(cv: ClassVisitor) : ClassVisitor(Opcodes.ASM9, cv) {
83+
override fun visit(
84+
version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String?>?
85+
) {
86+
super.visit(version.coerceAtLeast(Opcodes.V1_8), access, name, signature, superName, interfaces)
87+
}
88+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
[versions]
22
asm = "9.9"
3-
shadow = "9.2.2"
43
junit-jupiter = "5.13.4"
54
junit-platform = "1.13.4"
65
assertj = "3.27.5"
@@ -13,7 +12,7 @@ jmh-plugin = "0.7.3"
1312

1413
[libraries]
1514
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
16-
shadow = { module = "com.gradleup.shadow:shadow-gradle-plugin", version.ref = "shadow" }
15+
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
1716
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
1817
junit-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-platform" }
1918
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }

0 commit comments

Comments
 (0)