How to generate a kotlin file from an annotation processor? - kotlin

I have a java annotation processor which generates a bunch of java files during compilation. I'd like to make the generated classes nicer to use in kotlin by adding extension methods. I've been told on the kotlin forums that something I could try would be to write a kotlin file that contains my extension functions. I've tried this, I used the Filer object to create this file outputting it to the StandardLocations.SOURCE_OUTPUT directory. Intellij can see my generated class, and I can use the extension functions as intended, but the app won't compile because the compiler can't find the new kotlin file. Is there any way I can write a new kotlin file that'll get picked up by the kotlin compiler?

For kapt you can get source folder via.
Map<String, String> options = processingEnv.getOptions();
String generatedPath = options.get("kapt.kotlin.generated");
String path = generatedPath
.replaceAll("(.*)tmp(/kapt/debug/)kotlinGenerated",
"$1generated/source$2");
Unfortunately it doesn't work for kapt2 (see issue KT-14070)
You also can create .kt files via resource writer
Writer w = processingEnv.getFiler().createResource(SOURCE_OUTPUT, "package_name", "Sample.kt")
But for now you need to invoke compiler twice cause compileDebugKotlin task runs before invoking javax annotation processor by compileDebugJavaWithJavac task)

Output your files (with proper package names) into a directory like src/build/generated-src/kotlin/your/package/File.kt
and add this to your build.gradle:
sourceSets {
main.java.srcDirs += 'build/generated-src/kotlin'
}

Related

How to use Kotlin compiler library to analyze external project

I have a problem with processing Kotlin source code via kotlin-compiler-embeddable. I need to obtain PSI class representations for all classes in an external project, build them, and get BindingContext, but I can't find any examples.
I would like to use the kotlin-compiler-embeddable library to analyse dependencies in a Kotlin project outside the IntelliJ environment.
I manage to obtain PSI representations for individual classes. Unfortunately this is not enough. I would like to track dependencies between classes.
private val project by lazy {
KotlinCoreEnvironment.createForProduction(
Disposer.newDisposable(),
CompilerConfiguration(),
EnvironmentConfigFiles.JVM_CONFIG_FILES
).project
}
fun createKtFile(codeString: String, fileName: String) =
PsiManager.getInstance(project)
.findFile(
LightVirtualFile(fileName, KotlinFileType.INSTANCE, codeString)
) as KtFile
For example, I need to get a PSI Class representation or fully-qualified class name for the service parameter type.
class ShapeEndpoint(service: ShapeService)
Is there somewhere a comprehensive example that would show how to compile a group of source files, build a BindingContext, access it and then use it effectively?

How can I refer to libraries defined in a shared Gradle build plugin from another build script?

I'm trying to define libraries in a common location. So in an our.libraries.gradle.kts script in a shared build plugin, I have this:
inner class Libraries {
val junit get() = ...
val junitParams get() = ...
}
val libraries = Libraries()
project.extra["libraries"] = libraries
In one of the Groovy build scripts elsewhere in the project, this is referred to like this:
allprojects {
dependencies {
testImplementation libraries.junit
}
}
This works fine.
So I try converting that script to Kotlin:
allprojects {
dependencies {
"testImplementation"(libraries.junit)
}
}
And now this fails, because it can't see the libraries property on the project, so I try explicitly pulling it out at the start:
val libraries: Libraries by project.extra
allprojects {
dependencies {
"testImplementation"(libraries.junit)
}
}
But this doesn't work either, because the script can't find the Libraries class.
I also tried putting Libraries in Libraries.kt, but then I can't seem to call methods like exclude using named parameters because for whatever reason Gradle doesn't support using the DSL when it's moved to a top-level class.
This is sort of similar to this question, but in the case of wanting to put in simple types, everything works fine. Which is to say, I can put the libraries in as a Map, but then any time I want to reference one, I have to write this:
"testImplementation"(libraries["junit"]!!)
This is obviously ugly, so I have been trying to avoid it.
So I'm stumped again.
This is part of a long saga trying to get this to work in many different ways, so the essential question is still the same: how can we define all our libraries in one location, and then refer to those in a type-safe way from other build scripts?
Recently, Gradle added shared dependencies via a TOML file, but that method only supports the version numbers, whereas our library definitions also include the excluded dependencies.
It was hard to put a completely self-contained example in the question because multiple files are involved, so here's a test repo.

Gradle. Custom function in block plugins{}

Can i write in my custom plugin some function like kotlin("jvm")?
plugins {
java
kotlin("jvm") version "1.3.71"
}
I want to write function myplugin("foo") in my custom plugin and then use it like
plugins {
java
kotlin("jvm") version "1.3.71"
custom.plugin
myplugin("foo")
}
How i can do it?
I think that plugins block is some kind of a macro expression. It is parsed and precompiled using a very limited context. Probably, the magic happens somewhere in kotlin-dsl. This is probably the only way to get static accessors and extension functions from plugins to work in Kotlin. I've never seen a mention of this process in Gradle's documentation, but let me explain my thought. Probably, some smart guys from Gradle will correct me.
Let's take a look at some third-party plugin, like Liquibase. It allows you to write something like this in your build.gradle.kts:
liquibase {
activities {
register("name") {
// Configure the activity here
}
}
}
Think about it: in a statically compiled language like Kotlin, in order for this syntaxt to work, there should be an extension named liquibase on a Project type (as it is the type of this object in every build.gradle.kts) available in the classpath of a Gradle's VM that executes the build script.
Indeed, if you click on it, you'll see something like:
fun org.gradle.api.Project.`liquibase`(configure: org.liquibase.gradle.LiquibaseExtension.() -> Unit): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("liquibase", configure)
But take a look at the file where it is defined. In my case it is ~/.gradle/caches/6.3/gradle-kotlin-dsl-accessors/cmljl3ridzazieb8fzn553oa8/cache/src/org/gradle/kotlin/dsl/Accessors39qcxru7gldpadn6lvh8lqs7b.kt. It is definitelly an auto-generated file. A few levels upper in a file tree — at ~/.gradle/caches/6.3/gradle-kotlin-dsl-accessors/ in my case — there are dozens of similar directories. I guess, one by every plugin/version I've ever used with Gradle 6.3. Here is another one for the Detekt plugin:
fun org.gradle.api.Project.`detekt`(configure: io.gitlab.arturbosch.detekt.extensions.DetektExtension.() -> Unit): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("detekt", configure)
So, we have a bunch of .kt files defining all that extensions for different plugins applied to the project. That files are obviously pre-cached and precompiled and their content is available in build.gradle.kts. Indeed, you can find classes directories beside those sources.
The sources are generated based on the content of the applied plugins. It is probably a tricky task that includes some magic, reflection and introspection. Sometimes this magic doesn't work (due too chatic Groovy nature) and then you need to use some crappy DSL from this package.
How are they generated? I see no other way, but to
Parse the build.script.kts with an embedded Kotlin compiler / lexer
Extract all the plugins sections
Compile them, probably against some mocks (remember that Project is not yet available: we're not executing the build.gradle.kts itself yet!)
Resolve the declared plugins from Gradle Plugin repository (with some nuances coming from settngs.gradle.kts)
Introspect plugin's artifacts
Generate the sources
Compile the sources
Add the resulting classes to the script's classpath
And here is the gotcha: there is a very limited context (classpath, classes, methods — call it whatever) available when compiling the plugins block. Actually, no plugins are yet applied! Because, you know, you're parsing the block that applies plugins. Chickens, eggs, and their problems, huh…
So, and we're getting closer to the answer on your question, to provide custom DSL in plugins block, you need to modify that classpath. It's not a classpath of your build.gradle.kts, it's the classpath of the VM that parses build.gradle.kts. Basically, it's Gradle's own classpath — all the classes bundled in a Gradle distribution.
So, probably the only way to provide really custom DSLs in plugins block is to create a custom Gradle distribution.
EDIT:
Indeed, totally forgot to test the buildSrc. I've created a file PluginExtensions.kt in it, with a content
inline val org.gradle.plugin.use.PluginDependenciesSpec.`jawa`: org.gradle.plugin.use.PluginDependencySpec
get() = id("org.gradle.war") // Randomly picked
inline fun org.gradle.plugin.use.PluginDependenciesSpec.`jawa`(): org.gradle.plugin.use.PluginDependencySpec {
return id("org.gradle.cunit") // Randomly picked
}
And it seems to be working:
plugins {
jawa
jawa()
}
However, this is only working when PluginExtensions.kt is in the default package. Whenever I put it into a sub-package, the extensions are not recognized, even with an import:
Magic!
The kotlin function is just a simple extension function wrapping the traditional id method, not hard to define:
fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec =
id("org.jetbrains.kotlin.$module")
However, this extension function is part of the standard gradle kotlin DSL API, which means it's available without any plugin. If you want to make a custom function like this available, you would need a plugin. A plugin to load your plugin. Not very practical.
I also tried using the buildSrc module to make an extension function like the above. But it turns out that buildSrc definitions aren't even available from the plugins DSL block, which has a very constrained syntax. That wouldn't have been very practical anyway, you would have needed to make a buildSrc folder for every project in which you have wanted to use the extension.
I'm not sure if this is possible at all. Try asking on https://discuss.gradle.org/.

How to find source file path by compiled class file programmatically in kotlin?

In kotlin, package can be declared randomly, which is not relative to source file path, then compiler will generate class file in package folder. How to find source file path by compiled class file programmatically?
Example:
package kt.notsamepackage_another
class NotSamePackageKotlin {
fun call() {
}
}
NotSamePackageKotlin.class will be generated in folder: ../kt/notsamepackage_another, but source file may be in ../kt/notsamepackage.
I tried these methods:
decompile class file. But source file path can't be found in byte code
adjust kotlin compiler parameters. I want find a parameter to output a mapping contains path relationship, but nothing was found
traverse source folder recursively. This method is not efficient, it's the last choice.
Is there any method more efficient?
I don't think this is possible.  If the full source path isn't compiled into the bytecode, then there's no way to find it programmatically.  And I'm not aware of any compiler parameters which would do this.  (The simple filename is often compiled in, of course; that's how stack traces can show the filename and line number at which an exception was thrown.  But not the full path.)
Even your third method won't work in general; programs aren't usually run on the same machine on which they were compiled, so your code would have to make some very risky assumptions.
But I'm curious as to why you'd want to know this at all.  What problem are you trying to solve?

Generated classes not found

Using Dagger2 and Kotlin, my component class implementations are not being generated when other calling code exists. When no calling code exists, the implementations are generated.
E.g.
val comp = DaggerMyComponent.create()
Causes the build to fail, DaggerMyComponent is not generated and thus cannot be found
// val comp = DaggerMyComponent.create()
The DaggerMyComponent class is generate and can be viewed on disk, the build succeeds.
I've tried Dagger 2.0.1, 2.0.2, 2.1-Snapshot, using Kotlin beta-1103.
I can post my gradle file, source code, or stack trace if needed.
Has anyone encountered this issue before?
In your app's build file, make sure you added
kapt {
generateStubs = true
}
some example projects can be found here
https://github.com/damianpetla/kotlin-dagger-example/tree/master/app
https://github.com/burntcookie90/KotlinDaggerDataBinding