Skip to content

Commit 228e85b

Browse files
committed
Minify distributable JARs with R8
1 parent 6f81725 commit 228e85b

32 files changed

+170
-75
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ jobs:
1313
with:
1414
java-version: 14
1515

16-
- name: Build & test JAR with Gradle
17-
run: ./gradlew test
16+
- name: Build & test the agent standalone
17+
run: ./gradlew quickTest

.idea/jarRepositories.xml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
buildscript {
2+
repositories {
3+
mavenCentral()
4+
}
5+
dependencies {
6+
classpath 'net.sf.proguard:proguard-gradle:6.2.2'
7+
}
8+
}
9+
10+
111
plugins {
212
id 'java'
313
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
@@ -9,21 +19,13 @@ version '1.0-SNAPSHOT'
919

1020
repositories {
1121
mavenCentral()
12-
}
13-
14-
jar {
15-
manifest {
16-
attributes 'Premain-Class': 'tech.httptoolkit.javaagent.HttpProxyAgent'
17-
attributes 'Agent-Class': 'tech.httptoolkit.javaagent.HttpProxyAgent'
18-
attributes 'Main-Class': 'tech.httptoolkit.javaagent.AttachMain'
19-
20-
attributes 'Can-Redefine-Classes': 'true'
21-
attributes 'Can-Retransform-Classes': 'true'
22+
maven {
23+
url "https://maven.google.com/"
2224
}
25+
}
2326

24-
// We include classes in our package. We *don't* include stub classes used to support multiple
25-
// dependency versions, which are under their corresponding real package names.
26-
include('tech/httptoolkit/javaagent/**/*')
27+
configurations {
28+
r8
2729
}
2830

2931
dependencies {
@@ -40,6 +42,9 @@ dependencies {
4042
testImplementation group: 'io.kotest', name: 'kotest-runner-junit5-jvm', version: '4.4.0'
4143
testImplementation group: 'io.kotest', name: 'kotest-assertions-core-jvm', version: '4.4.0'
4244
testImplementation "com.github.tomakehurst:wiremock-jre8:2.27.2"
45+
46+
// Only used during the R8 build task
47+
r8 group: 'com.android.tools', name: 'r8', version: '2.1.75'
4348
}
4449

4550
compileJava {
@@ -53,13 +58,19 @@ compileKotlin {
5358
}
5459
}
5560

56-
test {
57-
// We need to build both JARs before the integration tests can run
58-
dependsOn('shadowJar')
59-
dependsOn(':test-app:shadowJar')
60-
useJUnitPlatform()
61+
tasks.withType(Jar) {
62+
manifest {
63+
attributes 'Premain-Class': 'tech.httptoolkit.javaagent.HttpProxyAgent'
64+
attributes 'Agent-Class': 'tech.httptoolkit.javaagent.HttpProxyAgent'
65+
attributes 'Main-Class': 'tech.httptoolkit.javaagent.AttachMain'
66+
67+
attributes 'Can-Redefine-Classes': 'true'
68+
attributes 'Can-Retransform-Classes': 'true'
69+
}
6170
}
6271

72+
// First, we bundle everything into a workable standalone JAR, with all runtime source included plus
73+
// dependencies plus agent metadata:
6374
shadowJar {
6475
minimize()
6576
exclude '**/*.kotlin_metadata'
@@ -68,16 +79,82 @@ shadowJar {
6879
exclude '**/module_info.class'
6980
exclude 'META-INF/maven/**'
7081

71-
// We have to specifically exclude packages here, because we *do* want to include lots of non-local code, just not
72-
// these specific client stubs:
82+
// We have to specifically exclude our reactor stub code here, because we don't want to the type
83+
// stubs that we've manually defined in our own source included here.
7384
exclude 'reactor/'
7485
}
7586

87+
// As part of bundling the JAR, we relocate all dependencies into our namespace:
7688
import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation
77-
7889
task relocateShadowJar(type: ConfigureShadowRelocation) {
7990
target = tasks.shadowJar
8091
prefix = "tech.httptoolkit.relocated"
8192
}
93+
tasks.shadowJar.dependsOn tasks.relocateShadowJar
94+
95+
// Then we take this bundled JAR and optimize it. This shrinks it dramatically, but also breaks it, because
96+
// bytebuddy depends on some of our source being unmodified by R8 (frames in advice classes get messed with).
97+
def r8File = new File("$buildDir/libs/$archivesBaseName-r8.jar")
98+
tasks.register('r8Jar', JavaExec) { task ->
99+
def rules = file('r8-rules.txt')
100+
task.dependsOn(tasks.shadowJar)
101+
task.outputs.file(r8File)
102+
103+
task.classpath(configurations.r8)
104+
task.main = 'com.android.tools.r8.R8'
105+
task.args = [
106+
'--release',
107+
'--classfile',
108+
'--output', r8File.toString(),
109+
'--pg-conf', rules.toString()
110+
] + configurations.compileOnly.collect {path ->
111+
['--lib', path.toString()] // Include libs from every runtime-only dep, so R8 can resolve them
112+
}.flatten()
113+
114+
doFirst {
115+
def java8Home = System.getenv("JAVA_HOME_8_X64")
116+
if (java8Home == null || java8Home.empty) {
117+
throw new GradleException("\$JAVA_HOME_8_X64 must be set to build a minified distributable")
118+
} else {
119+
// AFAICT R8 only supports the Java 8 lib files. We require that to be available, configured by env
120+
task.args += "--lib"
121+
task.args += java8Home
122+
}
123+
124+
task.args += shadowJar.getArchiveFile().get().asFile.toString()
125+
}
126+
}
127+
128+
// Then we fix this, by taking the raw advice classes for our own source from the original bundled JAR (i.e. including
129+
// any relocated references) and combining that with the minified & optimized dependencies from R8, to get a single
130+
// bundled and 99% optimized JAR.
131+
task distJar(type: Jar) {
132+
dependsOn(tasks.shadowJar, tasks.r8Jar)
133+
archiveClassifier = 'dist'
134+
135+
// Pull raw advice classes from the shadow JAR, unminified:
136+
from (zipTree(shadowJar.getArchiveFile())) {
137+
include "tech/httptoolkit/javaagent/advice/**/*"
138+
}
139+
140+
// Pull other source & bundled dependencies in their minified form, from R8:
141+
from (zipTree(r8Jar.outputs.files[0])) {
142+
exclude "tech/httptoolkit/javaagent/advice/**/*"
143+
}
144+
}
145+
146+
tasks.withType(Test) {
147+
// We need to build both JARs before the integration tests can run
148+
dependsOn('shadowJar')
149+
dependsOn(':test-app:shadowJar')
150+
useJUnitPlatform()
151+
}
152+
153+
task quickTest(type: Test) {
154+
environment 'TEST_JAR', tasks.shadowJar.getArchiveFile().get().asFile.toString()
155+
}
82156

83-
tasks.shadowJar.dependsOn tasks.relocateShadowJar
157+
task distTest(type: Test) {
158+
environment 'TEST_JAR', tasks.distJar.getArchiveFile().get().asFile.toString()
159+
dependsOn('distJar')
160+
}

r8-rules.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-dontobfuscate
2+
-allowaccessmodification
3+
-keepattributes SourceFile, LineNumberTable, *Annotation*
4+
5+
-keep class tech.httptoolkit.javaagent.** { *; }
6+
-keep class tech.httptoolkit.relocated.net.bytebuddy.asm.** { *; }

src/main/java/tech/httptoolkit/javaagent/OverrideSslContextFieldAdvice.java renamed to src/main/java/tech/httptoolkit/javaagent/advice/OverrideSslContextFieldAdvice.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package tech.httptoolkit.javaagent;
1+
package tech.httptoolkit.javaagent.advice;
22

33
import net.bytebuddy.asm.Advice;
4+
import tech.httptoolkit.javaagent.HttpProxyAgent;
45

56
import javax.net.ssl.SSLContext;
67

src/main/java/tech/httptoolkit/javaagent/ReturnProxyAddressAdvice.java renamed to src/main/java/tech/httptoolkit/javaagent/advice/ReturnProxyAddressAdvice.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package tech.httptoolkit.javaagent;
1+
package tech.httptoolkit.javaagent.advice;
22

33
import net.bytebuddy.asm.Advice;
4+
import tech.httptoolkit.javaagent.HttpProxyAgent;
45

56
import java.net.InetSocketAddress;
67
import java.net.SocketAddress;

src/main/java/tech/httptoolkit/javaagent/ReturnProxyAdvice.java renamed to src/main/java/tech/httptoolkit/javaagent/advice/ReturnProxyAdvice.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package tech.httptoolkit.javaagent;
1+
package tech.httptoolkit.javaagent.advice;
22

33
import net.bytebuddy.asm.Advice;
4+
import tech.httptoolkit.javaagent.HttpProxyAgent;
45

56
import java.net.InetSocketAddress;
67
import java.net.Proxy;

src/main/java/tech/httptoolkit/javaagent/ReturnProxySelectorAdvice.java renamed to src/main/java/tech/httptoolkit/javaagent/advice/ReturnProxySelectorAdvice.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tech.httptoolkit.javaagent;
1+
package tech.httptoolkit.javaagent.advice;
22

33
import net.bytebuddy.asm.Advice;
44

src/main/java/tech/httptoolkit/javaagent/ReturnSslContextAdvice.java renamed to src/main/java/tech/httptoolkit/javaagent/advice/ReturnSslContextAdvice.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tech.httptoolkit.javaagent;
1+
package tech.httptoolkit.javaagent.advice;
22

33
import net.bytebuddy.asm.Advice;
44

src/main/java/tech/httptoolkit/javaagent/ReturnSslSocketFactoryAdvice.java renamed to src/main/java/tech/httptoolkit/javaagent/advice/ReturnSslSocketFactoryAdvice.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package tech.httptoolkit.javaagent;
1+
package tech.httptoolkit.javaagent.advice;
22

33
import net.bytebuddy.asm.Advice;
4+
import tech.httptoolkit.javaagent.HttpProxyAgent;
45

56
import javax.net.ssl.SSLSocketFactory;
67

0 commit comments

Comments
 (0)