From 16306e89dece9908e9ada2382f4b94b591120ea5 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Tue, 7 Mar 2023 22:55:40 -0800 Subject: [PATCH] Read library glide module names from Java indexes Progress for #5043 Annotation processors (including ksp) only run on newly compiled code. Libraries have been previously compiled, so an annotation processor will not be run on them. To find any LibraryGlideModules included in those libraries, we look for specific generated classes in those libraries that we call Indexes. Indexes contain an annotation listing the class names of any LibraryGlideModules. Indexes are generated by Glide's annotation processors for each library and are exported as part of the library. To preserve the package private visibility of the existing Java Index annotation, I created a new Index annotation for the KSP processor. This lets us reference the KSP Index annotation directly. Unfortunately it also means that the Java and KSP Index classes are not the same. While it's only a small amount of duplicated code, it's a significant compatibility issue because the KSP and Java processors no longer produce the same Index class. In particular the KSP processor looks only for its Index class and not for the Java processor's Index class. This is unfortunate because Glide's libraries are always processed by the Java annotation processor, not the KSP processor. In turn this means that Glide's KSP processor effectively ignores any of Glide's integration libraries. To fix this in the short term I've made the KSP processor look for both its Indexes and the Java annotation processors Indexes. A more robust fix would be to merge the two Index processors so that the annotation processors are mutually compatible. I'll do that in a follow-up. I've written tests, but they're somewhat involved so I'll send them as a follow-up. --- .../glide/annotation/ksp/AppGlideModules.kt | 67 +++++++++++++++---- .../annotation/ksp/GlideSymbolProcessor.kt | 1 + glide/build.gradle | 29 +++++++- 3 files changed, 82 insertions(+), 15 deletions(-) diff --git a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt index 0426bdc270..b684da0a79 100644 --- a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt +++ b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt @@ -191,23 +191,58 @@ internal class AppGlideModuleParser( private fun getIndexesAndLibraryGlideModuleNames(): IndexFilesAndLibraryModuleNames { val allIndexFiles: MutableList = mutableListOf() val allLibraryGlideModuleNames: MutableList = mutableListOf() - resolver.getDeclarationsFromPackage(GlideSymbolProcessorConstants.PACKAGE_NAME).forEach { - index: KSDeclaration -> - val libraryGlideModuleNames = extractGlideModulesFromIndexAnnotation(index) - if (libraryGlideModuleNames.isNotEmpty()) { - allIndexFiles.add(index) - allLibraryGlideModuleNames.addAll(libraryGlideModuleNames) - } + + val allIndexesAndLibraryModules = + getAllLibraryNamesFromJavaIndexes() + getAllLibraryNamesFromKspIndexes() + for ((index, libraryGlideModuleNames) in allIndexesAndLibraryModules) { + allIndexFiles.add(index) + allLibraryGlideModuleNames.addAll(libraryGlideModuleNames) } return IndexFilesAndLibraryModuleNames(allIndexFiles, allLibraryGlideModuleNames) } - private fun extractGlideModulesFromIndexAnnotation( + internal data class IndexAndLibraryModuleNames( + val index: KSDeclaration, val libraryModuleNames: List + ) + + private fun getAllLibraryNamesFromKspIndexes(): List = + getAllLibraryNamesFromIndexes(GlideSymbolProcessorConstants.PACKAGE_NAME) { index -> + extractGlideModulesFromKspIndexAnnotation(index) + } + + private fun getAllLibraryNamesFromJavaIndexes(): List = + getAllLibraryNamesFromIndexes(GlideSymbolProcessorConstants.JAVA_ANNOTATION_PACKAGE_NAME) { + index -> extractGlideModulesFromJavaIndexAnnotation(index) + } + + @OptIn(KspExperimental::class) + private fun getAllLibraryNamesFromIndexes( + packageName: String, extractLibraryModuleNamesFromIndex: (KSDeclaration) -> List + ) = buildList { + resolver.getDeclarationsFromPackage(packageName) + .forEach { index: KSDeclaration -> + val libraryGlideModuleNames = extractLibraryModuleNamesFromIndex(index) + if (libraryGlideModuleNames.isNotEmpty()) { + environment.logger.info( + "Found index annotation: $index with modules: $libraryGlideModuleNames" + ) + add(IndexAndLibraryModuleNames(index, libraryGlideModuleNames)) + } + } + } + + private fun extractGlideModulesFromJavaIndexAnnotation( + index: KSDeclaration, + ): List { + val indexAnnotation: KSAnnotation = index.atMostOneJavaIndexAnnotation() ?: return emptyList() + return indexAnnotation.getModuleArgumentValues().toList() + } + + private fun extractGlideModulesFromKspIndexAnnotation( index: KSDeclaration, ): List { - val indexAnnotation: KSAnnotation = index.atMostOneIndexAnnotation() ?: return emptyList() - environment.logger.info("Found index annotation: $indexAnnotation") + val indexAnnotation: KSAnnotation = index.atMostOneKspIndexAnnotation() ?: return emptyList() return indexAnnotation.getModuleArgumentValues().toList() } @@ -220,17 +255,23 @@ internal class AppGlideModuleParser( throw InvalidGlideSourceException("Found an invalid internal Glide index: $this") } - private fun KSDeclaration.atMostOneIndexAnnotation() = atMostOneAnnotation(Index::class) + private fun KSDeclaration.atMostOneJavaIndexAnnotation() = + atMostOneAnnotation("com.bumptech.glide.annotation.compiler.Index") + private fun KSDeclaration.atMostOneKspIndexAnnotation() = atMostOneAnnotation(Index::class) private fun KSDeclaration.atMostOneExcludesAnnotation() = atMostOneAnnotation(Excludes::class) private fun KSDeclaration.atMostOneAnnotation( annotation: KClass, + ): KSAnnotation? = atMostOneAnnotation(annotation.qualifiedName) + + private fun KSDeclaration.atMostOneAnnotation( + annotationQualifiedName: String?, ): KSAnnotation? { val matchingAnnotations: List = annotations .filter { - annotation.qualifiedName?.equals( + annotationQualifiedName?.equals( it.annotationType.resolve().declaration.qualifiedName?.asString() ) ?: false @@ -238,7 +279,7 @@ internal class AppGlideModuleParser( .toList() if (matchingAnnotations.size > 1) { throw InvalidGlideSourceException( - """Expected 0 or 1 $annotation annotations on $qualifiedName, but found: + """Expected 0 or 1 $annotationQualifiedName annotations on $qualifiedName, but found: ${matchingAnnotations.size}""" ) } diff --git a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt index 46a421958b..f1dc18f79b 100644 --- a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt +++ b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt @@ -144,6 +144,7 @@ object GlideSymbolProcessorConstants { // This variable is visible only for testing // TODO(b/174783094): Add @VisibleForTesting when internal is supported. val PACKAGE_NAME: String = GlideSymbolProcessor::class.java.`package`.name + val JAVA_ANNOTATION_PACKAGE_NAME: String = "com.bumptech.glide.annotation.compiler" const val SINGLE_APP_MODULE_ERROR = "You can have at most one AppGlideModule, but found: %s" const val DUPLICATE_LIBRARY_MODULE_ERROR = "LibraryGlideModules %s are included more than once, keeping only one!" diff --git a/glide/build.gradle b/glide/build.gradle index d09357a95a..ed2d27f754 100644 --- a/glide/build.gradle +++ b/glide/build.gradle @@ -1,5 +1,18 @@ import com.android.build.gradle.api.LibraryVariant +/** + * This module is used for two things: + *
    + *
  • Compiling a single unified set of javadocs for Glide + *
  • Providing a jar version of Glide for internal libraries, like + * Glide's annotation processor. + *
+ * + *

Previously this module was used to produce a release jar for Glide, but + * we've long since stopped releasing the jar. Now all release artifacts come + * from the upload script, which uploads aars for each production submodule + */ + apply plugin: 'java' // The paths of Android projects that should be included only in Javadoc, not in the jar. @@ -18,6 +31,10 @@ static def getAndroidPathsForJavadoc() { ] } +static def getAndroidPathsForJar() { + [':library', ':third_party:disklrucache', ':third_party:gif_decoder'] +} + // The paths of Java projects that should be included only in Javadoc, not in the jar. static def getJavaPathsForJavadoc() { [':annotation'] @@ -43,8 +60,16 @@ def getAndroidProjectsForJavadoc() { asProjects(getAndroidPathsForJavadoc()) } +def getAndroidLibraryVariantsForJar() { + getAndroidLibraryVariantsForProjects(asProjects(getAndroidPathsForJar())) +} + def getAndroidLibraryVariantsForJavadoc() { - getAndroidProjectsForJavadoc().collect { project -> + getAndroidLibraryVariantsForProjects(getAndroidProjectsForJavadoc()) +} + +def getAndroidLibraryVariantsForProjects(projects) { + projects.collect { project -> project.android.libraryVariants.findAll { type -> type.buildType.name.equalsIgnoreCase("release") } @@ -114,7 +139,7 @@ javadocJarTask.dependsOn(javadocTask) jar { from files( - getAndroidLibraryVariantsForJavadoc().collect { LibraryVariant variant -> + getAndroidLibraryVariantsForJar().collect { LibraryVariant variant -> variant.getJavaCompileProvider().get().destinationDirectory } )