Skip to content

Commit

Permalink
adding CodeGenerator.group
Browse files Browse the repository at this point in the history
The `group` value allows for basic scheduling of code generators. All code generators with the same group will be executed in a loop together until no new code is generated.
  • Loading branch information
RBusarow committed Apr 26, 2024
1 parent f9def92 commit 39d357e
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 93 deletions.
16 changes: 2 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

- Anvil's generated hints are now all generated to the same `anvil.hint` package, which simplifies hint lookups and better future-proofs future KSP work. Note that this is a user-invisible change, but it will require a one-time recompilation of any Anvil-generated hints.
Expand All @@ -22,24 +24,10 @@

## [2.5.0-beta07] - 2024-04-16

### Added

### Changed

### Deprecated

### Removed

### Fixed

* Another mangled name workaround in KSP ([#966](https://github.com/square/anvil/pull/966))

### Security

### Custom Code Generator

### Other Notes & Contributions

## [2.5.0-beta06] - 2024-04-16

### Deprecated
Expand Down
4 changes: 4 additions & 0 deletions compiler-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
11 changes: 11 additions & 0 deletions compiler-api/api/compiler-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_.
Expand All @@ -39,6 +51,14 @@ public interface CodeGenerator : AnvilApplicabilityChecker {
module: ModuleDescriptor,
projectFiles: Collection<KtFile>,
): Collection<FileWithContent>

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
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import java.io.File
import kotlin.LazyThreadSafetyMode.NONE

internal class CodeGenerationExtension(
codeGenerators: List<CodeGenerator>,
private val codeGenerators: List<CodeGenerator>,
private val commandLineOptions: CommandLineOptions,
private val moduleDescriptorFactory: RealAnvilModuleDescriptor.Factory,
private val projectDir: BaseDir.ProjectDir,
Expand All @@ -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,
Expand Down Expand Up @@ -196,11 +184,6 @@ internal class CodeGenerationExtension(

val generatedFiles = mutableMapOf<String, FileWithContent>()

val (privateCodeGenerators, nonPrivateCodeGenerators) =
codeGenerators
.filter { it.isApplicable(anvilContext) }
.partition { it is PrivateCodeGenerator }

fun onGenerated(
generatedFile: FileWithContent,
codeGenerator: CodeGenerator,
Expand Down Expand Up @@ -237,43 +220,23 @@ internal class CodeGenerationExtension(
.toKtFiles(psiManager, anvilModule)
}

fun Collection<FlushingCodeGenerator>.flush(): List<KtFile> =
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<CodeGenerator>.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<FlushingCodeGenerator>().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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,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
Expand Down Expand Up @@ -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<Trigger>()
private val contributions = mutableSetOf<Contribution>()
Expand All @@ -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<KtFile>,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 39d357e

Please sign in to comment.