Skip to content

watchOS support #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
matrix:
include:
- os: macos-latest
targets: iosSimulatorArm64Test macosArm64Test jvmTest
targets: iosSimulatorArm64Test macosArm64Test watchosSimulatorArm64Test jvmTest
- os: ubuntu-latest
targets: testDebugUnitTest testReleaseUnitTest jvmTest lintKotlin
- os: windows-latest
Expand Down
4 changes: 4 additions & 0 deletions PowerSyncKotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ kotlin {
iosSimulatorArm64(),
macosArm64(),
macosX64(),
watchosDeviceArm64(),
watchosArm64(),
watchosSimulatorArm64(),
watchosX64(),
).forEach {
it.binaries.framework {
baseName = "PowerSyncKotlin"
Expand Down
3 changes: 2 additions & 1 deletion connectors/supabase/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ plugins {
}

kotlin {
powersyncTargets()
// The Supabase KMP project does not support arm64 watchOS builds
powersyncTargets(watchOS = false)
targets.withType<KotlinNativeTarget> {
compilations.named("main") {
compileTaskProvider {
Expand Down
17 changes: 8 additions & 9 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.gradle.internal.os.OperatingSystem
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest
import org.jetbrains.kotlin.gradle.tasks.KotlinTest
import org.jetbrains.kotlin.konan.target.Family


plugins {
Expand Down Expand Up @@ -133,18 +134,16 @@ kotlin {
compileTaskProvider {
compilerOptions.freeCompilerArgs.add("-Xexport-kdoc")
}
}

/*
If we ever need macOS support:
{
binaries.withType<TestExecutable>().configureEach {
linkTaskProvider.dependsOn(downloadPowersyncDesktopBinaries)
linkerOpts("-lpowersync")
linkerOpts("-L", binariesFolder.map { it.dir("powersync") }.get().asFile.path)
if (this.target.konanTarget.family == Family.WATCHOS) {
// We're linking the core extension statically, which means that we need a cinterop
// to call powersync_init_static
cinterops.create("powersync_static") {
packageName("com.powersync.static")
headers(file("src/watchosMain/powersync_static.h"))
}
}
}
*/
}

explicitApi()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import co.touchlab.sqliter.sqlite3.sqlite3_enable_load_extension
import co.touchlab.sqliter.sqlite3.sqlite3_load_extension
import co.touchlab.sqliter.sqlite3.sqlite3_rollback_hook
import co.touchlab.sqliter.sqlite3.sqlite3_update_hook
import com.powersync.DatabaseDriverFactory.Companion.powerSyncExtensionPath
import com.powersync.db.internal.InternalSchema
import com.powersync.persistence.driver.NativeSqliteDriver
import com.powersync.persistence.driver.wrapConnection
Expand All @@ -22,6 +23,7 @@ import kotlinx.cinterop.MemScope
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.alloc
import kotlinx.cinterop.asStableRef
import kotlinx.cinterop.free
import kotlinx.cinterop.nativeHeap
import kotlinx.cinterop.ptr
import kotlinx.cinterop.staticCFunction
Expand Down Expand Up @@ -132,38 +134,9 @@ public actual class DatabaseDriverFactory {
connection: DatabaseConnection,
driver: DeferredDriver,
) {
val ptr = connection.getDbPointer().getPointer(MemScope())
val extensionPath = powerSyncExtensionPath

// Enable extension loading
// We don't disable this after the fact, this should allow users to load their own extensions
// in future.
val enableResult = sqlite3_enable_load_extension(ptr, 1)
if (enableResult != SqliteErrorType.SQLITE_OK.code) {
throw PowerSyncException(
"Could not dynamically load the PowerSync SQLite core extension",
cause =
Exception(
"Call to sqlite3_enable_load_extension failed",
),
)
}

// A place to store a potential error message response
val errMsg = nativeHeap.alloc<CPointerVar<ByteVar>>()
val result =
sqlite3_load_extension(ptr, extensionPath, "sqlite3_powersync_init", errMsg.ptr)
if (result != SqliteErrorType.SQLITE_OK.code) {
val errorMessage = errMsg.value?.toKString() ?: "Unknown error"
throw PowerSyncException(
"Could not load the PowerSync SQLite core extension",
cause =
Exception(
"Calling sqlite3_load_extension failed with error: $errorMessage",
),
)
}
connection.loadPowerSyncSqliteCoreExtension()

val ptr = connection.getDbPointer().getPointer(MemScope())
val driverRef = StableRef.create(driver)

sqlite3_update_hook(
Expand Down Expand Up @@ -221,3 +194,41 @@ public actual class DatabaseDriverFactory {
}
}
}

internal fun DatabaseConnection.loadPowerSyncSqliteCoreExtensionDynamically() {
val ptr = getDbPointer().getPointer(MemScope())
val extensionPath = powerSyncExtensionPath

// Enable extension loading
// We don't disable this after the fact, this should allow users to load their own extensions
// in future.
val enableResult = sqlite3_enable_load_extension(ptr, 1)
if (enableResult != SqliteErrorType.SQLITE_OK.code) {
throw PowerSyncException(
"Could not dynamically load the PowerSync SQLite core extension",
cause =
Exception(
"Call to sqlite3_enable_load_extension failed",
),
)
}

// A place to store a potential error message response
val errMsg = nativeHeap.alloc<CPointerVar<ByteVar>>()
val result =
sqlite3_load_extension(ptr, extensionPath, "sqlite3_powersync_init", errMsg.ptr)
val resultingError = errMsg.value
nativeHeap.free(errMsg)
if (result != SqliteErrorType.SQLITE_OK.code) {
val errorMessage = resultingError?.toKString() ?: "Unknown error"
throw PowerSyncException(
"Could not load the PowerSync SQLite core extension",
cause =
Exception(
"Calling sqlite3_load_extension failed with error: $errorMessage",
),
)
}
}

internal expect fun DatabaseConnection.loadPowerSyncSqliteCoreExtension()
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.powersync

import kotlin.experimental.ExperimentalNativeApi
import kotlin.test.Test

class DatabaseDriverFactoryTest {
@OptIn(ExperimentalNativeApi::class)
@Test
fun findsPowerSyncFramework() {
DatabaseDriverFactory.powerSyncExtensionPath
if (Platform.osFamily != OsFamily.WATCHOS) {
// On watchOS targets, there's no special extension path because we expect to link the
// PowerSync extension statically due to platform restrictions.
DatabaseDriverFactory.powerSyncExtensionPath
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.powersync

import co.touchlab.sqliter.DatabaseConnection

internal actual fun DatabaseConnection.loadPowerSyncSqliteCoreExtension() {
loadPowerSyncSqliteCoreExtensionDynamically()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.powersync

import co.touchlab.sqliter.DatabaseConnection

internal actual fun DatabaseConnection.loadPowerSyncSqliteCoreExtension() {
loadPowerSyncSqliteCoreExtensionDynamically()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.powersync

import co.touchlab.sqliter.DatabaseConnection
import com.powersync.static.powersync_init_static

internal actual fun DatabaseConnection.loadPowerSyncSqliteCoreExtension() {
val rc = powersync_init_static()
if (rc != 0) {
throw PowerSyncException(
"Could not load the PowerSync SQLite core extension",
cause =
Exception(
"Calling powersync_init_static returned result code $rc",
),
)
}
}
1 change: 1 addition & 0 deletions core/src/watchosMain/powersync_static.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int powersync_init_static();
53 changes: 20 additions & 33 deletions plugins/build-plugin/src/main/kotlin/SharedBuildPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,42 +57,29 @@ class SharedBuildPlugin : Plugin<Project> {
.targets
.withType<KotlinNativeTarget>()
.configureEach {
if (konanTarget.family == Family.IOS &&
konanTarget.name.contains(
"simulator",
)
) {
binaries
.withType<org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable>()
.configureEach {
linkTaskProvider.configure { dependsOn(unzipPowersyncFramework) }
linkerOpts("-framework", "powersync-sqlite-core")
val abiName = when(konanTarget.family) {
Family.OSX -> "macos-arm64_x86_64"
// We're testing on simulators
Family.IOS -> "ios-arm64_x86_64-simulator"
Family.WATCHOS -> "watchos-arm64_x86_64-simulator"
else -> return@configureEach
}

val frameworkRoot =
binariesFolder
.map { it.dir("framework/extracted/powersync-sqlite-core.xcframework/ios-arm64_x86_64-simulator") }
.get()
.asFile.path
binaries
.withType<org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable>()
.configureEach {
linkTaskProvider.configure { dependsOn(unzipPowersyncFramework) }
linkerOpts("-framework", "powersync-sqlite-core")

linkerOpts("-F", frameworkRoot)
linkerOpts("-rpath", frameworkRoot)
}
} else if (konanTarget.family == Family.OSX) {
binaries
.withType<org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable>()
.configureEach {
linkTaskProvider.configure { dependsOn("unzipPowersyncFramework") }
linkerOpts("-framework", "powersync-sqlite-core")
var frameworkRoot =
binariesFolder
.map { it.dir("framework/extracted/powersync-sqlite-core.xcframework/macos-arm64_x86_64") }
.get()
.asFile.path
val frameworkRoot =
binariesFolder
.map { it.dir("framework/extracted/powersync-sqlite-core.xcframework/$abiName") }
.get()
.asFile.path

linkerOpts("-F", frameworkRoot)
linkerOpts("-rpath", frameworkRoot)
}
}
linkerOpts("-F", frameworkRoot)
linkerOpts("-rpath", frameworkRoot)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public fun KotlinTargetContainerWithPresetFunctions.powersyncTargets(
native: Boolean = true,
jvm: Boolean = true,
includeTargetsWithoutComposeSupport: Boolean = true,
watchOS: Boolean = true,
) {
if (jvm) {
androidTarget {
Expand Down Expand Up @@ -37,6 +38,14 @@ public fun KotlinTargetContainerWithPresetFunctions.powersyncTargets(
if (includeTargetsWithoutComposeSupport) {
macosX64()
macosArm64()

if (watchOS) {
watchosDeviceArm64() // aarch64-apple-watchos
watchosArm64() // arm64_32-apple-watchos

watchosSimulatorArm64() // aarch64-apple-watchos-simulator
watchosX64() // x86_64-apple-watchos-simulator
}
}
}
}
Loading