Receiver class does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()' - kotlin

I'm trying to run local server with KTOR and to cover it with tests. I wrote some code and after writing some tests, the tests successfully raised the local server and passed. However, if I try to start a local server, I get this error
Exception in thread "main" java.lang.AbstractMethodError: Receiver
class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does
not define or inherit an implementation of the resolved method
'abstract boolean getDevelopmentMode()' of interface
io.ktor.application.ApplicationEnvironment.
I attach the code and the stack trace from below
server.kt
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
gson()
}
routing {
val controller = Controller()
get("/converter{from}{to}") {
try {
val fromCurrency = call.request.queryParameters["from"]
val toCurrency = call.request.queryParameters["to"]
val rate = controller.converter(fromCurrency, toCurrency)
val response = Response(
"1 $fromCurrency = $rate $toCurrency",
null
)
call.respond(HttpStatusCode.OK, response)
} catch (e: ControllerException) {
val response = Response(
null,
e.message
)
call.respond(HttpStatusCode.BadRequest, response)
}
}
get {
call.respond(HttpStatusCode.NotFound, Response())
}
}
}
#Serializable
data class Response(
#SerialName("converterResponse")
val converterResponse: String? = null,
#SerialName("errorMessage")
val errorMessage: String? = null
)
converterTest.kt
import com.google.gson.Gson
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ConverterTest {
#Test
fun `Identical Correct Currencies`() {
withTestApplication(Application::module) {
handleRequest(HttpMethod.Get, "/converter?from=USD&to=USD").apply {
assertEquals(HttpStatusCode.OK, response.status())
val expectedResponse = Response("1 USD = 1 USD")
assertEquals(
Gson().toJson(expectedResponse),
response.content
)
}
}
}
}
stacktrace
Exception in thread "main" java.lang.AbstractMethodError: Receiver class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()' of interface io.ktor.application.ApplicationEnvironment.
at io.ktor.application.Application.<init>(Application.kt:20)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:269)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:125)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:245)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:126)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:26)
at ServerKt.main(server.kt:11)
build.gradle
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.30" // or kotlin("multiplatform") or any other kotlin plugin
application
}
group = "me.admin"
version = "1.0-SNAPSHOT"
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
dependencies {
val kotlin_version = "1.5.30"
val ktor_version = "1.6.3"
implementation("io.ktor:ktor-server-netty:1.4.0")
implementation("io.ktor:ktor-client-cio:1.4.0")
implementation("io.ktor:ktor-html-builder:1.4.0")
implementation("io.ktor:ktor-client-serialization:1.3.2-1.4-M2")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
implementation("org.slf4j:slf4j-api:2.0.0-alpha5")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC")
implementation(kotlin("stdlib-jdk8"))
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
implementation("io.ktor:ktor-gson:$ktor_version")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClassName = "ServerKt"
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}

The dependency io.ktor:ktor-server-netty:1.4.0 causes the AbstractMethodError. To fix it just use the same version for all Ktor dependencies:
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("io.ktor:ktor-html-builder:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")

Related

Using async func in Compose Web

I should use Ktor client in Compose Web. But, It can't be called in Compose Web due to async/non-async problem.
Environment is template project made by IntelliJ IDEA.
First, I use this:
val client=HttpClient(Js){
install(ContentNegotiation){
json()
}
}
suspend fun shorterCall(url:String):String{
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
suspend fun main() {
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({ style {
height(100.vh)
margin(0.px)
width(100.vw)
} }) {
Input(type = InputType.Url) {
onInput {
val input=it.value.trim()
if(input.startsWith("http"))
i=shorterCall(input)
else
i="NaN"
}
}
Text(i)
}
}
}
Then, I got that error:
Suspend function can be called only within a coroutine body.
So, I tried another one:
import kotlinx.coroutines.*
fun shorterCall(url:String):String{
var ret:String
suspend fun t():String {
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
runBlocking{
ret=t()
}
return ret
}
//main func is same as upper one.
Then, I got that error:
Unresolved reference: runBlocking
+editing body 1: When I use GlobalScope.launch or js("JSCode"), It raise that error:
e: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
(a lot of internal errors bellow)
You can use the GlobalScope.launch() method to launch a job for a request in a browser environment:
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
fun main() {
val client = HttpClient(Js) {}
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({
style {
height(100.vh)
margin(0.px)
width(100.vw)
}
}) {
Input(type = InputType.Url) {
onInput {
val input = it.value.trim()
if (input.startsWith("http")) {
GlobalScope.launch {
i = client.shorterCall(input)
}
} else {
i = "NaN"
}
}
}
Text(i)
}
}
}
suspend fun HttpClient.shorterCall(url: String): String {
val response = get(url) {
contentType(ContentType.Application.Json)
}
return response.bodyAsText()
}

Parameter 0 of constructor in '' required a bean of type '' that could not be found

I am creating a spring boot application, and using doma for O/R mapper.
I could not start application because repositoryimpl can not find dao class , but i can see them in the build/classes . so build is successful but the application fail to start.
How can I fix it?
Package
Build class
dao class
package com.event.app.backend.infrastructure.dao
import com.event.app.backend.infrastructure.table.EventsTableRecord
import org.seasar.doma.Dao
import org.seasar.doma.Select
import org.seasar.doma.Update
import org.seasar.doma.boot.ConfigAutowireable
#ConfigAutowireable
#Dao
interface EventDao {
#Select
fun getEvents():List<EventsTableRecord>
#Select
fun getEventById(eventId:String):EventsTableRecord
#Update(sqlFile = true)
fun updateTicketCnt(eventId:String,bookTicketCnt:Int):Int
#Update(sqlFile = true)
fun subtractTicketCnt(eventId:String,subtractCount:Int):Int
}
impl class
#Repository
class EventRepositoryImpl(
private val eventDao: EventDao,
private val userDao: UserDao,
) : EventRepository {
/**
* get Events
*/
override fun getEvents(): List<Event> {
// return convertToEvent(eventDao.getEvents())
return listOf(Event(
name = "event1",
description = "description1",
date = LocalDate.now(),
availableTickets = 1
))
}
-omit the details-
build.gradle
buildscript{
repositories{
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.6.2")
}
}
plugins{
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}
apply plugin: "java"
apply plugin: "kotlin"
repositories {
mavenCentral()
}
// テンポラリディレクトリのパスを定義する
ext.domaResourcesDir = "${buildDir}/tmp/doma-resources"
// domaが注釈処理で参照するリソースをテンポラリディレクトリに抽出
task extractDomaResources(type: Copy) {
dependsOn processResources
from processResources.destinationDir
include 'doma.compile.config'
include 'META-INF/**/*.sql'
include 'META-INF/**/*.script'
into domaResourcesDir
}
// テンポラリディレクトリ内のリソースをcompileJavaタスクの出力先ディレクトリにコピーする
task copyDomaResources(type: Copy, dependsOn: extractDomaResources) {
dependsOn extractDomaResources
from domaResourcesDir
into compileJava.destinationDir
}
compileJava {
// 上述のタスクに依存させる
dependsOn copyDomaResources
// テンポラリディレクトリをcompileJavaタスクの入力ディレクトリに設定する
inputs.dir domaResourcesDir
options.encoding = 'UTF-8'
}
compileTestJava {
options.encoding = 'UTF-8'
// テストの実行時は注釈処理を無効にする
options.compilerArgs = ['-proc:none']
}
dependencies {
// spring starter web
implementation ("org.springframework.boot:spring-boot-starter-web")
// doma sprig boot starter
implementation ("org.seasar.doma.boot:doma-spring-boot-starter:1.5.0")
// domaの注釈処理を実行することを示す
annotationProcessor 'org.seasar.doma:doma:2.29.0'
// domaへの依存を示す
implementation 'org.seasar.doma:doma:2.29.0'
}
repositories {
mavenCentral()
}
dependencyManagement{
imports{
mavenBom "org.springframework.boot:spring-boot-dependencies:2.6.2"
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
kotlinOptions {
jvmTarget = '11'
}
}
added UserDao.kt
package com.event.app.backend.infrastructure.dao
import com.event.app.backend.infrastructure.table.UsersTicketsTableRecord
import org.seasar.doma.Dao
import org.seasar.doma.Insert
import org.seasar.doma.Select
import org.seasar.doma.boot.ConfigAutowireable
#ConfigAutowireable
#Dao
interface UserDao {
#Insert
fun insertUsersTickets(usersTicketsTableRecord: UsersTicketsTableRecord):Result<UsersTicketsTableRecord>
#Select
fun getTicketCntByEventUserId(eventId:String,userId:String):Int
}
You'd need to use kapt instead of annotationProcessor in build.gradle when you use Doma with Kotlin.
The code generated by kapt can be found under build/generated/source/kapt.
The following sample repository may be helpful.
https://github.com/domaframework/kotlin-sample
In the case of Spring Boot, you'd need to define the Dao's in a BeanConfig. I guess it's something similiar in this case.
import org.jdbi.v3.core.Jdbi;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import se.xxx.dao.*;
#Configuration
public class BeanConfig {
#Bean
public SwingPositionDao swingPositionDao(#Qualifier("jdbi.manager") Jdbi jdbi) {
return jdbi.onDemand(SwingPositionDao.class);
}
}

Using Ktor, Kmongo and kotlinx.serialization together causing ClassCastException ... What am I doing wrong?

https://github.com/reticent-monolith/winds_server is the github repo if anyone finds it easier looking there.
I'm trying to use KMongo and Ktor with Kotlin's Serialization module but creating the MongoClient results in the following exception:
java.lang.ClassCastException: class org.litote.kmongo.serialization.SerializationCodecRegistry cannot be cast to class org.bson.codecs.configuration.CodecProvider (org.litote.kmongo.serialization.SerializationCodecRegistry and org.bson.codecs.configuration.CodecProvider are in unnamed module of loader 'app')
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry(ClassMappingTypeService.kt:83)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.codecRegistry(SerializationClassMappingTypeService.kt:40)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.coreCodecRegistry(SerializationClassMappingTypeService.kt:97)
at org.litote.kmongo.service.ClassMappingType.coreCodecRegistry(ClassMappingType.kt)
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry$default(ClassMappingTypeService.kt:79)
at org.litote.kmongo.KMongo.configureRegistry$kmongo_core(KMongo.kt:79)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:70)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:57)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:47)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:39)
at com.reticentmonolith.repo.MongoDispatchRepo.<init>(MongoDispatchRepo.kt:11)
at com.reticentmonolith.ApplicationKt.<clinit>(Application.kt:14)
... 23 more
Am I missing something in my build.gradle?
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.5.10'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.10'
id 'application'
}
group 'com.reticentmonolith'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"
sourceSets {
main.kotlin.srcDirs = main.java.srcDirs = ['src']
test.kotlin.srcDirs = test.java.srcDirs = ['test']
main.resources.srcDirs = ['resources']
test.resources.srcDirs = ['testresources']
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
// KOTLIN
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// KTOR
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "ch.qos.logback:logback-classic:$logback_version"
implementation "io.ktor:ktor-server-core:$ktor_version"
testImplementation "io.ktor:ktor-server-tests:$ktor_version"
implementation "io.ktor:ktor-serialization:$ktor_version"
// MONGO
implementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.12.8'
// KOTLINX SERIALIZATION
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
// KMONGO
implementation 'org.litote.kmongo:kmongo-serialization:4.2.7'
implementation group: 'org.litote.kmongo', name: 'kmongo-id-serialization', version: '4.2.7'
// KBSON (optional dependency for KMONGO and Serialization)
implementation "com.github.jershell:kbson:0.4.4"
}
Here's my MongoClient class:
import com.mongodb.client.MongoDatabase
import com.reticentmonolith.models.Dispatch
import java.time.LocalDate
import org.litote.kmongo.*
class MongoDispatchRepo: DispatchRepoInterface {
private val client = KMongo.createClient()
private val database: MongoDatabase = client.getDatabase("zw")
private val windsData = database.getCollection<Dispatch>("winds")
override fun createDispatch(dispatch: Dispatch) {
windsData.insertOne(dispatch)
}
override fun getAllDispatches(): Map<String, List<Dispatch>> {
val list = windsData.find().toList()
return mapOf("dispatches" to list)
}
override fun getDispatchesByDate(date: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date eq date).toList())
}
override fun getDispatchesByDateRange(start: LocalDate, end: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date gte(start), Dispatch::date lte(end)).toList())
}
override fun getDispatchById(id: Id<Dispatch>): Dispatch? {
return windsData.findOneById(id)
}
override fun updateDispatchById(id: Id<Dispatch>, update: Dispatch): Boolean {
val oldDispatch = getDispatchById(id)
if (oldDispatch != null) {
update.date = oldDispatch.date
update._id = oldDispatch._id
windsData.updateOneById(id, update)
return true
}
return false
}
override fun updateLastDispatch(update: Dispatch): Boolean {
val lastDispatch = getLastDispatch() ?: return false
update.date = lastDispatch.date
update._id = lastDispatch._id
windsData.updateOneById(lastDispatch._id, update)
return true
}
override fun deleteDispatchById(id: Id<Dispatch>) {
windsData.deleteOne(Dispatch::_id eq id)
}
override fun getLastDispatch(): Dispatch? {
val todaysDispatches = getDispatchesByDate(LocalDate.now())
if (todaysDispatches.isEmpty()) return null
return todaysDispatches["dispatches"]?.last()
}
override fun addSpeedsToLastDispatch(line4: Int?, line3: Int?, line2: Int?, line1: Int?) {
val lastDispatch = getLastDispatch() ?: return
lastDispatch.riders.get(4)?.speed = line4
lastDispatch.riders.get(3)?.speed = line3
lastDispatch.riders.get(2)?.speed = line2
lastDispatch.riders.get(1)?.speed = line1
updateLastDispatch(lastDispatch)
}
fun purgeDatabase() {
windsData.deleteMany(Dispatch::comment eq "")
}
}
And my Application.kt for Ktor:
package com.reticentmonolith
import com.reticentmonolith.models.createExampleDispatches
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.http.*
import com.reticentmonolith.repo.MongoDispatchRepo
import io.ktor.features.*
import io.ktor.serialization.*
import kotlinx.serialization.json.Json
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule
val repo = MongoDispatchRepo()
fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)
#Suppress("unused") // Referenced in application.conf
#kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
json(
Json { serializersModule = IdKotlinXSerializationModule },
contentType = ContentType.Application.Json
)
}
repo.purgeDatabase()
val dispatchList = repo.getAllDispatches()["dispatches"]
if (dispatchList != null && dispatchList.isEmpty()) {
createExampleDispatches(0).forEach {
repo.createDispatch(it)
println("######## date: ${it.date} ################")
}
}
routing {
get("/dispatches/") {
call.response.status(HttpStatusCode.OK)
val dispatches = repo.getAllDispatches()
println("Dispatches from Mongo: $dispatches")
println("Dispatches encoded for response: $dispatches")
}
}
}
And finally the class being serialized:
package com.reticentmonolith.models
import com.reticentmonolith.serializers.DateSerializer
import com.reticentmonolith.serializers.TimeSerializer
import kotlinx.serialization.Contextual
import org.litote.kmongo.*
import kotlinx.serialization.Serializable
import java.time.LocalDate
import java.time.LocalTime
#Serializable
data class Dispatch(
var riders: MutableMap<Int, #Contextual Rider?> = mutableMapOf(
1 to null,
2 to null,
3 to null,
4 to null
),
var comment: String = "",
var wind_degrees: Int,
var wind_speed: Double,
var winds_instructor: String,
var bt_radio: String,
var _id: #Contextual Id<Dispatch> = newId(),
// #Serializable(with=DateSerializer::class)
#Contextual var date: LocalDate = LocalDate.now(),
// #Serializable(with=TimeSerializer::class)
#Contextual var time: LocalTime = LocalTime.now()
)
fun createExampleDispatches(amount: Int): Collection<Dispatch> {
val dispatches = mutableListOf<Dispatch>()
for (i in 0..amount) {
dispatches.add(Dispatch(
bt_radio = "BT Instructor",
winds_instructor = "Winds Instructor",
wind_speed = 20.5,
wind_degrees = 186
).apply {
this.riders[2] = Rider(67, front_slider = Slider.BLACK, trolley = 125)
this.riders[3] = Rider(112, front_slider = Slider.NEW_RED, rear_slider = Slider.YELLOW, trolley = 34)
})
}
return dispatches
}
Any help would be hugely appreciated :) Thanks!

Squareup Retrofit/Moshi example process not shutting down

I have a working "hello world" App for Squareup's Retrofit2 http client (https://square.github.io/retrofit/) using JSON unmarschalling with Moshi (https://github.com/square/moshi) ...
Everything works fine, but without the System.exit(0) the App will run forever.
Any idea why this is??
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Path
fun main() {
MainApp().doIt()
println("main READY!")
System.exit(0)
}
interface GitHubService {
#GET("users/{user}/repos")
fun listRepos(#Path("user") user: String): Call<List<Repo>>
}
#JsonClass(generateAdapter = true)
data class Repo(val full_name: String, val private: Boolean)
class MainApp {
fun doIt() {
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
val retrofitGithub = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val githubService = retrofitGithub.create(GitHubService::class.java)
val reposCall: Call<List<Repo>> = githubService.listRepos("HoffiMuc")
val response = reposCall.execute()
if (response.isSuccessful) {
val repos = response.body() ?: ArrayList<Repo>()
if ( repos.isNotEmpty() ) {
for (repo in repos) {
println(repo)
}
} else {
println("Response Body is null or no repo in result list")
}
} else {
println(response.headers())
}
}
}
What happens if you add an explicit dependency on the latest release of OkHttp? The old version Retrofit depends on used non-daemon threads.

Compile-time value preprocessed into Kotlin code from Gradle

I am trying to get some values from a Gradle build into some Kotlin code.
For example, if my code was:
const val compileDate = THE_DATE_THAT_THE_CODE_WAS_COMPILED_ON;
const val compileBranch = THE_GIT_BRANCH_THAT_THE_CODE_WAS_COMPILED_FROM;
fun main() {
println("$compileDate - $compileBranch")
}
Is there any way to get those constants embedded into the code from the build itself?
If its not possible in that manner, I would think it might be possible using annotations, but again - I don't know how.
Any help would be appreciated.
Thank you.
Using annotations
So I found a solution by writing an annotation processor. Here's a fairly minimal working example:
./annotations/src/main/kotlin/net/example/CompileTimeValue.kt:
package net.example
#Retention(AnnotationRetention.SOURCE)
#MustBeDocumented
annotation class CompileTimeValue(val of : String)
./annotations/build.gradle.kts:
plugins {
kotlin("jvm")
}
./processor/src/main/kotlin/net/example/CompileTimeEvaluator.kt:
package net.example
import com.google.auto.service.AutoService
import java.io.File
import java.io.IOException
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
#AutoService(Processor::class)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedOptions(CompileTimeEvaluator.KAPT_OPTION)
class CompileTimeEvaluator : AbstractProcessor() {
private val assignments = mutableMapOf<Element, String?>()
override fun getSupportedAnnotationTypes(): MutableSet<String> =
mutableSetOf(CompileTimeValue::class.java.name)
override fun getSupportedSourceVersion(): SourceVersion =
SourceVersion.latest()
override fun process(set: MutableSet<out TypeElement>?, env: RoundEnvironment?): Boolean {
env?.getElementsAnnotatedWith(CompileTimeValue::class.java)?.forEach {
val note = it.getAnnotation(CompileTimeValue::class.java) as CompileTimeValue
assignments[it] = outputOfCommand(note)
}
val kaptGeneratedDir = processingEnv.options[KAPT_OPTION]
val file = File(kaptGeneratedDir, "ComputedConstants.kt")
file.writeText(generatedClass())
return true
}
private fun generatedClass() : String = """
object ComputedConstants {
fun load() {
${loadAssignments()}
}
}
""".trimIndent()
private fun loadAssignments() : String =
assignments.entries.joinToString("\n") {
val element = it.key
val value = it.value
val elementName = element.simpleName.toString()
// TODO: I think a recursive solution to find the true parent class
// is necessary here to deal with nested classes
val elementNest = element.enclosingElement.simpleName.toString()
val elementPack = processingEnv.elementUtils.getPackageOf(element)
val fullName = "$elementPack.$elementNest.$elementName"
if (value != null)
"$fullName = \"\"\"$value\"\"\""
else
"$fullName = null"
}
private fun outputOfCommand(note : CompileTimeValue) : String? = try {
val process = ProcessBuilder(*note.of.split(' ').toTypedArray())
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
process.waitFor()
process.inputStream.bufferedReader().readText().trimEnd('\n')
} catch (e : IOException) {
null
}
companion object {
const val KAPT_OPTION = "kapt.kotlin.generated"
}
}
./processor/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
}
kapt {
correctErrorTypes = true
}
dependencies {
"kapt"(project(":annotations"))
compileOnly(project(":annotations"))
implementation(fileTree("libs").include("*.jar"))
implementation("com.google.auto.service:auto-service:1.0-rc6")
annotationProcessor("com.google.auto.service:auto-service:1.0-rc6")
}
./example/src/main/kotlin/net/example/Example.kt:
package net.example
object Constants {
#field:CompileTimeValue("date")
var compileDate : String? = "Unknown"
}
fun main() {
ComputedConstants.load()
println("Compiled on: ${Constants.compileDate}")
}
./example/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
application
}
application {
mainClass.set("net.example.ExampleKt")
}
dependencies {
implementation(project(":annotations"))
"kapt"(project(":annotations"))
"kapt"(project(":processor"))
}
./example/build/generated/source/kaptKotlin/main/ComputedConstants.kt:
object ComputedConstants {
fun load() {
net.example.Constants.compileDate = """Sat 27 Mar 2021 01:30:46 PM EDT"""
}
}
./build.gradle.kts:
plugins {
// apply(false) so that it can be re-used in children
kotlin("jvm") version "1.4.31" apply(false)
kotlin("kapt") version "1.4.31" apply(false)
}
allprojects {
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
}
./settings.gradle.kts:
include(":example")
include(":annotations")
include(":processor")
Another quick way is to use the gradle copy task to inflate a kotlin source template.
Create a kotlin template file App.kt some where in the source tree (src/main/templates/App.kt)
object App {
const val VERSION = "$version"
const val BASE_VERSION = "$base_version"
}
Create a copy task in your build file
val copyTemplates by registering(Copy::class) {
filteringCharset = "UTF-8"
from(project.projectDir.resolve("src/main/templates"))
into(project.buildDir.resolve("generated-sources/templates/kotlin/main"))
}
Add the generated template to the main sourceset
sourceSets {
main {
java.srcDirs(copyTemplates)
}
}
Now you can access App as regular kotlin object from your classes
fun main() {
println("Version is : ${App.VERSION}")
}