Replace stuff within an existing Kotlin file, wit...
# gradle
n
> Replace stuff within an existing Kotlin file, without having to commit the result Sounds like something that should be just done with Gradle Plugin. No one stops you from manipulating files that are up to compilation and using stuff like https://github.com/kotlinx/ast + kotlinpoet (but if case is trivial, you might be fine with regular regex) But it might be that you're trying to achieve the solution with a wrong approach (you should ask yourself whether you actually need a code-generation) I feel like you better off using interface as a template and then generate additional methods with KSP inheriting from it (zero or almost zero cost at runtime) Another workaround without own compiler plugin magic (which IMHO probably is unjustified, as well as code-generation in general) is to have two source-sets (and two compilations in KGP) and generate a duplicate with KSP + KotlinPoet and expose only compiled code from the non-template source set which is huge pain, but easier than commiting to Kotlin Compiler and subsequently to IDE plugin to support it
c
then generate additional methods with KSP inheriting from it (zero or almost zero cost at runtime)
Downsides: • Two interfaces in the public API • The documentation of both is in different pages in Dokka output
Another workaround without own compiler plugin magic (which IMHO probably is unjustified, as well as code-generation in general) is to have two source-sets (and two compilations in KGP) and generate a duplicate with KSP + KotlinPoet and expose only compiled code from the non-template source set
That's interesting 🤔 But doesn't that require the other module to have access to the first's classpath? Since KSP can only generate code within the current module.
n
it's managable, you can filter template interface out for dokka and put a RequiresOptIn(ERROR) I mean it's not a silver bullet, but imho better than custom compiler plugin
regarding workaround - not really also it's still the same module, just a different sourceset i mean, you anyway urself assigning generated sources to a specific sourceset or I might've misunderstood the question
c
It's a KMP library, but all the code is in commonMain
n
I mean the scheme is pretty straightforward: create new compilation unit in KGP -> apply KSP on given source set (and write your templates there) -> generate code into build folder -> add this folder to the main compilation unit (commonMain)
but it will work only if you got interfaces to process there's no AST support for function bodies and so on, so you wouldn't be able to "copy" code
c
No, there's a lot of non-interface code.
Though all that code I only need to copy, there's no processing to do to it.
And the interfaces have many default methods
n
so ur best shot is probably standard solution: @DelicateApi open class SomethingSpec @OptIn(DelicateApi::class) class Something : SomethingSpec() .. or commiting to compiler plugin
you can create a small plugin for dokka to filter out stuff
or maybe kotlinx.ast from above might satisfy your case really depends on complexity
h
It really depends on your real problem, but did you think about generate both files with just kotlinpoet instead of creating one file manually and try to parse it for another generated file?
c
The real problem is that I have interfaces that contain MongoDB operators. I currently have about 200 operators, and expect to reach ~600 operators by the time the project is done. Each of these has a main overload, and 3–7 convenience overloads (that have a different type as parameter, convert it to the main one and trivially call the main one). That's a lot to maintain manually. Most importantly: all the overloads must carry the same documentation as the main one, because they are the ones end-users will directly call.
h
And what about generating all the operators with kotlinpoet in a custom Gradle task? I would not expect to write 600 operators by hand at all 😅 I guess, the operators are mostly the same, but requires different parameters.
n
What about marking base class as internal, generate another class and use delegation pattern? Mark constructor as internal and make factory inline function with PublishedApi
c
> base class as internal It's an interface, and a public interface can't implement an internal interface. It has to be an interface, because I use interface delegation to combine contexts where different operators are available.
n
you don't need to inherit an interface
it's easy to recreate interface
harder to recreate entire class and the solution above is a great workaround to do so
c
And what about generating all the operators with kotlinpoet in a custom Gradle task? I would not expect to write 600 operators by hand at all 😅
I guess, the operators are mostly the same, but requires different parameters.
Different names, different parameters, different type parameters, different documentation…
h
Different names, different parameters, different type parameters, different documentation…
Yes, but you could use loops. Anyway it was just an idea to not write any "Kotlin" code by hand but move the logic into a generator. The generator/the data model of the operators will be big, for sure (unless you can also scrap the operators from the mongoDB documentation)
👍 1
c
unless you can also scrap the operators from the mongoDB documentation
This is not feasible. I tweak many of them to fit better into Kotlin, etc.