diff --git a/build.gradle.kts b/build.gradle.kts index 4b1e9d8..bb8d878 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 { diff --git a/buildPlugin/build.gradle.kts b/buildPlugin/build.gradle.kts index 4d28913..a22b31f 100644 --- a/buildPlugin/build.gradle.kts +++ b/buildPlugin/build.gradle.kts @@ -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")) @@ -77,6 +78,7 @@ configure { tasks.withType().configureEach { kotlinOptions.jvmTarget = "1.8" + kotlinOptions.freeCompilerArgs += listOf("-Xopt-in=kotlin.RequiresOptIn") } extensions.getByType(SpotlessExtension::class).apply { diff --git a/buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/SqliteCompilation.kt b/buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/SqliteCompilation.kt index 7ae3fb6..7ae4839 100644 --- a/buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/SqliteCompilation.kt +++ b/buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/SqliteCompilation.kt @@ -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 @@ -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") @@ -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 @@ -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) } } diff --git a/gradle.properties b/gradle.properties index 99330a9..d3caf59 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4ce5a23..7b21978 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/jnigenerator/src/main/kotlin/com/birbit/jnigen/JniWriter.kt b/jnigenerator/src/main/kotlin/com/birbit/jnigen/JniWriter.kt index f82edb6..427ad2c 100644 --- a/jnigenerator/src/main/kotlin/com/birbit/jnigen/JniWriter.kt +++ b/jnigenerator/src/main/kotlin/com/birbit/jnigen/JniWriter.kt @@ -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) diff --git a/jnigenerator/src/main/kotlin/com/birbit/jnigen/Main.kt b/jnigenerator/src/main/kotlin/com/birbit/jnigen/Main.kt index 29dc2f7..ee47e52 100644 --- a/jnigenerator/src/main/kotlin/com/birbit/jnigen/Main.kt +++ b/jnigenerator/src/main/kotlin/com/birbit/jnigen/Main.kt @@ -16,7 +16,9 @@ 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 /** @@ -24,14 +26,26 @@ import org.jetbrains.kotlin.spec.grammar.tools.parseKotlinCode */ 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?") @@ -53,3 +67,64 @@ fun main() { .replace("\$YEAR", Calendar.getInstance().get(Calendar.YEAR).toString()) JniWriter(copyright, pairs).write(targetFile) } + +fun findMethodNamesFromClassFile(folder: String): List { + 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" + } +} diff --git a/jnigenerator/src/main/kotlin/com/birbit/jnigen/ParseTreeExt.kt b/jnigenerator/src/main/kotlin/com/birbit/jnigen/ParseTreeExt.kt index 192bc32..0548ead 100644 --- a/jnigenerator/src/main/kotlin/com/birbit/jnigen/ParseTreeExt.kt +++ b/jnigenerator/src/main/kotlin/com/birbit/jnigen/ParseTreeExt.kt @@ -71,6 +71,17 @@ fun KotlinParseTree.objectDeclarations() = asSequence().filter { ObjectDeclaration(it) } +fun KotlinParseTree.skipFindPath(target: String, sections: List): List { + 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): List { if (sections.isEmpty()) { return listOf(this) @@ -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" } } diff --git a/sqlitebindings-api/build.gradle.kts b/sqlitebindings-api/build.gradle.kts index af186af..21a17c3 100644 --- a/sqlitebindings-api/build.gradle.kts +++ b/sqlitebindings-api/build.gradle.kts @@ -33,5 +33,8 @@ kotlin { implementation(kotlin("stdlib")) } } + all { + languageSettings.enableLanguageFeature("InlineClasses") + } } } diff --git a/sqlitebindings/src/commonJvmMain/kotlin/com/birbit/sqlite3/internal/JvmCommonSqliteApi.kt b/sqlitebindings/src/commonJvmMain/kotlin/com/birbit/sqlite3/internal/JvmCommonSqliteApi.kt index 60e32cc..45da629 100644 --- a/sqlitebindings/src/commonJvmMain/kotlin/com/birbit/sqlite3/internal/JvmCommonSqliteApi.kt +++ b/sqlitebindings/src/commonJvmMain/kotlin/com/birbit/sqlite3/internal/JvmCommonSqliteApi.kt @@ -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 @@ -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() } @@ -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? { @@ -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? { @@ -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) } @@ -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, @@ -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, @@ -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, @@ -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) diff --git a/sqlitebindings/src/nativeMain/kotlin/com/birbit/sqlite3/internal/JniEnv.kt b/sqlitebindings/src/nativeMain/kotlin/com/birbit/sqlite3/internal/JniEnv.kt index 160f848..c033f27 100644 --- a/sqlitebindings/src/nativeMain/kotlin/com/birbit/sqlite3/internal/JniEnv.kt +++ b/sqlitebindings/src/nativeMain/kotlin/com/birbit/sqlite3/internal/JniEnv.kt @@ -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( @@ -192,7 +192,6 @@ internal class JvmAuthorizerCallback private constructor( target: jobject ) { val disposeMethod = env.nativeInterface().CallVoidMethod!!.reinterpret() - ?: error("cannot find dispose method on authorizer") disposeMethod.invoke(env, target, _instance.value().disposeMethodId) }