Skip to content

Commit

Permalink
Kotlin 1.4 (#35)
Browse files Browse the repository at this point in the history
updated to kotlin 1.4 and latest gradle
added jvm name annotations for sqlite api to avoid name mangling in native methods
which cannot be iplemented in C.

updated jnigenerator to parse properly when annotations are present.
  • Loading branch information
yigit authored Sep 10, 2020
1 parent 99532b3 commit 51db0f3
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 14 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import com.diffplug.gradle.spotless.SpotlessExtension
plugins {
id("com.diffplug.gradle.spotless")
id("org.jlleitschuh.gradle.ktlint")
kotlin("multiplatform")
kotlin("multiplatform") apply false
}

buildscript {
Expand Down
2 changes: 2 additions & 0 deletions buildPlugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ dependencies {
implementation(gradleApi())
implementation(gradleKotlinDsl())
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-native-utils:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin-api:$kotlinVersion")
implementation(kotlin("stdlib-jdk8"))
Expand All @@ -77,6 +78,7 @@ configure<GradlePluginDevelopmentExtension> {

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs += listOf("-Xopt-in=kotlin.RequiresOptIn")
}

extensions.getByType(SpotlessExtension::class).apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.birbit.ksqlite.build.internal
import com.birbit.ksqlite.build.CreateDefFileWithLibraryPathTask
import com.birbit.ksqlite.build.SqliteCompilationConfig
import java.io.File
import java.util.Locale
import java.util.concurrent.Callable
import org.gradle.api.Project
import org.gradle.api.Task
Expand All @@ -28,6 +29,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.konan.target.presetName

@OptIn(kotlin.ExperimentalStdlibApi::class)
internal object SqliteCompilation {
fun setup(project: Project, config: SqliteCompilationConfig) {
val buildFolder = project.buildDir.resolve("sqlite-compilation")
Expand Down Expand Up @@ -122,7 +124,7 @@ internal object SqliteCompilation {
val original = it.defFile
val newDefFile = generatedDefFileFolder.resolve("${konanTarget.presetName}/sqlite-generated.def")
val createDefFileTask = project.tasks.register(
"createDefFileForSqlite${konanTarget.presetName.capitalize()}",
"createDefFileForSqlite${konanTarget.presetName.capitalize(Locale.US)}",
CreateDefFileWithLibraryPathTask::class.java
) { task ->
task.original = original
Expand All @@ -133,9 +135,6 @@ internal object SqliteCompilation {
it.defFile = newDefFile
cInteropTask.dependsOn(createDefFileTask)
}
// workaround for https://youtrack.jetbrains.com/issue/KT-39396
it.compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary",
staticLibFile.absolutePath)
}
}

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ android.enableJetifier=true
spotlessVersion=4.0.1
agpVersion=3.6.3
ktlintVersion=9.2.1
kotlinVersion=1.4-M2
kotlinVersion=1.4.0

2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-milestone-1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class JniWriter(
}
}.build()
val output = StringBuilder()
output.appendln(copyright)
output.appendLine(copyright)
spec.writeTo(output)
println("will output to ${file.absolutePath}")
file.writeText(output.toString(), Charsets.UTF_8)
Expand Down
79 changes: 77 additions & 2 deletions jnigenerator/src/main/kotlin/com/birbit/jnigen/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,36 @@
package com.birbit.jnigen

import java.io.File
import java.io.IOException
import java.util.Calendar
import java.util.concurrent.TimeUnit
import org.jetbrains.kotlin.spec.grammar.tools.parseKotlinCode

/**
* TODO: should we make this part of the build?
*/
fun main() {
val srcFile = File("./sqlitebindings/src/commonJvmMain/kotlin/com/birbit/sqlite3/internal/JvmCommonSqliteApi.kt")
val targetFile = File("./sqlitebindings/src/nativeMain/com/birbit/sqlite3/internal/GeneratedJni.kt")
val targetFile = File("./sqlitebindings/src/nativeMain/kotlin/com/birbit/sqlite3/internal/GeneratedJni.kt")
val jvmMethodNames = findMethodNamesFromClassFile("./sqlitebindings/build")
val invalidMethods = jvmMethodNames.filter {
it.suffix.isNotBlank() && it.originalName.startsWith("native")
}
if (invalidMethods.isNotEmpty()) {
val methodNames = invalidMethods.joinToString("\n") {
it.fullName
}
error("""These methods cannot be called from native: $methodNames""")
}
println("hello ${File(".").absolutePath}")
val tokens = parseKotlinCode(srcFile.readText(Charsets.UTF_8))
val sqliteApiObject = tokens.objectDeclarations().first {
it.name == "SqliteApi"
}

val methods = sqliteApiObject.functions.groupBy {
val methods = sqliteApiObject.functions.filterNot {
false && it.name in listOf("Suppress", "JvmName")
}.groupBy {
it.external
}
val externalMethods = methods[true] ?: error("no external method?")
Expand All @@ -53,3 +67,64 @@ fun main() {
.replace("\$YEAR", Calendar.getInstance().get(Calendar.YEAR).toString())
JniWriter(copyright, pairs).write(targetFile)
}

fun findMethodNamesFromClassFile(folder: String): List<JvmClassName> {
val javaHome = System.getenv("JAVA_HOME") ?: error("cannot find java home")
val javap = File(javaHome, "bin/javap")
val classFile = File("$folder/classes/kotlin/jvm/main/com/birbit/sqlite3/internal/SqliteApi.class")
if (!classFile.exists()) {
throw IllegalStateException("compile the project first, we need to validate mangled names")
}
val cmd = javap.absolutePath + " -s $classFile"
val output = cmd.runCommand(File(".")) ?: "could not run javap"
return output.lines().mapNotNull {
it.parseJavapMethodName()
}
}

fun String.runCommand(workingDir: File): String? {
try {
val parts = this.split("\\s".toRegex())
val proc = ProcessBuilder(*parts.toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()

proc.waitFor(60, TimeUnit.MINUTES)
return proc.inputStream.bufferedReader().readText()
} catch (e: IOException) {
e.printStackTrace()
return null
}
}

fun String.parseJavapMethodName(): JvmClassName? {
val openParan = indexOf('(')
if (openParan < 0) return null
val firstSpace = substring(0, openParan).lastIndexOf(" ")
val methodName = substring(firstSpace + 1, openParan).let {
if (it.isBlank()) null
else it
} ?: return null
// native methods gets a `-` in their name, cleanup
return if (methodName.contains("-")) {
methodName.split("-").let {
JvmClassName(it[0], it[1])
}
} else {
JvmClassName(methodName, "")
}
}

data class JvmClassName(
val originalName: String,
val suffix: String
) {
val fullName: String
get() = if (suffix.isBlank()) {
originalName
} else {
"$originalName-$suffix"
}
}
15 changes: 13 additions & 2 deletions jnigenerator/src/main/kotlin/com/birbit/jnigen/ParseTreeExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ fun KotlinParseTree.objectDeclarations() = asSequence().filter {
ObjectDeclaration(it)
}

fun KotlinParseTree.skipFindPath(target: String, sections: List<String>): List<KotlinParseTree> {
val index = children.indexOfFirst {
it.name() == target
}
if (index < 0) {
error("cannot find $target")
}
return children.subList(index + 1, children.size).flatMap {
it.findPath(sections)
}
}
fun KotlinParseTree.findPath(sections: List<String>): List<KotlinParseTree> {
if (sections.isEmpty()) {
return listOf(this)
Expand Down Expand Up @@ -111,10 +122,10 @@ class FunctionDeclaration(
val parseTree: KotlinParseTree
) {
val name by lazy {
checkNotNull(parseTree.findPath(listOf(
checkNotNull(parseTree.skipFindPath(Declarations.FUN, listOf(
Declarations.SIMPLE_IDENTIFIER,
Declarations.IDENTIFIER
)).first().text()) {
)).firstOrNull()?.text()) {
"cannot find name for $parseTree"
}
}
Expand Down
3 changes: 3 additions & 0 deletions sqlitebindings-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ kotlin {
implementation(kotlin("stdlib"))
}
}
all {
languageSettings.enableLanguageFeature("InlineClasses")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.birbit.sqlite3.internal
import com.birbit.sqlite3.Authorizer
import com.birbit.sqlite3.ColumnType
import com.birbit.sqlite3.ResultCode
import kotlin.jvm.JvmName

open class JvmObjRef(
ptr: Long
Expand All @@ -36,7 +37,9 @@ actual class DbRef(ptr: Long) : JvmObjRef(ptr), ObjRef

actual class StmtRef(actual val dbRef: DbRef, ptr: Long) : JvmObjRef(ptr), ObjRef

@Suppress("INAPPLICABLE_JVM_NAME")
actual object SqliteApi {
// for all native jvm name methods, see: https://youtrack.jetbrains.com/issue/KT-28135
init {
loadNativeLibrary()
}
Expand All @@ -60,6 +63,7 @@ actual object SqliteApi {
return nativeStep(stmtRef.ptr)
}

@JvmName("nativeStep")
external fun nativeStep(stmtPtr: Long): ResultCode

actual fun columnText(stmtRef: StmtRef, index: Int): String? {
Expand All @@ -82,17 +86,20 @@ actual object SqliteApi {
return nativeReset(stmtRef.ptr)
}

@JvmName("nativeReset")
external fun nativeReset(stmtPtr: Long): ResultCode
actual fun close(dbRef: DbRef): ResultCode {
return nativeClose(dbRef.ptr)
}

@JvmName("nativeClose")
external fun nativeClose(ptr: Long): ResultCode

actual fun finalize(stmtRef: StmtRef): ResultCode {
return nativeFinalize(stmtRef.ptr)
}

@JvmName("nativeFinalize")
external fun nativeFinalize(stmtPtr: Long): ResultCode

actual fun columnBlob(stmtRef: StmtRef, index: Int): ByteArray? {
Expand All @@ -115,29 +122,36 @@ actual object SqliteApi {
return nativeBindBlob(stmtRef.ptr, index, bytes)
}

@JvmName("nativeBindBlob")
external fun nativeBindBlob(stmtPtr: Long, index: Int, bytes: ByteArray): ResultCode

actual fun bindText(stmtRef: StmtRef, index: Int, value: String): ResultCode {
return nativeBindText(stmtRef.ptr, index, value)
}

@JvmName("nativeBindText")
external fun nativeBindText(stmtPtr: Long, index: Int, value: String): ResultCode
actual fun bindInt(stmtRef: StmtRef, index: Int, value: Int): ResultCode {
return nativeBindInt(stmtRef.ptr, index, value)
}

@JvmName("nativeBindInt")
external fun nativeBindInt(stmtPtr: Long, index: Int, value: Int): ResultCode

actual fun bindLong(stmtRef: StmtRef, index: Int, value: Long): ResultCode {
return nativeBindLong(stmtRef.ptr, index, value)
}

@JvmName("nativeBindLong")
external fun nativeBindLong(stmtPtr: Long, index: Int, value: Long): ResultCode

actual fun bindNull(stmtRef: StmtRef, index: Int): ResultCode {
return nativeBindNull(stmtRef.ptr, index)
}

@JvmName("nativeBindNull")
external fun nativeBindNull(stmtPtr: Long, index: Int): ResultCode

actual fun errorMsg(dbRef: DbRef): String? {
return nativeErrorMsg(dbRef.ptr)
}
Expand All @@ -148,17 +162,21 @@ actual object SqliteApi {
return nativeErrorCode(dbRef.ptr)
}

@JvmName("nativeErrorCode")
external fun nativeErrorCode(dbPtr: Long): ResultCode

actual fun errorString(code: ResultCode): String? {
return nativeErrorString(code)
}

@JvmName("nativeErrorString")
external fun nativeErrorString(code: ResultCode): String?

actual fun bindDouble(stmtRef: StmtRef, index: Int, value: Double): ResultCode {
return nativeBindDouble(stmtRef.ptr, index, value)
}

@JvmName("nativeBindDouble")
external fun nativeBindDouble(stmtPtr: Long, index: Int, value: Double): ResultCode
actual fun setAuthorizer(
dbRef: DbRef,
Expand All @@ -167,6 +185,7 @@ actual object SqliteApi {
return nativeSetAuthorizer(dbRef.ptr, authorizer)
}

@JvmName("nativeSetAuthorizer")
external fun nativeSetAuthorizer(dbPtr: Long, authorizer: Authorizer?): ResultCode
actual fun columnType(
stmtRef: StmtRef,
Expand All @@ -175,6 +194,7 @@ actual object SqliteApi {
return nativeColumnType(stmtRef.ptr, index)
}

@JvmName("nativeColumnType")
external fun nativeColumnType(stmtPtr: Long, index: Int): ColumnType
actual fun exec(
dbRef: DbRef,
Expand All @@ -183,6 +203,7 @@ actual object SqliteApi {
return nativeExec(dbRef.ptr, query)
}

@JvmName("nativeExec")
external fun nativeExec(dbPtr: Long, query: String): ResultCode
actual fun columnDeclType(stmtRef: StmtRef, index: Int): String? {
return nativeColumnDeclType(stmtRef.ptr, index)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ internal class JvmAuthorizerCallback private constructor(
invokeMethodId = getMethodId(
env,
classRef.jobject,
"invoke",
"invoke-bGr0_7Q",
"(Lcom/birbit/sqlite3/AuthorizationParams;)I"
),
disposeMethodId = getMethodId(
Expand Down Expand Up @@ -192,7 +192,6 @@ internal class JvmAuthorizerCallback private constructor(
target: jobject
) {
val disposeMethod = env.nativeInterface().CallVoidMethod!!.reinterpret<DisposeMethod>()
?: error("cannot find dispose method on authorizer")
disposeMethod.invoke(env, target, _instance.value().disposeMethodId)
}

Expand Down

0 comments on commit 51db0f3

Please sign in to comment.