I configured kotlin-noarg plugin in my Spring application:
buildscript {
ext {
kotlinVersion = '1.2.51'
}
dependencies {
// ...
classpath "org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}"
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: "kotlin-noarg"
noArg {
annotation("com.example.demo.DefaultConstructor")
invokeInitializers = true
}
Then I have my annotation declared:
package com.example.demo
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.CLASS)
annotation class DefaultConstructor
And then I have my class:
#DefaultConstructor
data class UserForm(#field:NotNull
#field:DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") val dateTime: LocalDateTime)
Here is how I'm trying to use UserForm class:
#GetMapping("")
public String userForm(UserForm userForm) {
userForm.copy(LocalDateTime.now());
return "/users/form";
}
So Spring is trying here to initialize my UserForm class using reflection, but there is no default constructor being generated, despite kotlin-noarg plugin being configured(correctly, I believe).
Where is the problem here ? Is it because of misconfiguration of the kotlin-noarg plugin ? And how to fix it ?
I created demo project
I'm not fully sure with the answer but:
kotlin-noarg provides a syntactic constructor(only available with reflection)
If spring is trying to instantiate UserForm directly (without reflection) it will fail.
There is a way to avoid the problem.
Mark UserForm as #Entity after this it will be recognized as something that belongs to spring. After this no-arg constructor will be either generated by kotlin-spring plugin or you need to add kotlin-jpa plugin
Related
I'm writing a custom gradle plugin that uses an extension. Here's what the relevant part look like:
class Aar2Jar implements org.gradle.api.Plugin<Project> {
void apply (Project project) {
println('applying Android Jar')
def ext = project.extensions.create('jarSpec', JarSpecExtension)
if (ext.name.get() == "") {
ext.name.set(project.name)
}
and Here's what the extension looks like:
package test.android.gradle
import org.gradle.api.provider.Property
abstract class JarSpecExtension {
abstract Property<String> getName()
abstract Property<String> getVersion()
abstract Property<String> getInclude()
abstract Property<String> getExclude()
JarSpecExtension() {
name.convention("")
version.convention("1.0.0")
include.convention("*")
exclude.convention("")
}
}
And here's what my build.gradle script that uses this custom plugin looks like:
plugins {
id 'com.android.library'
id 'test.android.jar'
}
jarSpec {
name = 'myJar'
}
The problem is that doesn't work and I get the following error:
* What went wrong:
An exception occurred applying plugin request [id: 'test.android.jar']
> Failed to apply plugin 'test.android.jar'.
> No such property: extensions for class: test.android.gradle.Aar2Jar
However, if I change the build.gradle file to this:
plugins {
id 'com.android.library'
id 'test.android.jar'
}
jarSpec.name = 'myJar'
It works perfect. The question is why can I not refer to the extension using the closure style. I'm using gradle version 7.2.
The gradle doc describes the #Nested annotation for custom gradle tasks:
https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:task_input_output_annotations
Unfortunately, there is no complete example of this mechanism in terms of how it is used in a build.gradle file. I created a project to demonstrate a strange exception that happens whenever gradle configures the project:
https://github.com/NicolasRouquette/gradle-nested-property-test
The build.gradle has the following:
task T(type: NestedTest) {
tool = file('x')
metadata = {
a = "1"
}
}
The NestedTest custom task is in the buildSrc folder:
class NestedTest extends DefaultTask {
#InputFile
public File tool
#Nested
#Input
public Metadata metadata
#TaskAction
def run() throws IOException {
// do something...
}
}
The important bit is the #Nested property whose type is really basic:
class Mlang-groovyetadata {
String a
}
When I execute the following: ./gradlew tasks, I get this:
Build file '/opt/local/github.me/gradle-nested-property-test/build.gradle' line: 26
* What went wrong:
A problem occurred evaluating root project 'gradle-nested-property-test'.
> Cannot cast object 'build_6wy0cf8fn1e9nrlxf3vmxnl5z$_run_closure4$_closure5#2bde737' with class 'build_6wy0cf8fn1e9nrlxf3vmxnl5z$_run_closure4$_closure5' to class 'Metadata'
Can anyone explain what is happening and how to make this work?
Nicolas
Looking at the unit tests in Gradle's source code, I found that the syntax for #Nested properties requires invoking the type constructor in the build.gradle file.
That is, the following works:
task T(type: NestedTest) {
tool = file('x')
metadata = new Metadata(
a: "1"
)
}
I have a custom annotation processor that does roughly this:
generate an annotation type (classes using this type are deferred until later rounds)
in a later round, process the classes using this type and generate some more files for them
This has worked well so far in Java. But recently, I've been converting some of my classes from Java to Kotlin. The setup is still the same.
But when I converted a class using the generated annotation type to Kotlin, this error popped up:
error: incompatible types: NonExistentClass cannot be converted to Annotation
#error.NonExistentClass()
It's worth noting that this only happens when the file is in Kotlin - other (Java) files also use the same annotation, and they don't produce an error when I remove the annotation from the Kotlin file.
After some googling, I found out that the recommended solution is to add an option to the build.gradle:
kapt {
correctErrorTypes = true
}
However, using this did not fix the problem. I looked at the generated stub file, and it's clear that kapt keeps putting error.NonExistentClass in there despite the setting:
package my.pckg;
import my.pckg.GeneratedAnnotation;
#kotlin.Metadata(...)
public final class MyClass {
private int myAnnotatedField;
#error.NonExistentClass()
public static void myAnnotatedField$annotations() {
}
public final int getMyAnnotatedField() {
return 0;
}
public final void setMyAnnotatedField(int p0) {
}
//...
}
I think it's worth noting that:
the stub kept the correct import for the generated annotation
kapt moved the annotation to a static method, while the original generated annotation supports only fields
MyClass looks like this:
package my.pckg
import my.pckg.GeneratedAnnotation
class MyClass {
#GeneratedAnnotation
var myAnnotatedField: Int = 0
//...
}
And here's the build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
//...
kotlinOptions {
jvmTarget = '1.8'
}
}
kapt {
correctErrorTypes = true
}
dependencies {
kapt 'custom.annotation.processor'
implementation "androidx.core:core-ktx:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41"
//...
}
There have been other people with this problem on SO, but none of the solutions suggested there have fixed it for me.
Edit: I have done some more testing, and discovered that the generated type is only replaced by error.NonExistentClass when the type is used as an annotation (as an object type or even generics argument it works fine). At this point I believe this may simply be a kapt bug.
I'm developing Gradle custom plugin. I want to add dependency to the existing configuration. I'm trying to do it like this:
open class MyApplicationExtension #Inject constructor(objects: ObjectFactory) {
val version: Property<String> = objects.property(String::class)
}
class MyApplicationPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.apply(ApplicationPlugin::class)
val extension = project.extensions.create<MyApplicationExtension>("myApp")
val implConfig = project.configurations["implementation"]
implConfig.defaultDependencies {
add(project.dependencies.create("com:my-app:${extension.version.get()}"))
}
}
}
But when I try to use application in gradle project the added dependency is not added. I'm trying to use it like this:
apply<MyApplicationPlugin>()
the<MyApplicationExtension>().version.set("0.1.0")
dependencies {
// This overrides the default dependencies
implementation("com:another:0.2.0")
}
And when I invoke dependencies task my dependency is not shown there. So how to add configurable dependency to the implementation configuration from custom plugin? Running with Gradle 5.3.1 in Kotlin DSL.
Default dependencies are only used if no other dependency is added to the configuration.
Since that does not seem to be your use case, you should simply add the dependency in a normal fashion.
implConfig.defaultDependencies {
add(project.dependencies.create("com:my-app:${extension.version.get()}"))
}
needs to become
implConfig.dependencies.add(project.dependencies.create("com:my-app:${extension.version.get()}"))
I'm using a custom Gradle plugin with a class defined like:
class CustomPlugin implements Plugin<Project> {
public static final String CONSTANT = 'value'
}
I've tried all combinations of apply plugin: 'platform-java8-fix' and import netflix.nebula.platformjava8fix.PlatformJava8FixPlugin but none of them allow me to reference CustomPlugin.CONSTANT.
I would like to do something like:
dependencies {
compile "group:module:${CustomPlugin.CONSTANT}"
}
How can this be done?
UPDATE: From within build.gradle, I'm able to access CustomPlugin.CONSTANT. I would like to access it from another file, dependencies.gradle, and have build.gradle do:
apply from: "${rootDir}/dependencies.gradle"
But when I try to perform the import, I get the error:
startup failed:
script 'dependencies.gradle': 1: unable to resolve class com.example.CustomPlugin