From 2de4e4bf5e4c7ad876d2863bb83f84cb6640cc2f Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 12:34:06 +0200 Subject: [PATCH 1/8] Refactor SQLite compilation tasks --- gradle.properties | 1 + .../com/powersync/compile/ClangCompile.kt | 92 +++++++++++++ .../powersync/compile/CreateSqliteCInterop.kt | 33 +++++ .../powersync/compile/CreateStaticLibrary.kt | 37 +++++ .../com/powersync/compile/UnzipSqlite.kt | 23 ++++ static-sqlite-driver/build.gradle.kts | 129 ++++-------------- 6 files changed, 213 insertions(+), 102 deletions(-) create mode 100644 plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt create mode 100644 plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt create mode 100644 plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateStaticLibrary.kt create mode 100644 plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt diff --git a/gradle.properties b/gradle.properties index 47dddbbe..bc4e490b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,7 @@ kotlin.code.style=official # Gradle org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.caching=true # Compose org.jetbrains.compose.experimental.uikit.enabled=true # Android diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt new file mode 100644 index 00000000..7251e51f --- /dev/null +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/ClangCompile.kt @@ -0,0 +1,92 @@ +package com.powersync.compile + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.jetbrains.kotlin.konan.target.KonanTarget +import javax.inject.Inject +import kotlin.io.path.name + +@CacheableTask +abstract class ClangCompile: DefaultTask() { + @get:InputFile + @get:PathSensitive(PathSensitivity.NONE) + abstract val inputFile: RegularFileProperty + + @get:Input + abstract val konanTarget: Property + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.NONE) + abstract val include: DirectoryProperty + + @get:OutputFile + abstract val objectFile: RegularFileProperty + + @get:Inject + protected abstract val providers: ProviderFactory + + @TaskAction + fun run() { + val target = requireNotNull(KonanTarget.predefinedTargets[konanTarget.get()]) + + val (llvmTarget, sysRoot) = when (target) { + KonanTarget.IOS_X64 -> "x86_64-apple-ios12.0-simulator" to IOS_SIMULATOR_SDK + KonanTarget.IOS_ARM64 -> "arm64-apple-ios12.0" to IOS_SDK + KonanTarget.IOS_SIMULATOR_ARM64 -> "arm64-apple-ios14.0-simulator" to IOS_SIMULATOR_SDK + KonanTarget.MACOS_ARM64 -> "aarch64-apple-macos" to MACOS_SDK + KonanTarget.MACOS_X64 -> "x86_64-apple-macos" to MACOS_SDK + KonanTarget.WATCHOS_DEVICE_ARM64 -> "aarch64-apple-watchos" to WATCHOS_SDK + KonanTarget.WATCHOS_ARM32 -> "armv7k-apple-watchos" to WATCHOS_SDK + KonanTarget.WATCHOS_ARM64 -> "arm64_32-apple-watchos" to WATCHOS_SDK + KonanTarget.WATCHOS_SIMULATOR_ARM64 -> "aarch64-apple-watchos-simulator" to WATCHOS_SIMULATOR_SDK + KonanTarget.WATCHOS_X64 -> "x86_64-apple-watchos-simulator" to WATCHOS_SIMULATOR_SDK + else -> error("Unexpected target $target") + } + + val output = objectFile.get() + + providers.exec { + executable = "clang" + args( + "-B/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin", + "-fno-stack-protector", + "-target", + llvmTarget, + "-isysroot", + sysRoot, + "-fPIC", + "--compile", + "-I${include.get().asFile.absolutePath}", + inputFile.get().asFile.absolutePath, + "-DHAVE_GETHOSTUUID=0", + "-DSQLITE_ENABLE_DBSTAT_VTAB", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_ENABLE_RTREE", + "-O3", + "-o", + output.asFile.toPath().name, + ) + + workingDir = output.asFile.parentFile + }.result.get() + } + + companion object { + const val WATCHOS_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk" + const val WATCHOS_SIMULATOR_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/" + const val IOS_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk" + const val IOS_SIMULATOR_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk" + const val MACOS_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/" + } +} diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt new file mode 100644 index 00000000..d4b19ad4 --- /dev/null +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt @@ -0,0 +1,33 @@ +package com.powersync.compile + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault + +@DisableCachingByDefault(because = "not worth caching") +abstract class CreateSqliteCInterop: DefaultTask() { + @get:InputFile + abstract val archiveFile: RegularFileProperty + + @get:OutputFile + abstract val definitionFile: RegularFileProperty + + @TaskAction + fun run() { + val archive = archiveFile.get().asFile + val parent = archive.parentFile + + definitionFile.get().asFile.writeText(""" + package = com.powersync.sqlite3 + + linkerOpts.linux_x64 = -lpthread -ldl + linkerOpts.macos_x64 = -lpthread -ldl + staticLibraries=${archive.name} + libraryPaths=${parent.relativeTo(project.layout.projectDirectory.asFile.canonicalFile)} + """.trimIndent(), + ) + } +} diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateStaticLibrary.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateStaticLibrary.kt new file mode 100644 index 00000000..58dfdba6 --- /dev/null +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateStaticLibrary.kt @@ -0,0 +1,37 @@ +package com.powersync.compile + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import javax.inject.Inject + +@CacheableTask +abstract class CreateStaticLibrary: DefaultTask() { + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val objects: ConfigurableFileCollection + + @get:OutputFile + abstract val staticLibrary: RegularFileProperty + + @get:Inject + abstract val providers: ProviderFactory + + @TaskAction + fun run() { + providers.exec { + executable = "ar" + args("rc", staticLibrary.get().asFile.absolutePath) + for (file in objects.files) { + args(file.absolutePath) + } + }.result.get() + } +} diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt new file mode 100644 index 00000000..30e52d8c --- /dev/null +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt @@ -0,0 +1,23 @@ +package com.powersync.compile + +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.OutputDirectory + +/** + * A cacheable [Copy] task providing typed providers for the emitted [sqlite3C] and [sqlite3H] + * files, making them easier to access in other tasks. + */ +@CacheableTask +abstract class UnzipSqlite: Copy() { + @get:OutputDirectory + abstract val destination: DirectoryProperty + + fun intoDirectory(dir: Provider) { + into(dir) + destination.set(dir) + } +} diff --git a/static-sqlite-driver/build.gradle.kts b/static-sqlite-driver/build.gradle.kts index 4a5b81c8..8b7bab1f 100644 --- a/static-sqlite-driver/build.gradle.kts +++ b/static-sqlite-driver/build.gradle.kts @@ -1,14 +1,13 @@ +import com.powersync.compile.ClangCompile +import com.powersync.compile.CreateSqliteCInterop +import com.powersync.compile.CreateStaticLibrary +import com.powersync.compile.UnzipSqlite import java.io.File -import java.io.FileInputStream import com.powersync.plugins.sonatype.setupGithubRepository import com.powersync.plugins.utils.powersyncTargets import de.undercouch.gradle.tasks.download.Download import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader -import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.target.HostManager -import org.jetbrains.kotlin.konan.target.KonanTarget -import org.jetbrains.kotlin.konan.target.PlatformManager plugins { alias(libs.plugins.kotlinMultiplatform) @@ -20,22 +19,17 @@ plugins { val sqliteVersion = "3490100" val sqliteReleaseYear = "2025" -val downloads = layout.buildDirectory.dir("downloads") -val sqliteSrcFolder = downloads.map { it.dir("sqlite3") } - val downloadSQLiteSources by tasks.registering(Download::class) { val zipFileName = "sqlite-amalgamation-$sqliteVersion.zip" src("https://www.sqlite.org/$sqliteReleaseYear/$zipFileName") - dest(downloads.map { it.file(zipFileName) }) + dest(layout.buildDirectory.dir("downloads").map { it.file(zipFileName) }) onlyIfNewer(true) overwrite(false) } -val unzipSQLiteSources by tasks.registering(Copy::class) { - dependsOn(downloadSQLiteSources) - +val unzipSQLiteSources by tasks.registering(UnzipSqlite::class) { from( - zipTree(downloadSQLiteSources.get().dest).matching { + zipTree(downloadSQLiteSources.map { it.outputs.files.singleFile }).matching { include("*/sqlite3.*") exclude { it.isDirectory @@ -45,7 +39,7 @@ val unzipSQLiteSources by tasks.registering(Copy::class) { } }, ) - into(sqliteSrcFolder) + intoDirectory(layout.buildDirectory.dir("downloads/sqlite3")) } // Obtain host and platform manager from Kotlin multiplatform plugin. They're supposed to be @@ -53,99 +47,33 @@ val unzipSQLiteSources by tasks.registering(Copy::class) { // use to compile SQLite for the platforms we need. val hostManager = HostManager() -fun compileSqlite(target: KotlinNativeTarget): TaskProvider { +fun compileSqlite(target: KotlinNativeTarget): TaskProvider { val name = target.targetName val outputDir = layout.buildDirectory.dir("c/$name") - val compileSqlite = tasks.register("${name}CompileSqlite") { - dependsOn(unzipSQLiteSources) - val targetDirectory = outputDir.get() - val sqliteSource = sqliteSrcFolder.map { it.file("sqlite3.c") }.get() - val output = targetDirectory.file("sqlite3.o") - - inputs.file(sqliteSource) - outputs.file(output) + val sqlite3Obj = outputDir.map { it.file("sqlite3.o") } + val archive = outputDir.map { it.file("libsqlite3.a") } - doFirst { - targetDirectory.asFile.mkdirs() - output.asFile.delete() - } - - doLast { - val (llvmTarget, sysRoot) = when (target.konanTarget) { - KonanTarget.IOS_X64 -> "x86_64-apple-ios12.0-simulator" to "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk" - KonanTarget.IOS_ARM64 -> "arm64-apple-ios12.0" to "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk" - KonanTarget.IOS_SIMULATOR_ARM64 -> "arm64-apple-ios14.0-simulator" to "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk" - KonanTarget.MACOS_ARM64 -> "aarch64-apple-macos" to "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/" - KonanTarget.MACOS_X64 -> "x86_64-apple-macos" to "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/" - else -> error("Unexpected target $target") - } + val compileSqlite = tasks.register("${name}CompileSqlite", ClangCompile::class) { + inputs.dir(unzipSQLiteSources.map { it.destination }) - providers.exec { - executable = "clang" - args( - "-B/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin", - "-fno-stack-protector", - "-target", - llvmTarget, - "-isysroot", - sysRoot, - "-fPIC", - "--compile", - "-I${sqliteSrcFolder.get().asFile.absolutePath}", - sqliteSource.asFile.absolutePath, - "-DHAVE_GETHOSTUUID=0", - "-DSQLITE_ENABLE_DBSTAT_VTAB", - "-DSQLITE_ENABLE_FTS5", - "-DSQLITE_ENABLE_RTREE", - "-O3", - "-o", - "sqlite3.o", - ) - - workingDir = targetDirectory.asFile - }.result.get() - } + inputFile.set(unzipSQLiteSources.flatMap { it.destination.file("sqlite3.c") }) + konanTarget.set(target.konanTarget.name) + include.set(unzipSQLiteSources.flatMap { it.destination }) + objectFile.set(sqlite3Obj) } - val createStaticLibrary = tasks.register("${name}ArchiveSqlite") { - dependsOn(compileSqlite) - val targetDirectory = outputDir.get() - inputs.file(targetDirectory.file("sqlite3.o")) - outputs.file(targetDirectory.file("libsqlite3.a")) - - doLast { - providers.exec { - executable = "ar" - args("rc", "libsqlite3.a", "sqlite3.o") - - workingDir = targetDirectory.asFile - }.result.get() - } + val createStaticLibrary = tasks.register("${name}ArchiveSqlite", CreateStaticLibrary::class) { + inputs.file(compileSqlite.map { it.objectFile }) + objects.from(sqlite3Obj) + staticLibrary.set(archive) } - val buildCInteropDef = tasks.register("${name}CinteropSqlite") { - dependsOn(createStaticLibrary) - - val archive = createStaticLibrary.get().outputs.files.singleFile - inputs.file(archive) - - val parent = archive.parentFile - val defFile = File(parent, "sqlite3.def") - outputs.file(defFile) - - doFirst { - defFile.writeText( - """ - package = com.powersync.sqlite3 - - linkerOpts.linux_x64 = -lpthread -ldl - linkerOpts.macos_x64 = -lpthread -ldl - staticLibraries=${archive.name} - libraryPaths=${parent.relativeTo(project.layout.projectDirectory.asFile.canonicalFile)} - """.trimIndent(), - ) - } + val buildCInteropDef = tasks.register("${name}CinteropSqlite", CreateSqliteCInterop::class) { + inputs.file(createStaticLibrary.map { it.staticLibrary }) + + archiveFile.set(archive) + definitionFile.fileProvider(archive.map { File(it.asFile.parentFile, "sqlite3.def") }) } return buildCInteropDef @@ -180,10 +108,7 @@ kotlin { compilations.named("main") { cinterops.create("sqlite3") { - val cInteropTask = tasks[interopProcessingTaskName] - cInteropTask.dependsOn(compileSqlite3) - definitionFile = compileSqlite3.get().outputs.files.singleFile - includeDirs(sqliteSrcFolder.get()) + definitionFile.set(compileSqlite3.flatMap { it.definitionFile }) } } } From 639c743cb78426510e35686958635a3a13029c10 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 12:48:28 +0200 Subject: [PATCH 2/8] Simplify unzipping --- demos/supabase-todolist/gradle.properties | 1 + .../com/powersync/compile/UnzipSqlite.kt | 18 +++++++++++++++--- static-sqlite-driver/build.gradle.kts | 17 ++++++----------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/demos/supabase-todolist/gradle.properties b/demos/supabase-todolist/gradle.properties index a6ee93c4..7e6b6dc2 100644 --- a/demos/supabase-todolist/gradle.properties +++ b/demos/supabase-todolist/gradle.properties @@ -1,6 +1,7 @@ kotlin.code.style=official xcodeproj=./iosApp android.useAndroidX=true +org.gradle.caching=true org.gradle.jvmargs=-Xmx3g org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.macos.enabled=true diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt index 30e52d8c..65aa1157 100644 --- a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/UnzipSqlite.kt @@ -2,21 +2,33 @@ package com.powersync.compile import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileTree import org.gradle.api.provider.Provider import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Copy import org.gradle.api.tasks.OutputDirectory /** - * A cacheable [Copy] task providing typed providers for the emitted [sqlite3C] and [sqlite3H] - * files, making them easier to access in other tasks. + * A cacheable [Copy] task providing a typed provider for the output directory. */ @CacheableTask abstract class UnzipSqlite: Copy() { @get:OutputDirectory abstract val destination: DirectoryProperty - fun intoDirectory(dir: Provider) { + fun unzipSqlite(src: FileTree, dir: Provider) { + from( + src.matching { + include("*/sqlite3.*") + exclude { + it.isDirectory + } + eachFile { + this.path = this.name + } + }, + ) + into(dir) destination.set(dir) } diff --git a/static-sqlite-driver/build.gradle.kts b/static-sqlite-driver/build.gradle.kts index 8b7bab1f..ca3d6de3 100644 --- a/static-sqlite-driver/build.gradle.kts +++ b/static-sqlite-driver/build.gradle.kts @@ -28,18 +28,13 @@ val downloadSQLiteSources by tasks.registering(Download::class) { } val unzipSQLiteSources by tasks.registering(UnzipSqlite::class) { - from( - zipTree(downloadSQLiteSources.map { it.outputs.files.singleFile }).matching { - include("*/sqlite3.*") - exclude { - it.isDirectory - } - eachFile { - this.path = this.name - } - }, + val zip = downloadSQLiteSources.map { it.outputs.files.singleFile } + inputs.file(zip) + + unzipSqlite( + src = zipTree(zip), + dir = layout.buildDirectory.dir("downloads/sqlite3") ) - intoDirectory(layout.buildDirectory.dir("downloads/sqlite3")) } // Obtain host and platform manager from Kotlin multiplatform plugin. They're supposed to be From 2fc93122f72b3ffea79d036f4ced1a54aa3fd361 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 13:19:00 +0200 Subject: [PATCH 3/8] Improve error messages for sync progress test --- .../kotlin/com/powersync/sync/SyncProgressTest.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt b/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt index e7fe1d57..b1af5938 100644 --- a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt +++ b/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt @@ -83,14 +83,19 @@ class SyncProgressTest { val progress = item.downloadProgress ?: error("Expected download progress on $item") assertTrue { item.downloading } - assertEquals(total.first, progress.downloadedOperations) - assertEquals(total.second, progress.totalOperations) + run { + val message = "Expected total progress to be ${total.first}/${total.second}, but it is ${progress.downloadedOperations}/${progress.totalOperations}" + assertEquals(total.first, progress.downloadedOperations, message) + assertEquals(total.second, progress.totalOperations, message) + } priorities.forEach { (priority, expected) -> val (expectedDownloaded, expectedTotal) = expected - val progress = progress.untilPriority(priority) - assertEquals(expectedDownloaded, progress.downloadedOperations) - assertEquals(expectedTotal, progress.totalOperations) + val actualProgress = progress.untilPriority(priority) + val message = "Expected progress at prio $priority to be ${expectedDownloaded}/${expectedTotal}, but it is ${actualProgress.downloadedOperations}/${actualProgress.totalOperations}" + + assertEquals(expectedDownloaded, actualProgress.downloadedOperations, message) + assertEquals(expectedTotal, actualProgress.totalOperations, message) } } From dce1f5ed8fde94bc5b6966a49358bee35811e36d Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 14:26:44 +0200 Subject: [PATCH 4/8] Fix progress assertion message --- .../com/powersync/sync/SyncProgressTest.kt | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt b/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt index b1af5938..d8bc315a 100644 --- a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt +++ b/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt @@ -10,10 +10,11 @@ import com.powersync.bucket.OplogEntry import com.powersync.testutils.ActiveDatabaseTest import com.powersync.testutils.databaseTest import com.powersync.testutils.waitFor +import io.kotest.assertions.withClue +import io.kotest.matchers.properties.shouldHaveValue import kotlinx.coroutines.channels.Channel import kotlin.test.BeforeTest import kotlin.test.Test -import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue @@ -75,6 +76,16 @@ class SyncProgressTest { } } + private fun ProgressWithOperations.shouldBe( + downloaded: Int, + total: Int, + ) { + withClue("progress $downloadedOperations/$totalOperations should be $downloaded/$total") { + this::downloadedOperations shouldHaveValue downloaded + this::totalOperations shouldHaveValue total + } + } + private suspend fun ReceiveTurbine.expectProgress( total: Pair, priorities: Map> = emptyMap(), @@ -83,19 +94,12 @@ class SyncProgressTest { val progress = item.downloadProgress ?: error("Expected download progress on $item") assertTrue { item.downloading } - run { - val message = "Expected total progress to be ${total.first}/${total.second}, but it is ${progress.downloadedOperations}/${progress.totalOperations}" - assertEquals(total.first, progress.downloadedOperations, message) - assertEquals(total.second, progress.totalOperations, message) - } + progress.shouldBe(total.first, total.second) priorities.forEach { (priority, expected) -> val (expectedDownloaded, expectedTotal) = expected val actualProgress = progress.untilPriority(priority) - val message = "Expected progress at prio $priority to be ${expectedDownloaded}/${expectedTotal}, but it is ${actualProgress.downloadedOperations}/${actualProgress.totalOperations}" - - assertEquals(expectedDownloaded, actualProgress.downloadedOperations, message) - assertEquals(expectedTotal, actualProgress.totalOperations, message) + actualProgress.shouldBe(expectedDownloaded, expectedTotal) } } From d54e33756fd98251ce0384cc5bf8d1a5d6b7ffaa Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 17:08:44 +0200 Subject: [PATCH 5/8] Increment download count only after saving --- core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt index 81556310..0d82b6f3 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt @@ -436,8 +436,8 @@ internal class SyncStream( state: SyncStreamState, ): SyncStreamState { val batch = SyncDataBatch(listOf(data)) - status.update { copy(downloading = true, downloadProgress = downloadProgress?.incrementDownloaded(batch)) } bucketStorage.saveSyncData(batch) + status.update { copy(downloading = true, downloadProgress = downloadProgress?.incrementDownloaded(batch)) } return state } From 32b4fbad9fac7187f2da48150a224a21342ff0c0 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 17:49:07 +0200 Subject: [PATCH 6/8] Enable configuration cache --- .github/workflows/deploy.yml | 9 +++++---- demos/supabase-todolist/gradle.properties | 1 + gradle.properties | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 122556ca..1bf933d0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,12 +35,12 @@ jobs: - uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.konan key: ${{ runner.os }}-${{ hashFiles('**/.lock') }} - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' @@ -49,6 +49,7 @@ jobs: - name: Gradle publish run: | ./gradlew \ + --no-configuration-cache \ -PGITHUB_PUBLISH_TOKEN="${{ secrets.GITHUB_TOKEN }}" \ -PsigningInMemoryKey="${{ secrets.SIGNING_KEY }}" \ -PsigningInMemoryKeyId="${{ secrets.SIGNING_KEY_ID }}" \ @@ -66,12 +67,12 @@ jobs: - uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.konan key: ${{ runner.os }}-${{ hashFiles('**/.lock') }} - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' diff --git a/demos/supabase-todolist/gradle.properties b/demos/supabase-todolist/gradle.properties index 7e6b6dc2..0a77e1f0 100644 --- a/demos/supabase-todolist/gradle.properties +++ b/demos/supabase-todolist/gradle.properties @@ -2,6 +2,7 @@ kotlin.code.style=official xcodeproj=./iosApp android.useAndroidX=true org.gradle.caching=true +org.gradle.configuration-cache=true org.gradle.jvmargs=-Xmx3g org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.macos.enabled=true diff --git a/gradle.properties b/gradle.properties index bc4e490b..21eef183 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,7 @@ kotlin.code.style=official # Gradle org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" org.gradle.caching=true +org.gradle.configuration-cache=true # Compose org.jetbrains.compose.experimental.uikit.enabled=true # Android From d409fe6f719daa0d1c151a3de2c12299c77800b2 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 21 May 2025 21:53:41 +0200 Subject: [PATCH 7/8] Actually run it --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82b9780e..3cdc87cd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: push: branches: [ "main" ] pull_request: - branches: [ "main" ] + #branches: [ "main" ] workflow_call: permissions: From 938414e1d46bb95b577e11e0eaa4a853658bc2a5 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 22 May 2025 11:27:51 +0200 Subject: [PATCH 8/8] Fix reference to project in create interop task --- .../kotlin/com/powersync/compile/CreateSqliteCInterop.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt index d4b19ad4..e2bcef63 100644 --- a/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt +++ b/plugins/build-plugin/src/main/kotlin/com/powersync/compile/CreateSqliteCInterop.kt @@ -1,11 +1,13 @@ package com.powersync.compile import org.gradle.api.DefaultTask +import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.work.DisableCachingByDefault +import javax.inject.Inject @DisableCachingByDefault(because = "not worth caching") abstract class CreateSqliteCInterop: DefaultTask() { @@ -15,6 +17,9 @@ abstract class CreateSqliteCInterop: DefaultTask() { @get:OutputFile abstract val definitionFile: RegularFileProperty + @get:Inject + abstract val layout: ProjectLayout + @TaskAction fun run() { val archive = archiveFile.get().asFile @@ -26,7 +31,7 @@ abstract class CreateSqliteCInterop: DefaultTask() { linkerOpts.linux_x64 = -lpthread -ldl linkerOpts.macos_x64 = -lpthread -ldl staticLibraries=${archive.name} - libraryPaths=${parent.relativeTo(project.layout.projectDirectory.asFile.canonicalFile)} + libraryPaths=${parent.relativeTo(layout.projectDirectory.asFile.canonicalFile)} """.trimIndent(), ) }