From 4016448fc56179ac5fa6eaf352113617f04847a5 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Tue, 19 Jul 2022 12:31:39 -0700 Subject: [PATCH] Add support for parsing LibraryGlideModules. Every round now writes a new Index containing all LibraryGlideModules in that round. When we encounter an AppGlideModule, we read all previously read Index classes, extract the LibraryGlideModules they point to, and write a merged AppGlideModule that calls the developer's AppGlideModule and all of the LibraryGlideModules. Excluding LibraryGlideModules is not supported yet. The library is still in a pre-release state. Progress towards #4492 PiperOrigin-RevId: 461946515 --- .../glide/annotation/ksp/AppGlideModules.kt | 50 +- .../annotation/ksp/GlideSymbolProcessor.kt | 37 +- .../annotation/ksp/LibraryGlideModules.kt | 129 +++++ .../annotation/ksp/LibraryGlideModuleTests.kt | 447 ++++++++++++++++++ 4 files changed, 655 insertions(+), 8 deletions(-) create mode 100644 annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModules.kt create mode 100644 annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt 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 7ae72ec12f..b417e55901 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 @@ -1,6 +1,7 @@ package com.bumptech.glide.annotation.ksp import com.bumptech.glide.annotation.Excludes +import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.getConstructors import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessorEnvironment @@ -38,6 +39,8 @@ object AppGlideModuleConstants { internal data class AppGlideModuleData( val name: ClassName, val constructor: Constructor, + val allowedLibraryGlideModuleNames: List, + val sources: List, ) { internal data class Constructor(val hasContext: Boolean) } @@ -57,7 +60,26 @@ internal class AppGlideModuleParser( val constructor = parseAppGlideModuleConstructorOrThrow() val name = ClassName.bestGuess(appGlideModuleClass.qualifiedName!!.asString()) - return AppGlideModuleData(name = name, constructor = constructor) + val (indexFiles, allLibraryModuleNames) = getIndexesAndLibraryGlideModuleNames() + val excludedGlideModuleClassNames = getExcludedGlideModuleClassNames() + val filteredGlideModuleClassNames = + allLibraryModuleNames.filterNot { excludedGlideModuleClassNames.contains(it) } + + return AppGlideModuleData( + name = name, + constructor = constructor, + allowedLibraryGlideModuleNames = filteredGlideModuleClassNames, + sources = indexFiles + ) + } + + private fun getExcludedGlideModuleClassNames(): Set { + val excludesAnnotation = appGlideModuleClass.atMostOneExcludesAnnotation() + // TODO(judds): Implement support for the excludes annotation. + environment.logger.logging( + "Found excludes annotation arguments: ${excludesAnnotation?.arguments}" + ) + return emptySet() } private fun parseAppGlideModuleConstructorOrThrow(): AppGlideModuleData.Constructor { @@ -80,6 +102,22 @@ internal class AppGlideModuleParser( val libraryModuleNames: List, ) + @OptIn(KspExperimental::class) + 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) + } + } + + return IndexFilesAndLibraryModuleNames(allIndexFiles, allLibraryGlideModuleNames) + } + private fun extractGlideModulesFromIndexAnnotation( index: KSDeclaration, ): List { @@ -155,7 +193,7 @@ internal class AppGlideModuleGenerator(private val appGlideModuleData: AppGlideM .addModifiers(KModifier.INTERNAL) .addProperty("appGlideModule", data.name, KModifier.PRIVATE) .primaryConstructor(generateConstructor(data)) - .addFunction(generateRegisterComponents()) + .addFunction(generateRegisterComponents(data.allowedLibraryGlideModuleNames)) .addFunction(generateApplyOptions()) .addFunction(generateManifestParsingDisabled()) .build() @@ -183,13 +221,17 @@ internal class AppGlideModuleGenerator(private val appGlideModuleData: AppGlideM // TODO(judds): Log the discovered modules here. } - // TODO(judds): call registerComponents on LibraryGlideModules here. - private fun generateRegisterComponents() = + private fun generateRegisterComponents(allowedGlideModuleNames: List) = FunSpec.builder("registerComponents") .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE) .addParameter("context", AppGlideModuleConstants.CONTEXT_CLASS_NAME) .addParameter("glide", ClassName(AppGlideModuleConstants.GLIDE_PACKAGE_NAME, "Glide")) .addParameter("registry", ClassName(AppGlideModuleConstants.GLIDE_PACKAGE_NAME, "Registry")) + .apply { + allowedGlideModuleNames.forEach { + addStatement("%T().registerComponents(context, glide, registry)", ClassName.bestGuess(it)) + } + } .addStatement("appGlideModule.registerComponents(context, glide, registry)") .build() 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 b783be6b00..454dfdaad4 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 @@ -69,16 +69,31 @@ class GlideSymbolProcessor(private val environment: SymbolProcessorEnvironment) environment.logger.logging( "Found AppGlideModules: $appGlideModules, LibraryGlideModules: $libraryGlideModules" ) - // TODO(judds): Add support for parsing LibraryGlideModules here. + + if (libraryGlideModules.isNotEmpty()) { + if (isAppGlideModuleGenerated) { + throw InvalidGlideSourceException( + """Found $libraryGlideModules LibraryGlideModules after processing the AppGlideModule. + If you generated these LibraryGlideModules via another annotation processing, either + don't or also generate the AppGlideModule and do so in the same round as the + LibraryGlideModules or in a subsequent round""" + ) + } + parseLibraryModulesAndWriteIndex(libraryGlideModules) + return invalidSymbols + appGlideModules + } if (appGlideModules.isNotEmpty()) { - parseAppGlideModuleAndWriteGeneratedAppGlideModule(resolver, appGlideModules.single()) + parseAppGlideModuleAndIndexesAndWriteGeneratedAppGlideModule( + resolver, + appGlideModules.single() + ) } return invalidSymbols } - private fun parseAppGlideModuleAndWriteGeneratedAppGlideModule( + private fun parseAppGlideModuleAndIndexesAndWriteGeneratedAppGlideModule( resolver: Resolver, appGlideModule: KSClassDeclaration, ) { @@ -86,12 +101,26 @@ class GlideSymbolProcessor(private val environment: SymbolProcessorEnvironment) AppGlideModuleParser(environment, resolver, appGlideModule).parseAppGlideModule() val appGlideModuleGenerator = AppGlideModuleGenerator(appGlideModuleData) val appGlideModuleFileSpec: FileSpec = appGlideModuleGenerator.generateAppGlideModule() + val sources = appGlideModuleData.sources.mapNotNull { it.containingFile }.toMutableList() + if (appGlideModule.containingFile != null) { + sources.add(appGlideModule.containingFile!!) + } writeFile( appGlideModuleFileSpec, - listOfNotNull(appGlideModule.containingFile), + sources, ) } + private fun parseLibraryModulesAndWriteIndex( + libraryGlideModuleClassDeclarations: List, + ) { + val libraryGlideModulesParser = + LibraryGlideModulesParser(environment, libraryGlideModuleClassDeclarations) + val uniqueLibraryGlideModules = libraryGlideModulesParser.parseUnique() + val index: FileSpec = IndexGenerator.generate(uniqueLibraryGlideModules.map { it.name }) + writeFile(index, uniqueLibraryGlideModules.mapNotNull { it.containingFile }) + } + private fun writeFile(file: FileSpec, sources: List) { environment.codeGenerator .createNewFile( diff --git a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModules.kt b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModules.kt new file mode 100644 index 0000000000..2bbf0cd866 --- /dev/null +++ b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModules.kt @@ -0,0 +1,129 @@ +package com.bumptech.glide.annotation.ksp + +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.annotation.ksp.LibraryGlideModuleData.LibraryModuleName +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFile +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.DelicateKotlinPoetApi +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.TypeSpec +import java.util.UUID + +internal data class LibraryGlideModuleData( + val name: LibraryModuleName, + val containingFile: KSFile?, +) { + data class LibraryModuleName(val qualifiedName: String) +} + +internal class LibraryGlideModulesParser( + private val environment: SymbolProcessorEnvironment, + private val libraryGlideModules: List, +) { + init { + require(libraryGlideModules.isNotEmpty()) + } + + fun parseUnique(): List { + val allLibraryGlideModules = + libraryGlideModules + .map { + LibraryGlideModuleData( + LibraryModuleName(it.qualifiedName!!.asString()), + it.containingFile + ) + } + .toList() + val uniqueLibraryGlideModules = allLibraryGlideModules.associateBy { it.name }.values.toList() + if (uniqueLibraryGlideModules != libraryGlideModules) { + // Find the set of modules that have been included more than once by mapping the qualified + // name of the module to a count of the number of times it's been seen. Duplicates are then + // any keys that have a value > 1. + val duplicateModules: List = + allLibraryGlideModules + .groupingBy { it.name.qualifiedName } + .eachCount() + .filter { it.value > 1 } + .keys + .toList() + environment.logger.warn( + GlideSymbolProcessorConstants.DUPLICATE_LIBRARY_MODULE_ERROR.format(duplicateModules) + ) + } + + return uniqueLibraryGlideModules + } +} + +/** + * Generates an empty class with an annotation containing the class names of one or more + * LibraryGlideModules and/or one or more GlideExtensions. + * + * We use a separate class so that LibraryGlideModules and GlideExtensions written in libraries can + * be bundled into an AAR and later retrieved by the annotation processor when it processes the + * AppGlideModule in an application. + * + * The output file generated by this class with a single LibraryGlideModule looks like this: + * + * ``` + * @com.bumptech.glide.annotation.compiler.Index( + * ["com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule"] + * ) + * class Indexer_GlideModule_com_bumptech_glide_integration_okhttp3_OkHttpLibraryGlideModule + * ``` + * + * This class is not a public API and used only internally by the processor. + */ +internal object IndexGenerator { + private const val INDEXER_NAME_PREFIX = "GlideIndexer_" + private const val MAXIMUM_FILE_NAME_LENGTH = 255 + + @OptIn(DelicateKotlinPoetApi::class) // We're using AnnotationSpec.builder + fun generate( + libraryModuleNames: List, + ): FileSpec { + val libraryModuleQualifiedNames: List = libraryModuleNames.map { it.qualifiedName } + + val indexAnnotation: AnnotationSpec = + AnnotationSpec.builder(Index::class.java) + .addRepeatedMember(libraryModuleQualifiedNames) + .build() + val indexName = generateUniqueName(libraryModuleQualifiedNames) + + return FileSpec.builder(GlideSymbolProcessorConstants.PACKAGE_NAME, indexName) + .addType(TypeSpec.classBuilder(indexName).addAnnotation(indexAnnotation).build()) + .build() + } + + private fun generateUniqueName(libraryModuleQualifiedNames: List): String { + val glideModuleBasedName = generateNameFromLibraryModules(libraryModuleQualifiedNames) + + // If the indexer name has too many packages/modules, it can exceed the file name length + // allowed by the file system, which can break compilation. To avoid that, fall back to a + // deterministic UUID. + return if (glideModuleBasedName.exceedsFileSystemMaxNameLength()) { + generateShortUUIDBasedName(glideModuleBasedName) + } else { + glideModuleBasedName + } + } + + private fun String.exceedsFileSystemMaxNameLength() = + length >= MAXIMUM_FILE_NAME_LENGTH - INDEXER_NAME_PREFIX.length + + private fun generateShortUUIDBasedName(glideModuleBasedName: String) = + INDEXER_NAME_PREFIX + + UUID.nameUUIDFromBytes(glideModuleBasedName.toByteArray()).toString().replace("-", "_") + + private fun generateNameFromLibraryModules(libraryModuleQualifiedNames: List): String { + return libraryModuleQualifiedNames.joinToString( + prefix = INDEXER_NAME_PREFIX + GlideModule::class.java.simpleName + "_", + separator = "_" + ) { it.replace(".", "_") } + } + + private fun AnnotationSpec.Builder.addRepeatedMember(repeatedMember: List) = + addMember("[\n" + "%S,\n".repeat(repeatedMember.size) + "]", *repeatedMember.toTypedArray()) +} diff --git a/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt b/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt new file mode 100644 index 0000000000..8cb5952733 --- /dev/null +++ b/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt @@ -0,0 +1,447 @@ +package com.bumptech.glide.annotation.ksp.test + +import com.bumptech.glide.annotation.ksp.GlideSymbolProcessorConstants +import com.google.common.truth.Truth.assertThat +import com.tschuchort.compiletesting.KotlinCompilation.ExitCode +import java.io.FileNotFoundException +import kotlin.test.assertFailsWith +import org.intellij.lang.annotations.Language +import org.junit.Assume.assumeTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters + +@RunWith(Parameterized::class) +class LibraryGlideModuleTests(override val sourceType: SourceType) : PerSourceTypeTest { + + companion object { + @Parameters(name = "sourceType = {0}") @JvmStatic fun data() = SourceType.values() + } + + @Test + fun compile_withAnnotatedAndValidLibraryGlideModule_succeeds_butDoesNotGenerateGeneratedAppGlideModule() { + val kotlinModule = + KotlinSourceFile( + "Module.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class Module : LibraryGlideModule() + """ + ) + val javaModule = + JavaSourceFile( + "Module.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule public class Module extends LibraryGlideModule {} + """ + ) + + compileCurrentSourceType(kotlinModule, javaModule) { + assertThat(it.exitCode).isEqualTo(ExitCode.OK) + assertFailsWith { it.generatedAppGlideModuleContents() } + } + } + + @Test + fun compile_withValidLibraryGlideModule_andAppGlideModule_generatesGeneratedAppGlideModule_andCallsBothLibraryAndAppGlideModules() { + val kotlinLibraryModule = + KotlinSourceFile( + "LibraryModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule : LibraryGlideModule() + """ + ) + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + @GlideModule class AppModule : AppGlideModule() + """ + ) + val javaLibraryModule = + JavaSourceFile( + "LibraryModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule public class LibraryModule extends LibraryGlideModule {} + """ + ) + val javaAppModule = + JavaSourceFile( + "AppModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.AppGlideModule; + + @GlideModule public class AppModule extends AppGlideModule { + public AppModule() {} + } + """ + ) + + compileCurrentSourceType( + kotlinAppModule, + kotlinLibraryModule, + javaAppModule, + javaLibraryModule + ) { + assertThat(it.exitCode).isEqualTo(ExitCode.OK) + assertThat(it.generatedAppGlideModuleContents()) + .hasSourceEqualTo(appGlideModuleWithLibraryModule) + } + } + + @Test + fun compile_withMultipleLibraryGlideModules_andAppGlideModule_callsAllLibraryGlideModulesFromGeneratedAppGlideModule() { + val kotlinLibraryModule1 = + KotlinSourceFile( + "LibraryModule1.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule1 : LibraryGlideModule() + """ + ) + val kotlinLibraryModule2 = + KotlinSourceFile( + "LibraryModule2.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule2 : LibraryGlideModule() + """ + ) + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + @GlideModule class AppModule : AppGlideModule() + """ + ) + val javaLibraryModule1 = + JavaSourceFile( + "LibraryModule1.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule public class LibraryModule1 extends LibraryGlideModule {} + """ + ) + val javaLibraryModule2 = + JavaSourceFile( + "LibraryModule2.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule public class LibraryModule2 extends LibraryGlideModule {} + """ + ) + val javaAppModule = + JavaSourceFile( + "AppModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.AppGlideModule; + + @GlideModule public class AppModule extends AppGlideModule { + public AppModule() {} + } + """ + ) + + compileCurrentSourceType( + kotlinAppModule, + kotlinLibraryModule1, + kotlinLibraryModule2, + javaAppModule, + javaLibraryModule1, + javaLibraryModule2, + ) { + assertThat(it.generatedAppGlideModuleContents()) + .hasSourceEqualTo(appGlideModuleWithMultipleLibraryModules) + assertThat(it.exitCode).isEqualTo(ExitCode.OK) + } + } + + @Test + fun compile_withTheSameLibraryGlideModuleInMultipleFiles_andAnAppGlideModule_generatesGeneratedAppGlideModuleThatCallsTheLibraryGlideModuleOnce() { + // Kotlin seems fine with multiple identical classes. For Java this is compile time error + // already, so we don't have to handle it. + assumeTrue(sourceType == SourceType.KOTLIN) + val kotlinLibraryModule1 = + KotlinSourceFile( + "LibraryModule1.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule : LibraryGlideModule() + """ + ) + val kotlinLibraryModule2 = + KotlinSourceFile( + "LibraryModule2.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule : LibraryGlideModule() + """ + ) + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + @GlideModule class AppModule : AppGlideModule() + """ + ) + + compileCurrentSourceType( + kotlinAppModule, + kotlinLibraryModule1, + kotlinLibraryModule2, + ) { + assertThat(it.generatedAppGlideModuleContents()) + .hasSourceEqualTo(appGlideModuleWithLibraryModule) + assertThat(it.exitCode).isEqualTo(ExitCode.OK) + assertThat(it.messages) + .contains( + GlideSymbolProcessorConstants.DUPLICATE_LIBRARY_MODULE_ERROR.format("[LibraryModule]") + ) + } + } + + @Test + fun compile_withLibraryGlideModulesWithDifferentPackages_butSameName_andAppGlideModule_callsEachLibraryGlideModuleOnceFromGeneratedAppGlideModule() { + // TODO(judds): The two java classes don't compile when run by the annotation processor, which + // means we can't really test this case for java code. Fix compilation issue and re-enable this + // test for Java code. + assumeTrue(sourceType == SourceType.KOTLIN) + val kotlinLibraryModule1 = + KotlinSourceFile( + "LibraryModule1.kt", + """ + package first_package + + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule : LibraryGlideModule() + """ + ) + val kotlinLibraryModule2 = + KotlinSourceFile( + "LibraryModule2.kt", + """ + package second_package + + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule : LibraryGlideModule() + """ + ) + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + @GlideModule class AppModule : AppGlideModule() + """ + ) + val javaLibraryModule1 = + JavaSourceFile( + "LibraryModule1.java", + """ + package first_package; + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + public class LibraryModule1 { + @GlideModule public static final class LibraryModule extends LibraryGlideModule {} + } + """ + ) + val javaLibraryModule2 = + JavaSourceFile( + "LibraryModule2.java", + """ + package second_package; + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + public class LibraryModule2 { + @GlideModule public static final class LibraryModule extends LibraryGlideModule {} + } + """ + ) + val javaAppModule = + JavaSourceFile( + "AppModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.AppGlideModule; + + @GlideModule public class AppModule extends AppGlideModule { + public AppModule() {} + } + """ + ) + + compileCurrentSourceType( + kotlinAppModule, + kotlinLibraryModule1, + kotlinLibraryModule2, + javaAppModule, + javaLibraryModule1, + javaLibraryModule2, + ) { + assertThat(it.generatedAppGlideModuleContents()) + .hasSourceEqualTo(appGlideModuleWithPackagePrefixedLibraryModules) + assertThat(it.exitCode).isEqualTo(ExitCode.OK) + } + } +} + +@Language("kotlin") +const val appGlideModuleWithPackagePrefixedLibraryModules = + """ +package com.bumptech.glide + +import AppModule +import android.content.Context +import first_package.LibraryModule +import kotlin.Boolean +import kotlin.Suppress +import kotlin.Unit + +internal class GeneratedAppGlideModuleImpl( + @Suppress("UNUSED_VARIABLE") + context: Context, +) : GeneratedAppGlideModule() { + private val appGlideModule: AppModule + init { + appGlideModule = AppModule() + } + + public override fun registerComponents( + context: Context, + glide: Glide, + registry: Registry, + ): Unit { + LibraryModule().registerComponents(context, glide, registry) + second_package.LibraryModule().registerComponents(context, glide, registry) + appGlideModule.registerComponents(context, glide, registry) + } + + public override fun applyOptions(context: Context, builder: GlideBuilder): Unit { + appGlideModule.applyOptions(context, builder) + } + + public override fun isManifestParsingEnabled(): Boolean = false +} +""" + +@Language("kotlin") +const val appGlideModuleWithLibraryModule = + """ +package com.bumptech.glide + +import AppModule +import LibraryModule +import android.content.Context +import kotlin.Boolean +import kotlin.Suppress +import kotlin.Unit + +internal class GeneratedAppGlideModuleImpl( + @Suppress("UNUSED_VARIABLE") + context: Context, +) : GeneratedAppGlideModule() { + private val appGlideModule: AppModule + init { + appGlideModule = AppModule() + } + + public override fun registerComponents( + context: Context, + glide: Glide, + registry: Registry, + ): Unit { + LibraryModule().registerComponents(context, glide, registry) + appGlideModule.registerComponents(context, glide, registry) + } + + public override fun applyOptions(context: Context, builder: GlideBuilder): Unit { + appGlideModule.applyOptions(context, builder) + } + + public override fun isManifestParsingEnabled(): Boolean = false +} +""" + +@Language("kotlin") +const val appGlideModuleWithMultipleLibraryModules = + """ +package com.bumptech.glide + +import AppModule +import LibraryModule1 +import LibraryModule2 +import android.content.Context +import kotlin.Boolean +import kotlin.Suppress +import kotlin.Unit + +internal class GeneratedAppGlideModuleImpl( + @Suppress("UNUSED_VARIABLE") + context: Context, +) : GeneratedAppGlideModule() { + private val appGlideModule: AppModule + init { + appGlideModule = AppModule() + } + + public override fun registerComponents( + context: Context, + glide: Glide, + registry: Registry, + ): Unit { + LibraryModule1().registerComponents(context, glide, registry) + LibraryModule2().registerComponents(context, glide, registry) + appGlideModule.registerComponents(context, glide, registry) + } + + public override fun applyOptions(context: Context, builder: GlideBuilder): Unit { + appGlideModule.applyOptions(context, builder) + } + + public override fun isManifestParsingEnabled(): Boolean = false +} +"""