square / anvil

A Kotlin compiler plugin to make dependency injection with Dagger 2 easier.
Apache License 2.0
1.31k stars 81 forks source link

How to get list of contributed interfaces and modules #724

Open mattinger opened 1 year ago

mattinger commented 1 year ago

I am trying to write a compiler plug-in, and as part of that, i want to get the generated components and the things included in them.

    override fun generateCode(
        codeGenDir: File,
        module: ModuleDescriptor,
        projectFiles: Collection<KtFile>
    ): Collection<GeneratedFile> {
        return projectFiles
            .classAndInnerClassReferences(module)
            .filter {
                it.isAnnotatedWith(FqName(MockableComponent::class.java.name))
            }
            .mapNotNull { clazz ->

                val packageName = clazz.packageFqName.asString()
                val className = clazz.shortName

                val scope = clazz.annotations.find {
                    it.fqName.asString() == MergeComponent::class.java.name
                }?.arguments?.find {
                    it.name == "scope"
                }?.value<ClassReference>()?.fqName?.asString()

So, knowing the class, and scope, I'm trying to figure out how to get the list of included modules and interfaces. I just can't seem to find much documentation on using the compiler api itself, and nothing is jumping out at me when i'm perusing the code.

Any pointers as to how to get started would be appreciated.

Basically my end goal is to take the generated component, and produce a version of it with a full Component.Factory with functions for each module. This would allow me to auto generate a component suitable for use with daggermock.

something like this:

@Component(modules = [ MyModule::class ])
public interface MyComponent_Mockable : MyAccessors {
  @Component.Factory
  public interface Factory {
    public fun create(myModule: MyModule): MyComponent_Mockable
  }
}

I'm able to gather all the contributed classes and interfaces and the basic example above, but it will get hairy dealing with things like "replaces" and "excludes". I'm hoping to re-use the logic in the existing compiler to do that work for me of assembling the list of modules and interfaces.

mattinger commented 1 year ago

I had been hoping to use ClassScanner and perhaps FlushingCodeGenerator, but they are internal classes. And what i've done locally isn't picking up all my modules. It's only picking up ones in the current source tree:

    fun getContributedModulePsis(
        module: ModuleDescriptor,
        projectFiles: Collection<KtFile>,
        scope: String,
    ) = projectFiles
        .classAndInnerClassReferences(module)
        .filter {
            it.isContributesTo(scope) &&
                    !it.isInterface()
                    it.annotations.find { a -> a.fqName.asString() == Module::class.java.name } != null

        }
        .toList()

I suspect this is because the module hints and such are used for sort of keeping track across the different sourcepaths, and i'm not able to use the classes required for that because it all seems to be internal