hey all - I'm interseted in using KotlinPoet to ge...
# android
m
hey all - I'm interseted in using KotlinPoet to generate some code around some Android libraries. The kotlinpoet itself seems really straightforward and easy to understand, but the docs on the site don't really seem to cover actually how to insert KotlinPoet into a build lifecycle. I found a stackoverflow question regarding custom gradle tasks, but I thought I'd ask if anyone here is aware of best practices/approaches when it comes to generating your own code.
e
assuming you want to generate code in an Android library build as a Gradle task (as opposed to generating code for a non-Android build, or within KAPT/KSP):
Copy code
android {
    libraryVariants.all {
        registerJavaGeneratingTask(myTaskProvider, myTaskOutputDirectory)
    }
}
to wrap your generating code in a Gradle task, it needs to be in buildscript classpath, e.g. either a library or included build that the buildscript depends on, or in buildSrc
if it's just a single-use task, you can start hacking at it from inside build.gradle.kts, e.g.
Copy code
val myTask by tasks.registering {
    outputs.dir(layout.buildDirectory.dir("generated/source/my"))
    doLast {
        for (fileSpec in myFileSpecs) {
            fileSpec.writeTo(outputs.files.singleFile)
        }
    }
}
m
yeah, a library is my goal
if i'm using the
libraryVariants.all
version you posted first, I'm not familiar what type of argument
myTaskProvider
would be, though it sounds familiar based on research I was doing on KSP (though hope to avoid using it if possible)
e
if the input for your generator is Java sources, use (K)APT. if your input is Kotlin sources, use KSP (or KAPT).
myTaskProvider should be a Provider<Task>, which is what tasks.register/tasks.registering will give you
m
hmm I'm not able to make the connection here thanks to my basic understanding of Gradle and my near-nonexistant understanding of KSP/KAPT. Here is how I currently envision the process: 1. I write a code generator, say
MyGenerator
with a function
generate()
, that when invoked uses KotlinPoet to build files and write them to directory
build/generated/MyGeneratedCode
using
FileSpec.writeTo()
2. Somehow gradle executes a task prior to
assemble
that calls`MyGenerator.generate()` 3. Gradle builds a library that uses the code inside of
build/generated/MyGeneratedCode
so that it can create a
jar
or
aar
that contains this code under the package
com.mycompany.MyGeneratedCode
I'm sure I'm off here, but this is what I picture happening. If you have time, can you correct my understanding? If not, thanks for the help so far 🙂
e
1. 👍 note that the library must be separately compiled from this module 2: you have to define that task, but yes 1. the code within
build/generated/MyGeneratedCode
should then get compiled just like other code within
src/main/java
, not a separate jar/aar
m
so is KSP/KAPT how step 2 is intended to happen? Is KSP essentially making my own Kotlin source "callable" from Gradle, and when gradle invokes it, I can tell it to run arbitrary kotlin inside my codebase (that is, executing
MyGenerator.generate()
)?
e
no
😄 1
KAPT/KSP, like 1, take a processor defined in another library or module, but instead of having a task to run it in Gradle, are configured to run it inside the compiler. it gets access to the compiler's input sources, and can output additional sources for the compiler to consume as well
if you are transforming existing sources, you should absolutely use kapt or ksp instead of defining your own
m
okay, maybe it helps to hear my original plan then
I'm working with an existing library from Google that defines an
object
with dozens of functions, for instance, •
GoogleAPI.foo(arg: Int)
GoogleAPI.bar(arg: Boolean)
etc.... My plan was to use kotlin-reflect to take the input class (
GoogleAPI
), iterate through all its functions, and create "wrapper classes" for each member function of
GoogleAPI
using KotlinPoet, of a format like (notice
class foo
is generated because of
GoogleAPI.foo
)
Copy code
class foo() {
    val arg: Int
    
    fun execute() {
        GoogleAPI.foo(arg)
    }
}
it seems like I am opting for reflection because it's what I'm aware of, but I think you suggest KSP for this?
e
example of codegen for Android library from a Gradle task
it does sound like you would be better served with KAPT or KSP though. you won't be able to use "reflection" per se, instead the compiler APIs give you access to Elements, TypeMirrors etc.
m
right, I'd be lookiing at the actual tokens from compilation rather than reflecting them at runtime, right?
e
you're not going to be looking at the source code, the APIs give you something reflection-like (here are all the types, and the names and types of all the methods and variables, and all the associated annotations)
m
gotcha, I think we're on the same page
i'll look into this some more tomorrow. thanks so much for your time & patience @ephemient
340 Views