From 08bae3144792dfe4a95d8f17f664fa04148d965a Mon Sep 17 00:00:00 2001 From: Oleg Golberg Date: Sun, 19 Nov 2023 14:23:39 -0500 Subject: [PATCH] Add limited support for core library desugaring 2.0 --- README.md | 32 ++++-- .../toasttab/android/Api19SignaturesTest.kt | 13 +++ .../android/Api19TypeDescriptorsTest.kt | 99 ++++++++++++++++++- basic-sugar/build.gradle.kts | 6 -- .../java/lang/DesugarBoolean.java | 2 +- .../{ => desugar}/java/lang/DesugarByte.java | 2 +- .../java/lang/DesugarCharacter.java | 2 +- .../java/lang/DesugarDouble.java | 2 +- .../{ => desugar}/java/lang/DesugarFloat.java | 2 +- .../java/lang/DesugarInteger.java | 2 +- .../{ => desugar}/java/lang/DesugarLong.java | 2 +- .../{ => desugar}/java/lang/DesugarMath.java | 2 +- .../{ => desugar}/java/lang/DesugarShort.java | 2 +- buildSrc/src/main/kotlin/Common.kt | 1 + .../kotlin/signatures-conventions.gradle.kts | 3 +- ...signatures-core-lib-conventions.gradle.kts | 31 +++++- gradle/libs.versions.toml | 4 +- .../AndroidTypeDescriptorBuilder.kt | 8 +- .../descriptors/MutableTypeDescriptors.kt | 17 +--- ...former.kt => TransformedTypeDescriptor.kt} | 32 ++++-- .../transform/DesugarClassNameTransformer.kt | 25 ++--- 21 files changed, 220 insertions(+), 69 deletions(-) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarBoolean.java (94%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarByte.java (81%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarCharacter.java (89%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarDouble.java (94%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarFloat.java (94%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarInteger.java (97%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarLong.java (96%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarMath.java (98%) rename basic-sugar/src/main/java/{ => desugar}/java/lang/DesugarShort.java (82%) rename signature-builder/src/main/kotlin/com/toasttab/android/descriptors/{DesugarTypeTransformer.kt => TransformedTypeDescriptor.kt} (54%) diff --git a/README.md b/README.md index d2bea05..984476d 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,18 @@ When the APK is assembled, D8 (the Android Dexer) transforms java bytecode into ### Gummy Bears -This project provides a safe and more accurate set of signatures for Android 4.4-13 (API 19-33). The additional _sugary_ signatures are generated from hand-written stubs. The reference for the stubs is the [D8 source code](https://r8.googlesource.com/r8/+/master/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java). +This project provides a safe and more accurate set of signatures for Android 4.4-13 (API 19-34). The additional _sugary_ signatures are generated from hand-written stubs. The reference for the stubs is the [D8 source code](https://r8.googlesource.com/r8/+/master/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java). -This project also provides experimental sets of signatures for APIs available via _core library desugaring_, including `java.time`, `ConcurrentHashMap`, etc. The artifacts are tagged with the `coreLib` classifier and are available for Android 4.4-8.1 (API 19-27). +### Core library desugaring + +This project also provides _experimental_ sets of signatures for APIs available via [core library desugaring](https://developer.android.com/studio/write/java8-support), including `java.time`, `ConcurrentHashMap`, etc. + +Two versions of core library desugaring signatures are provided: v1, which requires `desugar_jdk_libs:1.2.3` or above and is published under the `coreLib` +classifier, and v2, which requires `desugar_jdk_libs:2.0.4` and is published under the `coreLib2` classifier. Note that `desugar_jdk_libs` version `2` +comes in three flavors: minimal, nio, and full. Currently, only the full flavor is supported. + +Using signatures with core library desugaring to validate a library effectively implies that all Android projects consuming the library +must have core library desugaring enabled at build time and bring in the appropriate version of `desugar_jdk_libs`. ## How to use @@ -41,7 +50,7 @@ plugins { } dependencies { - signature('com.toasttab.android:gummy-bears-api-24:0.3.0@signature') + signature('com.toasttab.android:gummy-bears-api-24:0.7.0@signature') } ``` @@ -49,7 +58,15 @@ With core library desugaring: ```groovy dependencies { - signature('com.toasttab.android:gummy-bears-api-24:0.5.0:coreLib@signature') + signature('com.toasttab.android:gummy-bears-api-24:0.7.0:coreLib@signature') +} +``` + +With core library desugaring v2: + +```groovy +dependencies { + signature('com.toasttab.android:gummy-bears-api-24:0.7.0:coreLib2@signature') } ``` @@ -61,7 +78,7 @@ plugins { } dependencies { - add("signature", "com.toasttab.android:gummy-bears-api-24:0.5.0@signature") + add("signature", "com.toasttab.android:gummy-bears-api-24:0.7.0@signature") } ``` @@ -76,7 +93,7 @@ dependencies { com.toasttab.android gummy-bears-api-21 - 0.5.0 + 0.7.0 @@ -84,7 +101,8 @@ dependencies { ## Expediter -As of version 0.6.0, this project also publishes [Expediter](/~https://github.com/open-toast/expediter) type descriptors. +As of version 0.6.0, this project also publishes native [Expediter](/~https://github.com/open-toast/expediter) type descriptors. Expediter provides a superset +of the Animal Sniffer binary compatibility checks and comes with its own Gradle plugin. ## License diff --git a/api/19/src/test/java/com/toasttab/android/Api19SignaturesTest.kt b/api/19/src/test/java/com/toasttab/android/Api19SignaturesTest.kt index 7605250..6c040ac 100644 --- a/api/19/src/test/java/com/toasttab/android/Api19SignaturesTest.kt +++ b/api/19/src/test/java/com/toasttab/android/Api19SignaturesTest.kt @@ -53,6 +53,19 @@ class Api19SignaturesTest { } } + @Test + fun `core lib v2 signatures include Base64$Decoder#decode`() { + val desc = ObjectInputStream(GZIPInputStream(File(System.getProperty("coreLibSignatures2")).inputStream())).use { + generateSequence { it.readObject() as Clazz? }.toList() + } + + val stream = desc.find { it.name == "java/util/Base64\$Decoder" } + + expectThat(stream).isNotNull().and { + get { signatures }.contains("decode([B)[B") + } + } + @Test fun `signatures use HashSet`() { val desc = ObjectInputStream(GZIPInputStream(File(System.getProperty("signatures")).inputStream())).use { diff --git a/api/19/src/test/java/com/toasttab/android/Api19TypeDescriptorsTest.kt b/api/19/src/test/java/com/toasttab/android/Api19TypeDescriptorsTest.kt index d0b1fd3..30f49dd 100644 --- a/api/19/src/test/java/com/toasttab/android/Api19TypeDescriptorsTest.kt +++ b/api/19/src/test/java/com/toasttab/android/Api19TypeDescriptorsTest.kt @@ -21,8 +21,11 @@ import protokt.v1.toasttab.expediter.v1.AccessProtection import protokt.v1.toasttab.expediter.v1.MemberDescriptor import protokt.v1.toasttab.expediter.v1.SymbolicReference import protokt.v1.toasttab.expediter.v1.TypeDescriptors +import protokt.v1.toasttab.expediter.v1.TypeExtensibility +import protokt.v1.toasttab.expediter.v1.TypeFlavor import strikt.api.expectThat import strikt.assertions.contains +import strikt.assertions.isEqualTo import strikt.assertions.isNotNull import java.io.File import java.util.zip.GZIPInputStream @@ -51,7 +54,7 @@ class Api19TypeDescriptorsTest { } @Test - fun `type descriptors include Stream#count()`() { + fun `core lib type descriptors include Stream#count()`() { val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors")).inputStream()).use { TypeDescriptors.deserialize(it) } @@ -71,4 +74,98 @@ class Api19TypeDescriptorsTest { ) } } + + /** + * java.util.Base64 is included in desugar_jdk_libs starting with version 2.0.4 + */ + @Test + fun `core lib v2 type descriptors include Base64$Decoder#decode`() { + val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use { + TypeDescriptors.deserialize(it) + } + + val decoder = desc.types.find { it.name == "java/util/Base64\$Decoder" } + + expectThat(decoder).isNotNull().and { + get { methods }.contains( + MemberDescriptor { + ref = SymbolicReference { + name = "decode" + signature = "([B)[B" + } + protection = AccessProtection.PUBLIC + declaration = AccessDeclaration.INSTANCE + } + ) + } + } + + /** + * Tests an edge case in merging type descriptors, where multiple versions of `LinuxFileSystemProvider` are provided, + * one extending `UnixFileSystemProvider` and another extending `FileSystem` + */ + @Test + fun `core lib v2 LinuxFileSystemProvider extends UnixFileSystemProvider`() { + val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use { + TypeDescriptors.deserialize(it) + } + + val provider = desc.types.find { it.name == "sun/nio/fs/LinuxFileSystemProvider" } + + expectThat(provider).isNotNull().and { + get { superName }.isEqualTo("sun/nio/fs/UnixFileSystemProvider") + } + } + + /** + * Tests an edge case in merging type descriptors, where multiple versions of `MimeTypesFileTypeDetector` are + * provided, one with the transformed `desugar/sun/nio/fs/DesugarAbstractFileTypeDetector` name + */ + @Test + fun `core lib v2 MimeTypesFileTypeDetector extends AbstractFileTypeDetector`() { + val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use { + TypeDescriptors.deserialize(it) + } + + val detector = desc.types.find { it.name == "sun/nio/fs/MimeTypesFileTypeDetector" } + + expectThat(detector).isNotNull().and { + get { superName }.isEqualTo("sun/nio/fs/AbstractFileTypeDetector") + } + } + + /** + * Tests an edge case in merging type descriptors, where multiple versions of `IntStream` + * are provided; one is an interface, and another one is a class + */ + @Test + fun `core lib v2 IntStream is an interface`() { + val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use { + TypeDescriptors.deserialize(it) + } + + val stream = desc.types.find { it.name == "java/util/stream/IntStream" } + + expectThat(stream).isNotNull().and { + get { flavor }.isEqualTo(TypeFlavor.INTERFACE) + get { extensibility }.isEqualTo(TypeExtensibility.NOT_FINAL) + } + } + + /** + * Tests an edge case in merging type descriptors, where multiple versions of `Character` + * are provided; one is final, and another one is not + */ + @Test + fun `core lib v2 Character is final`() { + val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use { + TypeDescriptors.deserialize(it) + } + + val character = desc.types.find { it.name == "java/lang/Character" } + + expectThat(character).isNotNull().and { + get { extensibility }.isEqualTo(TypeExtensibility.FINAL) + } + } } diff --git a/basic-sugar/build.gradle.kts b/basic-sugar/build.gradle.kts index 34ce060..8b87b6f 100644 --- a/basic-sugar/build.gradle.kts +++ b/basic-sugar/build.gradle.kts @@ -16,9 +16,3 @@ plugins { `java-conventions` } - -java { - // necessary to compile classes in java.lang - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} diff --git a/basic-sugar/src/main/java/java/lang/DesugarBoolean.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarBoolean.java similarity index 94% rename from basic-sugar/src/main/java/java/lang/DesugarBoolean.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarBoolean.java index 8fccf80..667d182 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarBoolean.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarBoolean.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarBoolean { public static int hashCode(boolean b) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarByte.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarByte.java similarity index 81% rename from basic-sugar/src/main/java/java/lang/DesugarByte.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarByte.java index 5ff6267..6f731ea 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarByte.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarByte.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarByte { public static int hashCode(byte i) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarCharacter.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarCharacter.java similarity index 89% rename from basic-sugar/src/main/java/java/lang/DesugarCharacter.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarCharacter.java index 66cf039..e2a6020 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarCharacter.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarCharacter.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarCharacter { public static int compare(char a, char b) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarDouble.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarDouble.java similarity index 94% rename from basic-sugar/src/main/java/java/lang/DesugarDouble.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarDouble.java index 5797190..95fc62d 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarDouble.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarDouble.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarDouble { public static boolean isFinite(double d) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarFloat.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarFloat.java similarity index 94% rename from basic-sugar/src/main/java/java/lang/DesugarFloat.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarFloat.java index 9c77d26..af24b52 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarFloat.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarFloat.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarFloat { public static int hashCode(float d) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarInteger.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarInteger.java similarity index 97% rename from basic-sugar/src/main/java/java/lang/DesugarInteger.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarInteger.java index 909d1fe..7ceeceb 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarInteger.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarInteger.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarInteger { public static int hashCode(int value) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarLong.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarLong.java similarity index 96% rename from basic-sugar/src/main/java/java/lang/DesugarLong.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarLong.java index 8d02d47..2a2de19 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarLong.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarLong.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarLong { public static int hashCode(long value) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarMath.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarMath.java similarity index 98% rename from basic-sugar/src/main/java/java/lang/DesugarMath.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarMath.java index 62c2580..6c87f08 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarMath.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarMath.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarMath { public static int addExact(int x, int y) { diff --git a/basic-sugar/src/main/java/java/lang/DesugarShort.java b/basic-sugar/src/main/java/desugar/java/lang/DesugarShort.java similarity index 82% rename from basic-sugar/src/main/java/java/lang/DesugarShort.java rename to basic-sugar/src/main/java/desugar/java/lang/DesugarShort.java index f5c0cec..4cad3ed 100644 --- a/basic-sugar/src/main/java/java/lang/DesugarShort.java +++ b/basic-sugar/src/main/java/desugar/java/lang/DesugarShort.java @@ -1,4 +1,4 @@ -package java.lang; +package desugar.java.lang; public final class DesugarShort { public static int hashCode(short i) { diff --git a/buildSrc/src/main/kotlin/Common.kt b/buildSrc/src/main/kotlin/Common.kt index 60525c4..3660a07 100644 --- a/buildSrc/src/main/kotlin/Common.kt +++ b/buildSrc/src/main/kotlin/Common.kt @@ -19,6 +19,7 @@ object Configurations { const val STANDARD_SUGAR = "sugar" const val EXERCISE_STANDARD_SUGAR = "exerciseStandardSugar" const val CORE_LIB_SUGAR = "coreLibSugar" + const val CORE_LIB_SUGAR_2 = "coreLibSugar_2" } object Publications { diff --git a/buildSrc/src/main/kotlin/signatures-conventions.gradle.kts b/buildSrc/src/main/kotlin/signatures-conventions.gradle.kts index 27f48cf..e21f1cf 100644 --- a/buildSrc/src/main/kotlin/signatures-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/signatures-conventions.gradle.kts @@ -14,7 +14,6 @@ */ import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.invoke import org.gradle.kotlin.dsl.named @@ -49,6 +48,7 @@ configurations { create(Configurations.STANDARD_SUGAR) create(Configurations.EXERCISE_STANDARD_SUGAR) create(Configurations.CORE_LIB_SUGAR).isTransitive = false + create(Configurations.CORE_LIB_SUGAR_2).isTransitive = false } dependencies { @@ -59,6 +59,7 @@ dependencies { add(Configurations.STANDARD_SUGAR, project(":basic-sugar")) add(Configurations.EXERCISE_STANDARD_SUGAR, project(":test:basic-sugar-treadmill")) add(Configurations.CORE_LIB_SUGAR, libs.desugarJdkLibs) + add(Configurations.CORE_LIB_SUGAR_2, libs.desugarJdkLibs2) testImplementation(project(":test:d8-runner")) testImplementation(libs.junit) diff --git a/buildSrc/src/main/kotlin/signatures-core-lib-conventions.gradle.kts b/buildSrc/src/main/kotlin/signatures-core-lib-conventions.gradle.kts index 73c68e9..3baf7cd 100644 --- a/buildSrc/src/main/kotlin/signatures-core-lib-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/signatures-core-lib-conventions.gradle.kts @@ -18,11 +18,14 @@ import org.gradle.kotlin.dsl.register private object Tasks { const val signaturesCoreLib = "buildSignaturesCoreLib" + const val signaturesCoreLib2 = "buildSignaturesCoreLib2" } private object Outputs { const val signaturesCoreLib = "signaturesCoreLib.sig" const val expediterCoreLib = "platformCoreLib.expediter" + const val signaturesCoreLib2 = "signaturesCoreLib-2.sig" + const val expediterCoreLib2 = "platformCoreLib-2.expediter" } plugins { @@ -35,9 +38,19 @@ tasks.register(Tasks.signaturesCoreLib) { desugar = configurations.getByName(Configurations.STANDARD_SUGAR) + configurations.getByName(Configurations.CORE_LIB_SUGAR) animalSnifferOutput = project.layout.buildDirectory.file(Outputs.signaturesCoreLib) expediterOutput = project.layout.buildDirectory.file(Outputs.expediterCoreLib) - outputDescription = "Android API ${project.name} with Core Library Desugaring" + outputDescription = "Android API ${project.name} with Core Library Desugaring 1.x" } +tasks.register(Tasks.signaturesCoreLib2) { + classpath = configurations.getByName(Configurations.GENERATOR) + sdk = configurations.getByName(Configurations.SDK) + desugar = configurations.getByName(Configurations.STANDARD_SUGAR) + configurations.getByName(Configurations.CORE_LIB_SUGAR_2) + animalSnifferOutput = project.layout.buildDirectory.file(Outputs.signaturesCoreLib2) + expediterOutput = project.layout.buildDirectory.file(Outputs.expediterCoreLib2) + outputDescription = "Android API ${project.name} with Core Library Desugaring 2.x" +} + + publishing.publications.named(Publications.MAIN) { artifact(layout.buildDirectory.file(Outputs.signaturesCoreLib)) { extension = "signature" @@ -50,6 +63,18 @@ publishing.publications.named(Publications.MAIN) { classifier = "coreLib" builtBy(tasks.named(Tasks.signaturesCoreLib)) } + + artifact(layout.buildDirectory.file(Outputs.signaturesCoreLib2)) { + extension = "signature" + classifier = "coreLib2" + builtBy(tasks.named(Tasks.signaturesCoreLib2)) + } + + artifact(layout.buildDirectory.file(Outputs.expediterCoreLib2)) { + extension = "expediter" + classifier = "coreLib2" + builtBy(tasks.named(Tasks.signaturesCoreLib2)) + } } tasks { @@ -57,6 +82,10 @@ tasks { fileProperty("platformCoreLibDescriptors", layout.buildDirectory.file(Outputs.expediterCoreLib)) fileProperty("coreLibSignatures", layout.buildDirectory.file(Outputs.signaturesCoreLib)) + fileProperty("platformCoreLibDescriptors2", layout.buildDirectory.file(Outputs.expediterCoreLib2)) + fileProperty("coreLibSignatures2", layout.buildDirectory.file(Outputs.signaturesCoreLib2)) + dependsOn(Tasks.signaturesCoreLib) + dependsOn(Tasks.signaturesCoreLib2) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b5e140c..a1a91ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,8 @@ kotlin = "1.9.20" animalSniffer = "1.22" clikt = "4.2.1" expediter = "0.0.7" -desugarJdkLibs = "1.2.2" +desugarJdkLibs = "1.2.3" +desugarJdkLibs2 = "2.0.4" r8 = "1.5.68" javapoet = "1.13.0" javassist = "3.29.2-GA" @@ -16,6 +17,7 @@ strikt = "0.34.1" animalSniffer = { module = "org.codehaus.mojo:animal-sniffer", version.ref = "animalSniffer" } clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" } desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" } +desugarJdkLibs2 = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs2" } expediter-core = { module = "com.toasttab.expediter:core", version.ref = "expediter" } r8 = { module = "com.android.tools:r8", version.ref = "r8" } javapoet = { module = "com.squareup:javapoet", version.ref = "javapoet" } diff --git a/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/AndroidTypeDescriptorBuilder.kt b/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/AndroidTypeDescriptorBuilder.kt index e62e413..9068983 100644 --- a/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/AndroidTypeDescriptorBuilder.kt +++ b/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/AndroidTypeDescriptorBuilder.kt @@ -41,9 +41,11 @@ class AndroidTypeDescriptorBuilder : CliktCommand() { val signatures = MutableTypeDescriptors(ClasspathScanner(listOf(File(sdk))).scan { stream, _ -> TypeParsers.typeDescriptor(stream) }) for (more in desugared) { - ClasspathScanner(listOf(File(more))).scan { stream, _ -> TypeParsers.typeDescriptor(stream) }.forEach { - signatures.add(DesugarTypeTransformer.transform(it)) - } + ClasspathScanner(listOf(File(more))).scan { stream, _ -> TransformedTypeDescriptor(TypeParsers.typeDescriptor(stream)) } + .sortedBy { it.priority } + .forEach { + signatures.add(it.toType()) + } } File(animalSnifferOutput).absoluteFile.run { diff --git a/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/MutableTypeDescriptors.kt b/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/MutableTypeDescriptors.kt index 532ce37..f1fa7b5 100644 --- a/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/MutableTypeDescriptors.kt +++ b/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/MutableTypeDescriptors.kt @@ -24,24 +24,9 @@ class MutableTypeDescriptors(initial: Collection) { fun add(type: TypeDescriptor) { types.merge(type.name, type) { oldType, newType -> - val superClass = when (newType.superName) { - null -> oldType.superName - "java/lang/Object" -> oldType.superName - oldType.superName -> oldType.superName - else -> throw IllegalArgumentException("conflicting superclasses ${oldType.superName} != ${newType.superName} for ${type.name}") - } - - if (oldType.flavor != newType.flavor) { - throw IllegalArgumentException("conflicting flavor ${oldType.flavor} != ${newType.flavor} for ${type.name}") - } - - if (oldType.protection != newType.protection) { - throw IllegalArgumentException("conflicting protection ${oldType.flavor} != ${newType.flavor} for ${type.name}") - } - TypeDescriptor { name = type.name - superName = superClass + superName = oldType.superName interfaces = oldType.interfaces.merge(newType.interfaces) fields = oldType.fields.merge(newType.fields) diff --git a/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/DesugarTypeTransformer.kt b/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/TransformedTypeDescriptor.kt similarity index 54% rename from signature-builder/src/main/kotlin/com/toasttab/android/descriptors/DesugarTypeTransformer.kt rename to signature-builder/src/main/kotlin/com/toasttab/android/descriptors/TransformedTypeDescriptor.kt index fc5a489..4712c2f 100644 --- a/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/DesugarTypeTransformer.kt +++ b/signature-builder/src/main/kotlin/com/toasttab/android/descriptors/TransformedTypeDescriptor.kt @@ -16,17 +16,31 @@ package com.toasttab.android.descriptors import com.toasttab.android.signature.transform.DesugarClassNameTransformer -import com.toasttab.android.signature.transform.ShouldTransform import protokt.v1.toasttab.expediter.v1.TypeDescriptor -object DesugarTypeTransformer { - private fun shouldTransform(type: TypeDescriptor) = DesugarClassNameTransformer.shouldTransform(type.name, '/') +class TransformedTypeDescriptor private constructor( + private val type: TypeDescriptor, + private val newName: String +) { + constructor(type: TypeDescriptor) : this(type, transform(type.name)) - fun transform(type: TypeDescriptor) = - when (val shouldTransform = shouldTransform(type)) { - is ShouldTransform.No -> type - is ShouldTransform.Yes -> type.copy { - name = shouldTransform.newName - } + val priority = if (type.name == newName) { + 0 + } else { + 1 + } + + fun toType() = if (type.name == newName) { + type + } else { + type.copy { + name = newName + superName = superName?.let(::transform) + interfaces = type.interfaces.map(::transform) } + } + + companion object { + private fun transform(name: String) = DesugarClassNameTransformer.transform(name, '/') + } } diff --git a/signature-transformer/src/main/kotlin/com/toasttab/android/signature/transform/DesugarClassNameTransformer.kt b/signature-transformer/src/main/kotlin/com/toasttab/android/signature/transform/DesugarClassNameTransformer.kt index 91d82ea..181f2e3 100644 --- a/signature-transformer/src/main/kotlin/com/toasttab/android/signature/transform/DesugarClassNameTransformer.kt +++ b/signature-transformer/src/main/kotlin/com/toasttab/android/signature/transform/DesugarClassNameTransformer.kt @@ -15,22 +15,17 @@ package com.toasttab.android.signature.transform -sealed interface ShouldTransform { - object No : ShouldTransform - class Yes(val newName: String) : ShouldTransform -} - object DesugarClassNameTransformer { - fun shouldTransform(name: String, delimiter: Char = '.'): ShouldTransform { - return if (name.substringAfterLast(delimiter).startsWith("Desugar")) { - ShouldTransform.Yes(name.substringBeforeLast(delimiter) + delimiter + name.substringAfterLast("${delimiter}Desugar")) - } else { - ShouldTransform.No - } + fun transform(name: String, delimiter: Char = '.'): String { + return removeClassPrefix(removePackagePrefix(name, delimiter), delimiter) } - fun transform(name: String) = when (val shouldTransform = shouldTransform(name)) { - ShouldTransform.No -> name - is ShouldTransform.Yes -> shouldTransform.newName - } + private fun removePackagePrefix(name: String, delimiter: Char) = name.removePrefix("desugar$delimiter") + + private fun removeClassPrefix(name: String, delimiter: Char) = + if (name.substringAfterLast(delimiter).startsWith("Desugar")) { + name.substringBeforeLast(delimiter) + delimiter + name.substringAfterLast("${delimiter}Desugar") + } else { + name + } }