Kotlin compiler argument not applied to scratch file - kotlin

I wanted to use kotlin.time.measureTime in a Kotlin scratch file but it doesn't work.
...
kotlinOptions {
...
freeCompilerArgs = ["-Xopt-in=kotlin.time.ExperimentalTime"]
}
Of course in my scratch file I "use classpath of module" which defines the compiler argument. When I use measureTime in regular code it works fine, but in the scratch file doesn't.

#OptIn works for me (see alternative recommendation by IntelliJ IDEA).
#OptIn(ExperimentalTime::class)
val s = measureTime { sleep(10) }
println(s)

Try to use explicit declaration of experimental API usage inside scratch file:
#ExperimentalTime
val s = measureTime {
}

Related

Why is the kotlin-gradle-plugin failing to create a PSIFile from CodeInsightTestFixture.configureByText?

I created an IntelliJ plugin using the template https://github.com/JetBrains/intellij-platform-plugin-template. The template comes with a test that runs on an XML file. I want to create a similar test for a Kotlin file. Here's the template test file plus my added test (test2):
package org.jetbrains.plugins.template
import com.intellij.ide.highlighter.XmlFileType
import com.intellij.psi.xml.XmlFile
import com.intellij.testFramework.TestDataPath
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.util.PsiErrorElementUtil
#TestDataPath("\$CONTENT_ROOT/src/test/testData")
class MyPluginTest : BasePlatformTestCase() {
fun testXMLFile() {
val psiFile = myFixture.configureByText(XmlFileType.INSTANCE, "<foo>bar</foo>")
val xmlFile = assertInstanceOf(psiFile, XmlFile::class.java)
assertFalse(PsiErrorElementUtil.hasErrors(project, xmlFile.virtualFile))
assertNotNull(xmlFile.rootTag)
xmlFile.rootTag?.let {
assertEquals("foo", it.name)
assertEquals("bar", it.value.text)
}
}
override fun getTestDataPath() = "src/test/testData/rename"
fun testRename() {
myFixture.testRename("foo.xml", "foo_after.xml", "a2")
}
// Here's my test
fun test2() {
val fileText: String = """
package com.loganmay.test
data class MyClass(val myString: String)
""".trimIndent()
val psiFile = myFixture.configureByText("a.kt", fileText)
val xmlFile = assertInstanceOf(psiFile, XmlFile::class.java)
}
}
Without changing the build.gradle file, that test fails with:
Expected instance of: com.intellij.psi.xml.XmlFile actual: com.intellij.psi.impl.source.PsiPlainTextFileImpl
I want it to parse the text as a PsiFile that's also a KtFile. From various sources, I've been led to believe that the fixture is parsing it as a plain text file because the test project doesn't have access to the Kotlin compiler. So, I added:
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
}
to the build.gradle. Then, when I run the test, configureByText throws an exception with a big trace, the root exception of which is:
Caused by: java.lang.Throwable: 'filetype.archive.display.name' is not found in java.util.PropertyResourceBundle#4ecbb519(messages.CoreBundle)
... 53 more
org.jetbrains.plugins.template.MyPluginTest > test2 FAILED
com.intellij.diagnostic.PluginException at ComponentManagerImpl.kt:511
Caused by: java.util.MissingResourceException at Registry.java:164
Does anyone have any insight into what the issue is or know how to resolve it?
Notes:
I also tried importing the kotlin compiler and casting psiFile as KtFile, which produced the same error, an idea I got from here
This project has a test like this that may be working
This post and this post recommend adding the kotlin gradle plugin, which I did
This question seems similar
Yann Cebron replied on the jetbrains help forum with an answer for Java, which also worked for Kotlin.
The solution is to add a dependency to the IntelliJ gradle plugin. The template comes with these lines in the build.gradle:
intellij {
pluginName.set(properties("pluginName"))
version.set(properties("platformVersion"))
type.set(properties("platformType"))
// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty))
}
So, didn't need to do anything there. In my gradle.properties, I added
platformPlugins = com.intellij.java, org.jetbrains.kotlin
To my plugin.xml, I added:
<depends>com.intellij.modules.java</depends>
<depends>org.jetbrains.kotlin</depends>
I was able to remove
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
}
from the build.gradle which I mentioned above.
Now, the test works for Java and Kotlin files.

Calling PCEnhancerTask from Kotlin in Gradle

I need to call the OpenJPA PCEnhancerTask class from Kotlin instead of Groovy. The following code works just fine (based on a previous solution documented here):
def openJPAClosure = {
def entityFiles = sourceSets.main.output.classesDirs.asFileTree.matching {
include 'com/company/persist/*Entity.class'
}
println "Enhancing with OpenJPA:"
entityFiles.getFiles().each {
println it
}
ant.taskdef(
name : 'openjpac',
classpath : sourceSets.main.runtimeClasspath.asPath,
classname : 'org.apache.openjpa.ant.PCEnhancerTask'
)
ant.openjpac(
classpath: sourceSets.main.runtimeClasspath.asPath,
addDefaultConstructor: false,
enforcePropertyRestrictions: true) {
entityFiles.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
}
}
I was looking at the documentation on how to call Ant tasks from Gradle but I could not translate all the necessary steps using the GroovyBuilder. So instead I tough of calling the PCEnhancer directly:
fun openJPAEnrich() {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.getFiles().forEach() {
println(it)
}
org.apache.openjpa.ant.PCEnhancerTask.main(asList(entityFiles))
}
But it complains about not being able to find org.apache.openjpa in the classpath (but is it listed as a compilation dependency)
My questions are:
What is the correct way to translate the original Groovy construct to Kotlin using groovyBuilder
If is not possible, how you can correctly call PCEnhancer from Kotlin in Gradle?
So I ended making it work with a custom JavaExec Gradle task:
tasks.create<JavaExec>("openJPAEnrich") {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.files.forEach() {
println(it)
}
classpath = sourceSets.main.get().runtimeClasspath
main = "org.apache.openjpa.enhance.PCEnhancer"
args(listOf("-enforcePropertyRestrictions", "true", "-addDefaultConstructor", "false"))
entityFiles.forEach { classFile -> args?.add(classFile.toString())}
}
I was tempted to build my own custom Gradle task but for this felt overkill.
Thanks.
--Jose

import kotlinx is impossible in IntelliJ IDEA

I`m testing coroutine example code on IntelliJ IDEA. But I cannot import library which needs for coroutine.
I created project as Kotlin - "JVM | IDEA". I tried simple print hello world code and succeesfully done. But coroutine example don`t even execute.
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
fun main()
{
runBlocking {
var counter = 0
val lock = Mutex()
val coroutines = List(3) {
launch {
repeat(1_000) {
lock.withLock {
counter++
}
}
}
}
coroutines.forEach { it.join() }
println("Final counter: $counter")
}
}
This Code runs on https://play.kotlinlang.org. But in IDEA, they cannot understand it, showing 'Unresolved reference'.
I`ve searched but no answer found. How can I run this on IDEA project?
it would be a good idea to switch to a Gradle-based build, which will automatically be imported by IntelliJ IDEA.
you can set IntelliJ to automatically stay in sync with your Gradle files, or you can opt to "sync" IntelliJ to Gradle's structure on demand.
you have two syntax options with Gradle: Groovy and Kotlin. make sure if you are new to Gradle that you use a consistent syntax, otherwise it can be hard to follow the guides. obviously if you are working in Kotlin, it's a great idea to use Kotlin in your build files, too.

Kotlin dynamically compile a class from source code at runtime

Is it possible to compile and instantiate Kotlin class at runtime? I'm talking about something like that but using Kotlin API: How do I programmatically compile and instantiate a Java class?
As example:
I'm getting full class definition as String:
val example = "package example\n" +
"\n" +
"fun main(args: Array<String>) {\n" +
" println(\"Hello World\")\n" +
"}\n"
And then inserting it into some class.kt and running it so I'm getting "Hello World" printed in console at runtime.
You might want to look at Kotlin Scripting, see https://github.com/andrewoma/kotlin-script
Alternatively, you'll need to write your own eval(kotlin-code-string-here) method which will dump the text inside blah.kt file for example, compile it using an external Kotlin compiler into blah.class then dynamically load those classes into the runtime using the Java Classloader doing something like this:
MainClass.class.classLoader.loadClass("com.mypackage.MyClass")
This might be very slow and unreliable.
Another no so great option is to make use of Rhino and run JavaScript inside your Kotlin code. So once again, you'll have an eval(kotlin-code-string-here) method which will dump the content to a blah.kt file, then you would use a Kotlin2JS compiler to compile it to JavaScript and directly execute the JavaScript inside Kotlin using Rhino which is not great either.
Another option is to make use of Kotlin Scripting or an external Kotlin compiler (in both cases, the Kotlin compiler will have to start up) and doing something like this will also allow you to execute dynamically, albeit, only on Unix systems.
Runtime.getRuntime().exec(""" "kotlin code here" > blah.kts | sh""")
I'm not aware of a clean solution for this, Kotlin was not designed to be run like like PHP / JavaScript / Python which just interprets text dynamically, it has to compile to bytecode first before it can do anything on the JVM; so in each scenario, you will need to compile that code first in one way or another, whether to bytecode or to javascript and in both cases load it into you application using the Java Classloader or Rhino.
Please check this solution for dependencies, jar resources, etc. Code below isn't enough for successful execution.
However, to compile dynamic class you can do the following:
val classLoader = Thread.currentThread().contextClassLoader
val engineManager = ScriptEngineManager(classLoader)
setIdeaIoUseFallback() // hack to have ability to do this from IntelliJ Idea context
val ktsEngine: ScriptEngine = engineManager.getEngineByExtension("kts")
ktsEngine.eval("object MyClass { val number = 123 } ")
println(ktsEngine.eval("MyClass.number"))
Please note: there is code injection possible here. Please be careful and use dedicated process or dedicated ClassLoader for this.
KotlinScript can be used to compile Kotlin source code (e.g. to generate a jar file that can then be loaded).
Here's a Java project which demonstrates this (code would be cleaner in Kotlin):
https://github.com/alexoooo/sample-kotlin-compile/blob/main/src/main/java/io/github/alexoooo/sample/compile/KotlinCompilerFacade.java
Note that the code you provide would be generated as a nested class (inside the script).
Here is a Kotlin version:
#KotlinScript
object KotlinDynamicCompiler {
//-----------------------------------------------------------------------------------------------------------------
const val scriptClassName = "__"
const val classNamePrefix = "${scriptClassName}$"
private val baseClassType: KotlinType = KotlinType(KotlinDynamicCompiler::class.java.kotlin)
private val contextClass: KClass<*> = ScriptCompilationConfiguration::class.java.kotlin
//-----------------------------------------------------------------------------------------------------------------
fun compile(
kotlinCode: String, outputJarFile: Path, classpathLocations: List<Path>, classLoader: ClassLoader
): String? {
Files.createDirectories(outputJarFile.parent)
val scriptCompilationConfiguration = createCompilationConfigurationFromTemplate(
baseClassType, defaultJvmScriptingHostConfiguration, contextClass
) {
jvm {
val classloaderClasspath: List<File> = classpathFromClassloader(classLoader, false)!!
val classpathFiles = classloaderClasspath + classpathLocations.map { it.toFile() }
updateClasspath(classpathFiles)
}
hostConfiguration(ScriptingHostConfiguration (defaultJvmScriptingHostConfiguration) {
jvm {
compilationCache(
CompiledScriptJarsCache { _, _ ->
outputJarFile.toFile()
}
)
}
})
}
val scriptCompilerProxy = ScriptJvmCompilerIsolated(defaultJvmScriptingHostConfiguration)
val result = scriptCompilerProxy.compile(
kotlinCode.toScriptSource(KotlinCode.scriptClassName), scriptCompilationConfiguration)
val errors = result.reports.filter { it.severity == ScriptDiagnostic.Severity.ERROR }
return when {
errors.isEmpty() -> null
else -> errors.joinToString(" | ")
}
}
}

Aspectj doesn't work with kotlin

i want to use aspectj aop in kotlin,here is my code:
my annotation in annotation.lazy_list:
Kotlin:
package anotation
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class lazy_list
my aspectj aop class:
#Aspect
class ActiveListAop{
#Pointcut("execution(#annotation.lazy_list * *(..))")
fun profile() {
}
#Before("profile()")
fun testModeOnly(joinPoint: JoinPoint) {
println("123")
}
}
my usage:
#lazy_list
fun all():List<T>{
return lazy_obj?.all() as List<T>
}
when i call all() function , no error,but wont't print "123", why?
EDIT 9-2021 - there is a nice updated plugin for android that works well as an alternate to my original 2018 answer below: https://github.com/Ibotta/gradle-aspectj-pipeline-plugin
For what it's worth, we needed aspectJ weaving in our android project but really wanted to move to kotlin so we had to solve this problem. So the solutions in this thread using spring or maven didn't work for us. This is the solution for android gradle projects however, this WILL break incremental compilation and therefor slow down your build times and/or break something eventually. This gets us by until I can re-think our architecture and phase out aspectJ or (hopefully) android starts supporting it.
There is confusion in some of the answers and comments to the OP that kapt solves this, but kapt lets you do compile time annotation processing, not weaving. That is, annotation processors let you generate code based on annotations but do not let you inject logic into existing code.
This builds on top of this blog on adding aspectJ to android: https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android
Your kotlin classes get compiled into byte code, just into a different directory. So this solution using the same process to weave the java classes but runs it again on the kotlin class files
at the top of your App/build.gradle add:
buildscript {
ext.aspectjVersion = '1.9.1'
dependencies {
classpath "org.aspectj:aspectjtools:$aspectjVersion"
}
}
At the bottom of your App/build.gradle add:
android.applicationVariants.all { variant ->
// add the versionName & versionCode to the apk file name
variant.outputs.all { output ->
def newPath = outputFileName.replace(".apk", "-${variant.versionName}.${variant.versionCode}.apk")
outputFileName = new File(outputFileName, newPath)
def fullName = ""
output.name.tokenize('-').eachWithIndex { token, index ->
fullName = fullName + (index == 0 ? token : token.capitalize())
}
JavaCompile javaCompile = variant.javaCompiler
MessageHandler handler = new MessageHandler(true)
javaCompile.doLast {
String[] javaArgs = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(
File.pathSeparator)]
String[] kotlinArgs = ["-showWeaveInfo",
"-1.8",
"-inpath", project.buildDir.path + "/tmp/kotlin-classes/" + fullName,
"-aspectpath", javaCompile.classpath.asPath,
"-d", project.buildDir.path + "/tmp/kotlin-classes/" + fullName,
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(
File.pathSeparator)]
new Main().run(javaArgs, handler)
new Main().run(kotlinArgs, handler)
def log = project.logger
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break
case IMessage.DEBUG:
log.debug message.message, message.thrown
break
}
}
}
}
spring + kotlin + AOP work nice, just go to http://start.spring.io/ and generate a project with AOP support, you can see a piece of build.gradle here...
buildscript {
ext {
kotlinVersion = '1.2.30'
springBootVersion = '2.0.0.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'
...
dependencies {
compile('org.springframework.boot:spring-boot-starter-aop')
...
}
plugin kotlin-spring makes all classes open to allow AOP
Then, just declare your aspect as follows
#Aspect
#Component
class MyAspect {
...
Important: annotate your aspect class with #Aspect and #Component annotations
Piece of cake! :)
For annotation process in Kotlin, you must enable and use KAPT. Without this being added via Gradle or Maven plugin, nothing is going to work for annotation processing in Kotlin code.
The Kotlin plugin supports annotation processors like Dagger or DBFlow. In order for them to work with Kotlin classes, apply the kotlin-kapt plugin.
See also:
Pushing the limits of Kotlin annotation processing
kapt: Annotation Processing for Kotlin
Better Annotation Processing: Supporting Stubs in kapt
You can use freefair gradle plugin
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "io.freefair.gradle:aspectj-plugin:5.2.1"
}
}
apply plugin: "io.freefair.aspectj.post-compile-weaving"
So I think I've got a good (but wordy) solution for Android. At the time of writing I'm using Gradle 6.7, Android plugin 4.1.0, and AspectJ tools 1.9.6.
The gist of the problem is that:
Java is compiled by task compileDebugJavaWithJavac
Kotlin is compiled by task compileDebugKotlin
Gradle can run either one of these tasks, or both of them, or none
compileDebugJavaWithJavac depends on compileDebugKotlin
weaving Kotlin usually requires Java classes.
If you look at these points closely, you'll see that you can't do weaving as part of compiling Kotlin, as Java classes can be missing at this point. If you do that, you'll get warnings such as:
WARN: incorrect classpath: C:\Users\user\StudioProjects\myapp\app\build\intermediates\javac\debug\classes
and errors such as
ERROR: can’t determine modifiers of missing type myapp.Foo.Bar
So the better approach would be to postpone weaving until Java classes are compiled. But as you would be modifying files not as a part of compilation task, you lose incremental builds... Besides, this postponed weaving is super hard to get right—remember, none of the compile tasks might be actually scheduled for running!
The real solution is to wrap weaving in a Transform, which will produce a Gradle task with its own inputs and outputs. This means that you will not be polluting the files of compile tasks, and those tasks, as well as this task, will be, so to say, UP-TO-DATE-able. This requires quite a bit of code, but it's rather sensible!
First, put this in your project build.gradle.kts:
buildscript {
dependencies {
classpath("org.aspectj:aspectjtools:1.9.6")
}
}
This is needed to run weaving from inside “inside” the buildscript. If you want to run weaving in a separate process, which is a good idea on Windows, you will need the path of this jar, which you can get by adding the following to your app build.gradle.kts:
val weaving: Configuration by configurations.creating
dependencies {
weaving("org.aspectj:aspectjtools:1.9.6")
}
Finally, put AspectJ runtime on the classpath (app build.gradle.kts, note that I only need weaving in debug builds):
dependencies {
debugImplementation("org.aspectj:aspectjrt:1.9.6")
}
Now, here's my setup. I have a local logging library, :cats, which containts aspects that I want to weave. Logging statements are only inside my project, and not anywhere else. Also, I only want to run these in debug builds. So here's the transformation that “weaves cats” into the app (app's build.gradle.kts):
class TransformCats : Transform() {
override fun getName(): String = TransformCats::class.simpleName!!
override fun getInputTypes() = setOf(QualifiedContent.DefaultContentType.CLASSES)
// only look for annotations in app classes
// transformation will consume these and put woven classes in the output dir
override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT)
// ...but also have the rest on our class path
// these will not be touched by the transformation
override fun getReferencedScopes() = mutableSetOf(QualifiedContent.Scope.SUB_PROJECTS,
QualifiedContent.Scope.EXTERNAL_LIBRARIES)
override fun isIncremental() = false
// only run on debug builds
override fun applyToVariant(variant: VariantInfo) = variant.isDebuggable
override fun transform(invocation: TransformInvocation) {
if (!invocation.isIncremental) {
invocation.outputProvider.deleteAll()
}
val output = invocation.outputProvider.getContentLocation(name, outputTypes,
scopes, Format.DIRECTORY)
if (output.isDirectory) FileUtils.deleteDirectoryContents(output)
FileUtils.mkdirs(output)
val input = mutableListOf<File>()
val classPath = mutableListOf<File>()
val aspectPath = mutableListOf<File>()
invocation.inputs.forEach { source ->
source.directoryInputs.forEach { dir ->
input.add(dir.file)
classPath.add(dir.file)
}
source.jarInputs.forEach { jar ->
input.add(jar.file)
classPath.add(jar.file)
}
}
invocation.referencedInputs.forEach { source ->
source.directoryInputs.forEach { dir ->
classPath.add(dir.file)
}
source.jarInputs.forEach { jar ->
classPath.add(jar.file)
if (jar.name == ":cats") aspectPath.add(jar.file)
}
}
weave(classPath, aspectPath, input, output)
}
}
android.registerTransform(TransformCats())
And here's the weaving code mentioned above:
// ajc gets hold of some files such as R.jar, and on Windows it leads to errors such as:
// The process cannot access the file because it is being used by another process
// to avoid these, weave in a process, which `javaexec` will helpfully launch for us.
fun weave(classPath: Iterable<File>, aspectPath: Iterable<File>, input: Iterable<File>, output: File) {
val runInAProcess = OperatingSystem.current().isWindows
val bootClassPath = android.bootClasspath
println(if (runInAProcess) ":: weaving in a process..." else ":: weaving...")
println(":: boot class path: $bootClassPath")
println(":: class path: $classPath")
println(":: aspect path: $aspectPath")
println(":: input: $input")
println(":: output: $output")
val arguments = listOf("-showWeaveInfo",
"-1.8",
"-bootclasspath", bootClassPath.asArgument,
"-classpath", classPath.asArgument,
"-aspectpath", aspectPath.asArgument,
"-inpath", input.asArgument,
"-d", output.absolutePath)
if (runInAProcess) {
javaexec {
classpath = weaving
main = "org.aspectj.tools.ajc.Main"
args = arguments
}
} else {
val handler = MessageHandler(true)
Main().run(arguments.toTypedArray(), handler)
val log = project.logger
for (message in handler.getMessages(null, true)) {
when (message.kind) {
IMessage.DEBUG -> log.debug("DEBUG " + message.message, message.thrown)
IMessage.INFO -> log.info("INFO: " + message.message, message.thrown)
IMessage.WARNING -> log.warn("WARN: " + message.message, message.thrown)
IMessage.FAIL,
IMessage.ERROR,
IMessage.ABORT -> log.error("ERROR: " + message.message, message.thrown)
}
}
}
}
val Iterable<File>.asArgument get() = joinToString(File.pathSeparator)
(The Windows part is using weaving configuration; you may not want either part of the if)
This is it!
Edit: As of AGP 4.2.0, jar.name doesn't return anything useful. For the time being, I used this fragile workaround:
if (jar.file.directoriesInsideRootProject().contains("cats")) {
aspectPath.add(jar.file)
}
fun File.directoriesInsideRootProject() = sequence {
var file = this#directoriesInsideRootProject
while (true) {
yield(file.name)
file = file.parentFile ?: break
if (file == rootProject.projectDir) break
}
}