Skip to content

Commit e32ff3f

Browse files
authored
Merge pull request #318 from gradle/gh/jdkImage/ordering
Fix issue with ordering on serialized module descriptors
2 parents b881ec8 + f82f452 commit e32ff3f

File tree

2 files changed

+134
-14
lines changed

2 files changed

+134
-14
lines changed

src/main/groovy/org/gradle/android/workarounds/JdkImageWorkaround.groovy

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.gradle.android.workarounds
22

3+
import com.google.common.annotations.VisibleForTesting
4+
import com.google.common.collect.Lists
35
import org.gradle.android.AndroidIssue
46
import org.gradle.api.Project
57
import org.gradle.api.artifacts.transform.CacheableTransform
@@ -24,6 +26,8 @@ import javax.inject.Inject
2426
import java.lang.module.ModuleDescriptor
2527
import java.nio.ByteBuffer
2628
import java.nio.file.Files
29+
import java.util.stream.Collectors
30+
import java.util.stream.Stream
2731

2832
/**
2933
* Works around cache misses due to the custom Java runtime used when source compatibility is set higher
@@ -40,13 +44,18 @@ class JdkImageWorkaround implements Workaround {
4044

4145
@Override
4246
void apply(Project project) {
47+
// We would prefer not to configure this if a jdkImage is not in use, but the attributes
48+
// being ignored are unlikely to ever have a runtime impact. Doing this outside of task
49+
// configuration prevents issues with things that use the tooling api to finalize the
50+
// runtime configuration before querying (and instantiating) task configurations.
51+
applyRuntimeClasspathNormalization(project)
52+
4353
applyToAllAndroidVariants(project) { variant ->
4454
variant.javaCompileProvider.configure { JavaCompile task ->
4555
def jdkImageInput = getJdkImageInput(task)
4656
if (jdkImageInput != null) {
4757
setupExtractedJdkImageInputTransform(project, getJvmHome(task))
4858
replaceCommandLineProvider(task, jdkImageInput)
49-
applyRuntimeClasspathNormalization(task.project)
5059
}
5160
}
5261
}
@@ -178,28 +187,76 @@ class JdkImageWorkaround implements Workaround {
178187

179188
// Capture the module descriptor ignoring the version, which is not enforced anyways
180189
File moduleInfoFile = new File(targetDir, 'java.base/module-info.class')
181-
ModuleDescriptor strippedDescriptor = captureModuleDescriptorWithoutVersion(moduleInfoFile)
190+
ModuleDescriptor descriptor = captureModuleDescriptorWithoutVersion(moduleInfoFile)
182191
File descriptorData = new File(targetDir, "module-descriptor.txt")
183-
descriptorData.text = strippedDescriptor.toString()
192+
descriptorData.text = serializeDescriptor(descriptor)
184193

185194
fileOperations.delete {
186195
delete(moduleInfoFile)
187196
}
188197
}
189198

190199
private static ModuleDescriptor captureModuleDescriptorWithoutVersion(File moduleFile) {
191-
ModuleDescriptor descriptor = ModuleDescriptor.read(ByteBuffer.wrap(Files.readAllBytes(moduleFile.toPath())))
192-
ModuleDescriptor.Builder strippedDescriptor = ModuleDescriptor.newModule(descriptor.name())
193-
strippedDescriptor.packages(descriptor.packages())
194-
if (descriptor.mainClass().present) {
195-
strippedDescriptor.mainClass(descriptor.mainClass().get())
200+
return ModuleDescriptor.read(ByteBuffer.wrap(Files.readAllBytes(moduleFile.toPath())))
201+
}
202+
203+
@VisibleForTesting
204+
static String serializeDescriptor(ModuleDescriptor descriptor) {
205+
StringBuilder sb = new StringBuilder()
206+
207+
if (descriptor.isOpen())
208+
sb.append("open ")
209+
sb.append("module { name: ").append(descriptor.name())
210+
if (!descriptor.requires().isEmpty())
211+
sb.append(", ").append(descriptor.requires().sort().collect { serializeRequires(it) })
212+
if (!descriptor.uses().isEmpty())
213+
sb.append(", uses: ").append(descriptor.uses().sort())
214+
if (!descriptor.exports().isEmpty())
215+
sb.append(", exports: ").append(descriptor.exports().sort().collect { serializeExports(it) })
216+
if (!descriptor.opens().isEmpty())
217+
sb.append(", opens: ").append(descriptor.opens().sort().collect { serializeOpens(it) })
218+
if (!descriptor.provides().isEmpty()) {
219+
sb.append(", provides: ").append(descriptor.provides().sort().collect { serializeProvides(it) })
196220
}
197-
descriptor.exports().each { strippedDescriptor.exports(it) }
198-
descriptor.opens().each {strippedDescriptor.opens(it) }
199-
descriptor.provides().each { strippedDescriptor.provides(it) }
200-
descriptor.requires().each { strippedDescriptor.requires(it) }
201-
descriptor.uses()each { strippedDescriptor.uses(it) }
202-
return strippedDescriptor.build()
221+
sb.append(" }")
222+
return sb.toString()
223+
}
224+
225+
private static String serializeRequires(ModuleDescriptor.Requires requires) {
226+
String requireString
227+
if (! requires.compiledVersion().empty) {
228+
requireString = requires.name() + " (@" + requires.compiledVersion() + ")"
229+
} else {
230+
requireString = requires.name()
231+
}
232+
return withSerializedMods(requires.modifiers(), requireString)
233+
}
234+
235+
private static String serializeExports(ModuleDescriptor.Exports exports) {
236+
String s = withSerializedMods(exports.modifiers(), exports.source())
237+
if (exports.targets().isEmpty())
238+
return s;
239+
else
240+
return s + " to " + exports.targets().sort()
241+
}
242+
243+
private static String serializeOpens(ModuleDescriptor.Opens opens) {
244+
String s = withSerializedMods(opens.modifiers(), opens.source())
245+
if (opens.targets().isEmpty())
246+
return s;
247+
else
248+
return s + " to " + opens.targets().sort()
249+
}
250+
251+
private static String serializeProvides(ModuleDescriptor.Provides provides) {
252+
return provides.service() + " with " + Lists.newArrayList(provides.providers()).sort()
253+
}
254+
255+
static <M> String withSerializedMods(Set<M> mods, String what) {
256+
return (Stream.concat(mods.stream().map(e -> e.toString()
257+
.toLowerCase(Locale.ROOT)).sorted(),
258+
Stream.of(what)))
259+
.collect(Collectors.joining(" "))
203260
}
204261
}
205262
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.gradle.android
2+
3+
import com.google.common.collect.Sets
4+
import org.gradle.android.workarounds.JdkImageWorkaround
5+
import spock.lang.Specification
6+
import spock.lang.Unroll
7+
8+
import java.lang.module.ModuleDescriptor
9+
10+
class JdkImageWorkaroundDescriptorTest extends Specification {
11+
def "normalizes module descriptors with different orders in the values"() {
12+
given:
13+
def serialized1 = JdkImageWorkaround.ExtractJdkImageTransform.serializeDescriptor(descriptor(["org.foo", "org.bar"], ["baz", "fizz"]))
14+
def serialized2 = JdkImageWorkaround.ExtractJdkImageTransform.serializeDescriptor(descriptor(["org.bar", "org.foo"], ["fizz", "baz"]))
15+
16+
expect:
17+
serialized1 == serialized2
18+
}
19+
20+
def "descriptors with different values are serialized differently"() {
21+
given:
22+
def serialized1 = JdkImageWorkaround.ExtractJdkImageTransform.serializeDescriptor(descriptor(["org.foo", "org.bar"], ["baz", "fizz"]))
23+
def serialized2 = JdkImageWorkaround.ExtractJdkImageTransform.serializeDescriptor(descriptor(["org.bar", "org.foo", "org.baz"], ["baz", "fizz"]))
24+
25+
expect:
26+
serialized1 != serialized2
27+
}
28+
29+
@Unroll
30+
def "all descriptor values are captured (#modifier.trim())"() {
31+
ModuleDescriptor descriptor = builder
32+
.requires("foo").requires("bar")
33+
.uses("org.baz").uses("org.fizz")
34+
.exports("org.exports", ["exportsTarget1", "exportsTarget2"] as Set)
35+
.provides("org.provides", ["org.provider1", "org.provider2"])
36+
.with {
37+
modifier == "" ? opens("org.opens", ["opensTarget1", "opensTarget2"] as Set) : it
38+
}.build()
39+
40+
expect:
41+
JdkImageWorkaround.ExtractJdkImageTransform.serializeDescriptor(descriptor) == "${modifier}module { name: myModule, " +
42+
"[bar, foo, mandated java.base], " +
43+
"uses: [org.baz, org.fizz], " +
44+
"exports: [org.exports to [exportsTarget1, exportsTarget2]], " +
45+
(modifier == "" ? "opens: [org.opens to [opensTarget1, opensTarget2]], " : "") +
46+
"provides: [org.provides with [org.provider1, org.provider2]] }"
47+
48+
where:
49+
builder | modifier
50+
ModuleDescriptor.newModule("myModule") | ""
51+
ModuleDescriptor.newOpenModule("myModule") | "open "
52+
}
53+
54+
static def descriptor(List<String> packages, List<String> values) {
55+
ModuleDescriptor.Builder builder = ModuleDescriptor.newModule("myModule")
56+
values.each { builder.requires(it) }
57+
packages.each { builder.uses(it) }
58+
packages.each { builder.exports("${it}.exports", Sets.newLinkedHashSet(values)) }
59+
packages.each { builder.opens("${it}.opens", Sets.newLinkedHashSet(values)) }
60+
packages.each { builder.provides("${it}.provides", values.collect { v -> "org.${v}".toString() }) }
61+
return builder.build()
62+
}
63+
}

0 commit comments

Comments
 (0)