-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
MetaInfStripTransform.kt
172 lines (157 loc) · 8.14 KB
/
MetaInfStripTransform.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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package io.sentry.android.gradle.transforms
import io.sentry.android.gradle.SentryPlugin
import io.sentry.android.gradle.util.warn
import java.io.File
import java.nio.file.Files
import java.util.jar.Attributes
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.artifacts.transform.CacheableTransform
import org.gradle.api.artifacts.transform.InputArtifact
import org.gradle.api.artifacts.transform.TransformAction
import org.gradle.api.artifacts.transform.TransformOutputs
import org.gradle.api.artifacts.transform.TransformParameters
import org.gradle.api.attributes.Attribute
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
/**
* Gradle's [TransformAction] that strips out unsupported Java classes from
* resources/META-INF/versions folder of a jar. This is the case for [Multi-Release JARs](https://openjdk.java.net/jeps/238),
* when a single jar contains classes for different Java versions.
*
* For Android it may have bad consequences, as the min supported Java version is 11 at the moment,
* and this can cause incompatibilities, if AGP or other plugins erroneously consider .class files
* under the META-INF folder during build-time transformations/instrumentation.
*
* The minimum supported Java version of the latest AGP is 11 -> https://developer.android.com/studio/releases/gradle-plugin#7-1-0
*/
@CacheableTransform
abstract class MetaInfStripTransform : TransformAction<MetaInfStripTransform.Parameters> {
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
interface Parameters : TransformParameters {
// only used for development purposes
@get:Input
@get:Optional
val invalidate: Property<Long>
}
override fun transform(outputs: TransformOutputs) {
val input = inputArtifact.get().asFile
val jarFile = JarFile(input)
if (jarFile.isMultiRelease) {
val outputFilename = "${input.nameWithoutExtension}-meta-inf-stripped.jar"
val tmpOutputFile = File.createTempFile("tmp-${input.nameWithoutExtension}", ".jar")
var isStillMultiRelease = false
tmpOutputFile.jarOutputStream().use { outStream ->
val entries = jarFile.entries()
// copy each .jar entry, except those that are under META-INF/versions/${unsupported_java_version}
while (entries.hasMoreElements()) {
val jarEntry = entries.nextElement()
if (jarEntry.name.equals(JarFile.MANIFEST_NAME, ignoreCase = true)) {
// we deal with the manifest as a last step, since we need to know if there
// any multi-release entries remained
continue
}
if (jarEntry.isSignatureEntry) {
SentryPlugin.logger.warn {
"""
Signed Multirelease Jar (${jarFile.name}) found, skipping transform.
This might lead to auto-instrumentation issues due to a bug in AGP (https://issuetracker.google.com/issues/206655905).
Please update to AGP >= 7.1.2 (https://developer.android.com/studio/releases/gradle-plugin) in order to keep using `autoInstrumentation` option.
""".trimIndent()
}
tmpOutputFile.delete()
outputs.file(inputArtifact)
return
}
if (jarEntry.name.startsWith(versionsDir, ignoreCase = true)) {
val javaVersion = jarEntry.javaVersion
if (javaVersion > MIN_SUPPORTED_JAVA_VERSION) {
continue
} else if (javaVersion > 0) {
isStillMultiRelease = true
}
}
outStream.putNextEntry(jarEntry)
jarFile.getInputStream(jarEntry).buffered().copyTo(outStream)
outStream.closeEntry()
}
val manifest = jarFile.manifest
if (manifest != null) {
// write MANIFEST.MF as a last entry and modify Multi-Release attribute accordingly
manifest.mainAttributes.put(
Attributes.Name.MULTI_RELEASE,
isStillMultiRelease.toString()
)
outStream.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME))
manifest.write(outStream.buffered())
outStream.closeEntry()
}
}
val transformedOutput = outputs.file(outputFilename)
Files.move(tmpOutputFile.toPath(), transformedOutput.toPath())
} else {
outputs.file(inputArtifact)
}
}
private val JarEntry.isSignatureEntry get() = signatureFileRegex.matches(name)
private val JarEntry.javaVersion: Int get() = regex.find(name)?.value?.toIntOrNull() ?: 0
private fun File.jarOutputStream(): JarOutputStream = JarOutputStream(outputStream())
companion object {
private val regex = "(?<=/)([0-9]*)(?=/)".toRegex()
private val signatureFileRegex = "^META-INF/.*\\.(SF|DSA|RSA|EC)|^META-INF/SIG-.*"
.toRegex()
private const val versionsDir = "META-INF/versions/"
private const val MIN_SUPPORTED_JAVA_VERSION = 11
internal val artifactType: Attribute<String> =
Attribute.of("artifactType", String::class.java)
internal val metaInfStripped: Attribute<Boolean> =
Attribute.of("meta-inf-stripped", Boolean::class.javaObjectType)
internal fun register(dependencies: DependencyHandler, forceInstrument: Boolean) {
// add our attribute to schema
dependencies.attributesSchema { schema ->
schema.attribute(metaInfStripped) { matchingStrategy ->
/*
* This is necessary so our transform is selected before the AGP's one.
* AGP's transform chain looks roughly like that:
*
* IdentityTransform (jar to processed-jar) -> IdentityTransform (processed-jar to classes-jar)
* -> AsmClassesTransform (classes-jar to asm-transformed-classes-jar)
*
* We want out transform to run before the first IdentityTransform, so the chain
* would look like that:
*
* MetaInfStripTransform (jar to processed-jar) -> IdentityTransform (jar to processed-jar)
* -> IdentityTransform (...) -> AsmClassesTransform (...)
*
* Since the first two transforms have conflicting attributes, we define a
* disambiguation rule to make Gradle select our transform first.
*/
matchingStrategy.disambiguationRules
.pickFirst { o1, o2 -> if (o1 > o2) -1 else if (o1 < o2) 1 else 0 }
}
}
// sets meta-inf-stripped attr to false for all .jar artifacts
dependencies.artifactTypes.named("jar") {
it.attributes.attribute(metaInfStripped, false)
}
// registers a transform
dependencies.registerTransform(MetaInfStripTransform::class.java) {
it.from.attribute(artifactType, "jar").attribute(metaInfStripped, false)
it.to.attribute(artifactType, "processed-jar").attribute(metaInfStripped, true)
if (forceInstrument) {
it.parameters.invalidate.set(System.currentTimeMillis())
}
}
}
}
}