Skip to content

Commit 216abca

Browse files
committed
Get sqlite3mc running on macOS JVM
1 parent f868350 commit 216abca

File tree

9 files changed

+77
-20
lines changed

9 files changed

+77
-20
lines changed

common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ kotlin {
137137
optIn("kotlinx.cinterop.ExperimentalForeignApi")
138138
optIn("kotlin.time.ExperimentalTime")
139139
optIn("kotlin.experimental.ExperimentalObjCRefinement")
140+
optIn("com.powersync.PowerSyncInternal")
140141
}
141142
}
142143

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.powersync
2+
3+
@RequiresOptIn(message = "This API should not be used outside of PowerSync SDK packages")
4+
@Retention(AnnotationRetention.BINARY)
5+
@Target(
6+
AnnotationTarget.CLASS,
7+
AnnotationTarget.FUNCTION,
8+
AnnotationTarget.CONSTRUCTOR,
9+
AnnotationTarget.PROPERTY,
10+
AnnotationTarget.VALUE_PARAMETER,
11+
)
12+
public annotation class PowerSyncInternal

common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ import com.powersync.db.runWrapped
55
@Throws(PowerSyncException::class)
66
public actual fun resolvePowerSyncLoadableExtensionPath(): String? = runWrapped { powersyncExtension }
77

8-
private val powersyncExtension: String by lazy { extractLib("powersync") }
8+
private val powersyncExtension: String by lazy { extractLib(BuildConfig::class,"powersync") }

common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package com.powersync
22

33
import java.io.File
44
import java.util.UUID
5+
import kotlin.reflect.KClass
56

6-
private class R
7-
8-
internal fun extractLib(fileName: String): String {
7+
@PowerSyncInternal
8+
public fun extractLib(reference: KClass<*>, fileName: String): String {
99
val os = System.getProperty("os.name").lowercase()
1010
val (prefix, extension) =
1111
when {
@@ -34,7 +34,7 @@ internal fun extractLib(fileName: String): String {
3434

3535
val resourcePath = "/$prefix${fileName}_$arch.$extension"
3636

37-
(R::class.java.getResourceAsStream(resourcePath) ?: error("Resource $resourcePath not found")).use { input ->
37+
(reference.java.getResourceAsStream(resourcePath) ?: error("Resource $resourcePath not found")).use { input ->
3838
file.outputStream().use { output -> input.copyTo(output) }
3939
}
4040

sqlite3multipleciphers/build.gradle.kts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ kotlin {
8989
optIn("kotlin.experimental.ExperimentalNativeApi")
9090
optIn("kotlinx.cinterop.ExperimentalForeignApi")
9191
optIn("kotlinx.cinterop.BetaInteropApi")
92+
optIn("com.powersync.PowerSyncInternal")
9293
}
9394
}
9495

@@ -159,27 +160,34 @@ tasks.withType<ExternalNativeBuildTask> {
159160
dependsOn(unzipSQLiteSources)
160161
}
161162

163+
tasks.named<ProcessResources>(kotlin.jvm().compilations["main"].processResourcesTaskName) {
164+
from("build/jni-build/")
165+
}
166+
162167
val xCodeInstallation = ClangCompile.resolveXcode(providers)
163168

164169
// Tasks to build the JNI shared library for multiple operating systems.
165170
// Since the JNI sources rarely change, we don't run these tasks on every build. Instead,
166171
// we'll publish these sources as one-off releases when needed, and then reference that URL.
167-
fun registerCompileMacOsHostTask(architecture: String): TaskProvider<Exec> {
168-
return tasks.register<Exec>("jniCompileMacos$architecture") {
172+
fun registerCompileMacOsHostTask(arm: Boolean): TaskProvider<Exec> {
173+
val architectureName = if (arm) { "aarch64" } else { "x64" }
174+
175+
return tasks.register<Exec>("jniCompileMacos$architectureName") {
169176
val xcode = Path(xCodeInstallation.get())
170177
val toolchain =
171178
xcode.resolve("Toolchains/XcodeDefault.xctoolchain/usr/bin").absolutePathString()
172179

173-
val outputDirectory = layout.buildDirectory.dir("jni-build/macos")
174-
val outputFile = outputDirectory.map { it.file("libsqlite3mc_jni.$architecture.dylib") }
180+
val outputDirectory = layout.buildDirectory.dir("jni-build")
181+
val outputFile = outputDirectory.map {
182+
it.file("libsqlite3mc_jni_$architectureName.macos.dylib")
183+
}
175184
outputs.file(outputFile)
176185

177186
dependsOn(unzipSQLiteSources)
178187
val sqlite3McSources = unzipSQLiteSources.map { it.destinationDir }
179188
inputs.dir(sqlite3McSources)
180189

181-
val headers = layout.projectDirectory.dir("src/jni/headers/inc_mac")
182-
inputs.dir(headers)
190+
inputs.dir(layout.projectDirectory.dir("src/jni/"))
183191

184192
doFirst {
185193
outputDirectory.get().asFile.mkdirs()
@@ -190,23 +198,23 @@ fun registerCompileMacOsHostTask(architecture: String): TaskProvider<Exec> {
190198
"-B$toolchain",
191199
"-dynamiclib",
192200
"-fPIC",
193-
"--target=${architecture}-apple-macos",
201+
if (arm) "--target=aarch64-apple-macos" else "--target=x86_64-apple-macos",
194202
"-o",
195203
outputFile.get().asFile.path,
196204
"src/jni/sqlite_bindings.cpp",
197205
File(sqlite3McSources.get(), "sqlite3mc_amalgamation.c").path,
198206
"-I",
199207
sqlite3McSources.get().path,
200208
"-I",
201-
headers.asFile.path,
209+
"src/jni/headers/inc_mac",
202210
"-O3",
203211
*ClangCompile.sqlite3ClangOptions,
204212
)
205213
}
206214
}
207215

208-
val macosArm64 = registerCompileMacOsHostTask("aarch64")
209-
val macosX64 = registerCompileMacOsHostTask("x86_64")
216+
val macosArm64 = registerCompileMacOsHostTask(true)
217+
val macosX64 = registerCompileMacOsHostTask(false)
210218

211219
tasks.register("jniCompile") {
212220
dependsOn(macosArm64)

sqlite3multipleciphers/src/jni/sqlite_bindings.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,17 +421,17 @@ jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
421421
}
422422

423423
const int driverMethodCount = sizeof(sDriverMethods) / sizeof(sDriverMethods[0]);
424-
if (register_methods(env, "androidx/sqlite/driver/bundled/BundledSQLiteDriverKt",
424+
if (register_methods(env, "com/powersync/encryption/BundledSQLiteDriverKt",
425425
sDriverMethods, driverMethodCount) != JNI_OK) {
426426
return JNI_ERR;
427427
}
428428
const int connectionMethodCount = sizeof(sConnectionMethods) / sizeof(sConnectionMethods[0]);
429-
if (register_methods(env, "androidx/sqlite/driver/bundled/BundledSQLiteConnectionKt",
429+
if (register_methods(env, "com/powersync/encryption/BundledSQLiteConnectionKt",
430430
sConnectionMethods, connectionMethodCount) != JNI_OK) {
431431
return JNI_ERR;
432432
}
433433
const int statementMethodCount = sizeof(sStatementMethods) / sizeof(sStatementMethods[0]);
434-
if (register_methods(env, "androidx/sqlite/driver/bundled/BundledSQLiteStatementKt",
434+
if (register_methods(env, "com/powersync/encryption/BundledSQLiteStatementKt",
435435
sStatementMethods, statementMethodCount) != JNI_OK) {
436436
return JNI_ERR;
437437
}

sqlite3multipleciphers/src/jvmAndroidMain/kotlin/com/powersync/encryption/BundledSQLiteDriver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import androidx.sqlite.SQLiteConnection
2020
import com.powersync.PersistentConnectionFactory
2121
import com.powersync.resolvePowerSyncLoadableExtensionPath
2222

23-
internal abstract class BundledSQLiteDriver(private val key: Key): PersistentConnectionFactory {
23+
public abstract class BundledSQLiteDriver internal constructor(private val key: Key): PersistentConnectionFactory {
2424
private fun open(fileName: String, flags: Int): SQLiteConnection {
2525
ensureJniLibraryLoaded()
2626

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
package com.powersync.encryption
22

3-
internal actual fun ensureJniLibraryLoaded() {
3+
import com.powersync.extractLib
4+
5+
public class JavaEncryptedDatabaseFactory(key: Key): BundledSQLiteDriver(key) {
6+
override fun resolveDefaultDatabasePath(dbFilename: String): String {
7+
return dbFilename
8+
}
9+
}
410

11+
private val didLoadLibrary by lazy {
12+
val path = extractLib(JavaEncryptedDatabaseFactory::class, "sqlite3mc_jni")
13+
System.load(path)
14+
}
15+
16+
internal actual fun ensureJniLibraryLoaded() {
17+
didLoadLibrary
518
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.powersync
2+
3+
import com.powersync.encryption.JavaEncryptedDatabaseFactory
4+
import com.powersync.encryption.Key
5+
import io.kotest.matchers.shouldBe
6+
import kotlin.test.Test
7+
import kotlin.use
8+
9+
class JvmSmokeTest {
10+
@Test
11+
fun linksSqlite3MultipleCiphers() {
12+
JavaEncryptedDatabaseFactory(key).openInMemoryConnection().use { db ->
13+
db.prepare("PRAGMA cipher").use { stmt ->
14+
stmt.step() shouldBe true
15+
stmt.getText(0) shouldBe "chacha20"
16+
}
17+
}
18+
}
19+
20+
private companion object Companion {
21+
val key = Key.Passphrase("test")
22+
}
23+
}

0 commit comments

Comments
 (0)