diff --git a/CHANGELOG.md b/CHANGELOG.md index 17eaf98ff..605513980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Added +- Added `CodeGenerator.group` for scheduling of code generators. All code generators with the same group will be executed in a loop together until no new code is generated. + ### Changed ### Deprecated diff --git a/compiler-api/README.md b/compiler-api/README.md index dc78c2b7f..0678a4b27 100644 --- a/compiler-api/README.md +++ b/compiler-api/README.md @@ -40,6 +40,10 @@ After that implement the `CodeGenerator` interface: ```kotlin @AutoService(CodeGenerator::class) class SampleCodeGenerator : CodeGenerator { + + // execute before any of the default Anvil code generators (in group 0) + override val group = -1 + override fun isApplicable(context: AnvilContext): Boolean = true override fun generateCode( diff --git a/compiler-api/api/compiler-api.api b/compiler-api/api/compiler-api.api index efd71083e..db638c588 100644 --- a/compiler-api/api/compiler-api.api +++ b/compiler-api/api/compiler-api.api @@ -48,10 +48,21 @@ public abstract interface class com/squareup/anvil/compiler/api/AnvilContext { } public abstract interface class com/squareup/anvil/compiler/api/CodeGenerator : com/squareup/anvil/compiler/api/AnvilApplicabilityChecker { + public static final field Companion Lcom/squareup/anvil/compiler/api/CodeGenerator$Companion; + public static final field GROUP_DEFAULT I public abstract fun generateCode (Ljava/io/File;Lorg/jetbrains/kotlin/descriptors/ModuleDescriptor;Ljava/util/Collection;)Ljava/util/Collection; + public abstract fun getGroup ()I public abstract fun isApplicable (Lcom/squareup/anvil/compiler/api/AnvilContext;)Z } +public final class com/squareup/anvil/compiler/api/CodeGenerator$Companion { + public static final field GROUP_DEFAULT I +} + +public final class com/squareup/anvil/compiler/api/CodeGenerator$DefaultImpls { + public static fun getGroup (Lcom/squareup/anvil/compiler/api/CodeGenerator;)I +} + public final class com/squareup/anvil/compiler/api/CodeGeneratorKt { public static final fun createGeneratedFile (Lcom/squareup/anvil/compiler/api/CodeGenerator;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/anvil/compiler/api/GeneratedFile; public static final fun createGeneratedFile (Lcom/squareup/anvil/compiler/api/CodeGenerator;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;[Ljava/io/File;)Lcom/squareup/anvil/compiler/api/GeneratedFileWithSources; diff --git a/compiler-api/src/main/java/com/squareup/anvil/compiler/api/CodeGenerator.kt b/compiler-api/src/main/java/com/squareup/anvil/compiler/api/CodeGenerator.kt index 97513d880..5fcfdfc7a 100644 --- a/compiler-api/src/main/java/com/squareup/anvil/compiler/api/CodeGenerator.kt +++ b/compiler-api/src/main/java/com/squareup/anvil/compiler/api/CodeGenerator.kt @@ -19,6 +19,18 @@ import java.io.File @ExperimentalAnvilApi public interface CodeGenerator : AnvilApplicabilityChecker { + /** + * The group in which this generator will be invoked. + * + * All code generators with the same group will be executed in a loop together, + * in ascending order of their fully qualified name, until no new code is generated. + * Lower numbers will be executed first, + * and will not execute again after their group has finished. + * + * The default group is 0. + */ + public val group: Int get() = 0 + /** * Returns true if this code generator is applicable for the given [context] or false if not. This * will only be called _once_. @@ -39,6 +51,14 @@ public interface CodeGenerator : AnvilApplicabilityChecker { module: ModuleDescriptor, projectFiles: Collection, ): Collection + + public companion object { + /** + * The default [group][CodeGenerator.group] for code generators. + * A lower group value will be executed before any higher group value. + */ + public const val GROUP_DEFAULT: Int = 0 + } } /** diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CheckOnlyCodeGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CheckOnlyCodeGenerator.kt index 6d522a9f9..2fd9ae82f 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CheckOnlyCodeGenerator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CheckOnlyCodeGenerator.kt @@ -10,6 +10,8 @@ import java.io.File */ internal abstract class CheckOnlyCodeGenerator : PrivateCodeGenerator() { + override val group: Int get() = Int.MAX_VALUE + final override fun generateCodePrivate( codeGenDir: File, module: ModuleDescriptor, diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CodeGenerationExtension.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CodeGenerationExtension.kt index c13d66fbf..0325fa98b 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CodeGenerationExtension.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/CodeGenerationExtension.kt @@ -29,7 +29,7 @@ import java.io.File import kotlin.LazyThreadSafetyMode.NONE internal class CodeGenerationExtension( - codeGenerators: List, + private val codeGenerators: List, private val commandLineOptions: CommandLineOptions, private val moduleDescriptorFactory: RealAnvilModuleDescriptor.Factory, private val projectDir: BaseDir.ProjectDir, @@ -56,18 +56,6 @@ internal class CodeGenerationExtension( private var didRecompile = false private var didSyncGeneratedDir = false - private val codeGenerators = codeGenerators - .onEach { - check(it !is FlushingCodeGenerator || it !is PrivateCodeGenerator) { - "A code generator can't be a private code generator and flushing code generator at the " + - "same time. Private code generators don't impact other code generators, therefore " + - "they shouldn't need to flush files after other code generators generated code." - } - } - // Use a stable sort in case code generators depend on the order. - // At least don't make it random. - .sortedWith(compareBy({ it is PrivateCodeGenerator }, { it::class.qualifiedName })) - override fun doAnalysis( project: Project, module: ModuleDescriptor, @@ -196,11 +184,6 @@ internal class CodeGenerationExtension( val generatedFiles = mutableMapOf() - val (privateCodeGenerators, nonPrivateCodeGenerators) = - codeGenerators - .filter { it.isApplicable(anvilContext) } - .partition { it is PrivateCodeGenerator } - fun onGenerated( generatedFile: FileWithContent, codeGenerator: CodeGenerator, @@ -237,43 +220,23 @@ internal class CodeGenerationExtension( .toKtFiles(psiManager, anvilModule) } - fun Collection.flush(): List = - flatMap { codeGenerator -> - codeGenerator.flush(generatedDir, anvilModule) - .onEach { - onGenerated( - generatedFile = it, - codeGenerator = codeGenerator, - // flushing code generators write the files but no content during normal rounds. - allowOverwrites = true, - ) - } - .toKtFiles(psiManager, anvilModule) - } - - fun List.loopGeneration() { - var newFiles = generateAndCache(anvilModule.allFiles.toList()) - while (newFiles.isNotEmpty()) { - // Parse the KtFile for each generated file. Then feed the code generators with the new - // parsed files until no new files are generated. - newFiles = generateAndCache(newFiles) + // Group the code generators by their group number, + // then sort them by their class name to keep the order stable. + // All generators in a group will be looped together until none of them generate new files. + codeGenerators + .filter { it.isApplicable(anvilContext) } + .groupBy { it.group } + .entries + .sortedBy { it.key } + .map { (_, generators) -> generators.sortedBy { it::class.qualifiedName } } + .forEach { generators -> + var newFiles = generators.generateAndCache(anvilModule.allFiles.toList()) + while (newFiles.isNotEmpty()) { + // Parse the KtFile for each generated file. Then feed the code generators with the new + // parsed files until no new files are generated. + newFiles = generators.generateAndCache(newFiles) + } } - } - - // All non-private code generators are batched together. - // They will execute against the initial set of files, - // then loop until no generator produces any new files. - nonPrivateCodeGenerators.loopGeneration() - - // Flushing generators are next. - // They have already seen all generated code. - // Their output may be consumed by a private generator. - codeGenerators.filterIsInstance().flush() - - // Private generators do not affect each other, so they're invoked last. - // They may require multiple iterations of their own logic, though, - // so we loop them individually until there are no more changes. - privateCodeGenerators.forEach { listOf(it).loopGeneration() } return generatedFiles.values } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt index ff9e003ad..5af635594 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentHandlerGenerator.kt @@ -7,6 +7,7 @@ import com.squareup.anvil.compiler.PARENT_COMPONENT import com.squareup.anvil.compiler.SUBCOMPONENT_FACTORY import com.squareup.anvil.compiler.SUBCOMPONENT_MODULE import com.squareup.anvil.compiler.api.AnvilContext +import com.squareup.anvil.compiler.api.CodeGenerator import com.squareup.anvil.compiler.api.GeneratedFileWithSources import com.squareup.anvil.compiler.api.createGeneratedFile import com.squareup.anvil.compiler.contributesSubcomponentFactoryFqName @@ -62,7 +63,9 @@ import java.io.File */ internal class ContributesSubcomponentHandlerGenerator( private val classScanner: ClassScanner, -) : PrivateCodeGenerator() { +) : CodeGenerator { + + override val group: Int get() = 8 private val triggers = mutableListOf() private val contributions = mutableSetOf() @@ -73,7 +76,7 @@ internal class ContributesSubcomponentHandlerGenerator( override fun isApplicable(context: AnvilContext): Boolean = !context.generateFactoriesOnly - override fun generateCodePrivate( + override fun generateCode( codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection, diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/FlushingCodeGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/FlushingCodeGenerator.kt deleted file mode 100644 index 76d554a49..000000000 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/FlushingCodeGenerator.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.squareup.anvil.compiler.codegen - -import com.squareup.anvil.compiler.api.CodeGenerator -import com.squareup.anvil.compiler.api.GeneratedFileWithSources -import org.jetbrains.kotlin.descriptors.ModuleDescriptor -import java.io.File - -internal interface FlushingCodeGenerator : CodeGenerator { - - /** - * Called after the last round of [generateCode] when no new files were produced by any code - * generator anymore. The returned result should contain files that were added or changed one - * last time. Code generates that do not impact other code generators get a last chance to - * evaluate these results. - */ - fun flush( - codeGenDir: File, - module: ModuleDescriptor, - ): Collection -} diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PrivateCodeGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PrivateCodeGenerator.kt index c91d1ba5a..6334f49d4 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PrivateCodeGenerator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/PrivateCodeGenerator.kt @@ -8,11 +8,12 @@ import java.io.File /** * Generates code that doesn't impact any other [CodeGenerator], meaning no other code generator - * will process the generated code produced by this instance. A [PrivateCodeGenerator] is called - * one last time after [FlushingCodeGenerator.flush] has been called to get a chance to evaluate - * written results. + * will process the generated code produced by this instance. */ internal abstract class PrivateCodeGenerator : CodeGenerator { + + override val group: Int get() = 10 + final override fun generateCode( codeGenDir: File, module: ModuleDescriptor,