Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP adding CodeGenerator.group #984

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use an enum for this, too! Integers for priority levels tend to lose information fairly readily. An enum would retain ordering guarantees, but enforce naming and make it a lot easier to nav through and find out where a particular CodeGenerator sits in the group.

}
}

/**
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 @@ -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
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
Loading