Skip to content

Commit 428f5e3

Browse files
committedMay 22, 2025
Refactor SQLite compilation tasks
1 parent 4972089 commit 428f5e3

File tree

6 files changed

+213
-102
lines changed

6 files changed

+213
-102
lines changed
 

‎gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
kotlin.code.style=official
22
# Gradle
33
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
4+
org.gradle.caching=true
45
# Compose
56
org.jetbrains.compose.experimental.uikit.enabled=true
67
# Android
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.powersync.compile
2+
3+
import org.gradle.api.DefaultTask
4+
import org.gradle.api.file.DirectoryProperty
5+
import org.gradle.api.file.RegularFileProperty
6+
import org.gradle.api.provider.Property
7+
import org.gradle.api.provider.ProviderFactory
8+
import org.gradle.api.tasks.CacheableTask
9+
import org.gradle.api.tasks.Input
10+
import org.gradle.api.tasks.InputDirectory
11+
import org.gradle.api.tasks.InputFile
12+
import org.gradle.api.tasks.OutputFile
13+
import org.gradle.api.tasks.PathSensitive
14+
import org.gradle.api.tasks.PathSensitivity
15+
import org.gradle.api.tasks.TaskAction
16+
import org.jetbrains.kotlin.konan.target.KonanTarget
17+
import javax.inject.Inject
18+
import kotlin.io.path.name
19+
20+
@CacheableTask
21+
abstract class ClangCompile: DefaultTask() {
22+
@get:InputFile
23+
@get:PathSensitive(PathSensitivity.NONE)
24+
abstract val inputFile: RegularFileProperty
25+
26+
@get:Input
27+
abstract val konanTarget: Property<String>
28+
29+
@get:InputDirectory
30+
@get:PathSensitive(PathSensitivity.NONE)
31+
abstract val include: DirectoryProperty
32+
33+
@get:OutputFile
34+
abstract val objectFile: RegularFileProperty
35+
36+
@get:Inject
37+
protected abstract val providers: ProviderFactory
38+
39+
@TaskAction
40+
fun run() {
41+
val target = requireNotNull(KonanTarget.predefinedTargets[konanTarget.get()])
42+
43+
val (llvmTarget, sysRoot) = when (target) {
44+
KonanTarget.IOS_X64 -> "x86_64-apple-ios12.0-simulator" to IOS_SIMULATOR_SDK
45+
KonanTarget.IOS_ARM64 -> "arm64-apple-ios12.0" to IOS_SDK
46+
KonanTarget.IOS_SIMULATOR_ARM64 -> "arm64-apple-ios14.0-simulator" to IOS_SIMULATOR_SDK
47+
KonanTarget.MACOS_ARM64 -> "aarch64-apple-macos" to MACOS_SDK
48+
KonanTarget.MACOS_X64 -> "x86_64-apple-macos" to MACOS_SDK
49+
KonanTarget.WATCHOS_DEVICE_ARM64 -> "aarch64-apple-watchos" to WATCHOS_SDK
50+
KonanTarget.WATCHOS_ARM32 -> "armv7k-apple-watchos" to WATCHOS_SDK
51+
KonanTarget.WATCHOS_ARM64 -> "arm64_32-apple-watchos" to WATCHOS_SDK
52+
KonanTarget.WATCHOS_SIMULATOR_ARM64 -> "aarch64-apple-watchos-simulator" to WATCHOS_SIMULATOR_SDK
53+
KonanTarget.WATCHOS_X64 -> "x86_64-apple-watchos-simulator" to WATCHOS_SIMULATOR_SDK
54+
else -> error("Unexpected target $target")
55+
}
56+
57+
val output = objectFile.get()
58+
59+
providers.exec {
60+
executable = "clang"
61+
args(
62+
"-B/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin",
63+
"-fno-stack-protector",
64+
"-target",
65+
llvmTarget,
66+
"-isysroot",
67+
sysRoot,
68+
"-fPIC",
69+
"--compile",
70+
"-I${include.get().asFile.absolutePath}",
71+
inputFile.get().asFile.absolutePath,
72+
"-DHAVE_GETHOSTUUID=0",
73+
"-DSQLITE_ENABLE_DBSTAT_VTAB",
74+
"-DSQLITE_ENABLE_FTS5",
75+
"-DSQLITE_ENABLE_RTREE",
76+
"-O3",
77+
"-o",
78+
output.asFile.toPath().name,
79+
)
80+
81+
workingDir = output.asFile.parentFile
82+
}.result.get()
83+
}
84+
85+
companion object {
86+
const val WATCHOS_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk"
87+
const val WATCHOS_SIMULATOR_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/"
88+
const val IOS_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
89+
const val IOS_SIMULATOR_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
90+
const val MACOS_SDK = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"
91+
}
92+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.powersync.compile
2+
3+
import org.gradle.api.DefaultTask
4+
import org.gradle.api.file.RegularFileProperty
5+
import org.gradle.api.tasks.InputFile
6+
import org.gradle.api.tasks.OutputFile
7+
import org.gradle.api.tasks.TaskAction
8+
import org.gradle.work.DisableCachingByDefault
9+
10+
@DisableCachingByDefault(because = "not worth caching")
11+
abstract class CreateSqliteCInterop: DefaultTask() {
12+
@get:InputFile
13+
abstract val archiveFile: RegularFileProperty
14+
15+
@get:OutputFile
16+
abstract val definitionFile: RegularFileProperty
17+
18+
@TaskAction
19+
fun run() {
20+
val archive = archiveFile.get().asFile
21+
val parent = archive.parentFile
22+
23+
definitionFile.get().asFile.writeText("""
24+
package = com.powersync.sqlite3
25+
26+
linkerOpts.linux_x64 = -lpthread -ldl
27+
linkerOpts.macos_x64 = -lpthread -ldl
28+
staticLibraries=${archive.name}
29+
libraryPaths=${parent.relativeTo(project.layout.projectDirectory.asFile.canonicalFile)}
30+
""".trimIndent(),
31+
)
32+
}
33+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.powersync.compile
2+
3+
import org.gradle.api.DefaultTask
4+
import org.gradle.api.file.ConfigurableFileCollection
5+
import org.gradle.api.file.RegularFileProperty
6+
import org.gradle.api.provider.ProviderFactory
7+
import org.gradle.api.tasks.CacheableTask
8+
import org.gradle.api.tasks.InputFiles
9+
import org.gradle.api.tasks.OutputFile
10+
import org.gradle.api.tasks.PathSensitive
11+
import org.gradle.api.tasks.PathSensitivity
12+
import org.gradle.api.tasks.TaskAction
13+
import javax.inject.Inject
14+
15+
@CacheableTask
16+
abstract class CreateStaticLibrary: DefaultTask() {
17+
@get:InputFiles
18+
@get:PathSensitive(PathSensitivity.RELATIVE)
19+
abstract val objects: ConfigurableFileCollection
20+
21+
@get:OutputFile
22+
abstract val staticLibrary: RegularFileProperty
23+
24+
@get:Inject
25+
abstract val providers: ProviderFactory
26+
27+
@TaskAction
28+
fun run() {
29+
providers.exec {
30+
executable = "ar"
31+
args("rc", staticLibrary.get().asFile.absolutePath)
32+
for (file in objects.files) {
33+
args(file.absolutePath)
34+
}
35+
}.result.get()
36+
}
37+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.powersync.compile
2+
3+
import org.gradle.api.file.Directory
4+
import org.gradle.api.file.DirectoryProperty
5+
import org.gradle.api.provider.Provider
6+
import org.gradle.api.tasks.CacheableTask
7+
import org.gradle.api.tasks.Copy
8+
import org.gradle.api.tasks.OutputDirectory
9+
10+
/**
11+
* A cacheable [Copy] task providing typed providers for the emitted [sqlite3C] and [sqlite3H]
12+
* files, making them easier to access in other tasks.
13+
*/
14+
@CacheableTask
15+
abstract class UnzipSqlite: Copy() {
16+
@get:OutputDirectory
17+
abstract val destination: DirectoryProperty
18+
19+
fun intoDirectory(dir: Provider<Directory>) {
20+
into(dir)
21+
destination.set(dir)
22+
}
23+
}

‎static-sqlite-driver/build.gradle.kts

Lines changed: 27 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1+
import com.powersync.compile.ClangCompile
2+
import com.powersync.compile.CreateSqliteCInterop
3+
import com.powersync.compile.CreateStaticLibrary
4+
import com.powersync.compile.UnzipSqlite
15
import java.io.File
2-
import java.io.FileInputStream
36
import com.powersync.plugins.sonatype.setupGithubRepository
47
import com.powersync.plugins.utils.powersyncTargets
58
import de.undercouch.gradle.tasks.download.Download
69
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
7-
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
8-
import org.jetbrains.kotlin.konan.properties.Properties
910
import org.jetbrains.kotlin.konan.target.HostManager
10-
import org.jetbrains.kotlin.konan.target.KonanTarget
11-
import org.jetbrains.kotlin.konan.target.PlatformManager
1211

1312
plugins {
1413
alias(libs.plugins.kotlinMultiplatform)
@@ -20,22 +19,17 @@ plugins {
2019
val sqliteVersion = "3490100"
2120
val sqliteReleaseYear = "2025"
2221

23-
val downloads = layout.buildDirectory.dir("downloads")
24-
val sqliteSrcFolder = downloads.map { it.dir("sqlite3") }
25-
2622
val downloadSQLiteSources by tasks.registering(Download::class) {
2723
val zipFileName = "sqlite-amalgamation-$sqliteVersion.zip"
2824
src("https://www.sqlite.org/$sqliteReleaseYear/$zipFileName")
29-
dest(downloads.map { it.file(zipFileName) })
25+
dest(layout.buildDirectory.dir("downloads").map { it.file(zipFileName) })
3026
onlyIfNewer(true)
3127
overwrite(false)
3228
}
3329

34-
val unzipSQLiteSources by tasks.registering(Copy::class) {
35-
dependsOn(downloadSQLiteSources)
36-
30+
val unzipSQLiteSources by tasks.registering(UnzipSqlite::class) {
3731
from(
38-
zipTree(downloadSQLiteSources.get().dest).matching {
32+
zipTree(downloadSQLiteSources.map { it.outputs.files.singleFile }).matching {
3933
include("*/sqlite3.*")
4034
exclude {
4135
it.isDirectory
@@ -45,107 +39,41 @@ val unzipSQLiteSources by tasks.registering(Copy::class) {
4539
}
4640
},
4741
)
48-
into(sqliteSrcFolder)
42+
intoDirectory(layout.buildDirectory.dir("downloads/sqlite3"))
4943
}
5044

5145
// Obtain host and platform manager from Kotlin multiplatform plugin. They're supposed to be
5246
// internal, but it's very convenient to have them because they expose the necessary toolchains we
5347
// use to compile SQLite for the platforms we need.
5448
val hostManager = HostManager()
5549

56-
fun compileSqlite(target: KotlinNativeTarget): TaskProvider<Task> {
50+
fun compileSqlite(target: KotlinNativeTarget): TaskProvider<CreateSqliteCInterop> {
5751
val name = target.targetName
5852
val outputDir = layout.buildDirectory.dir("c/$name")
5953

60-
val compileSqlite = tasks.register("${name}CompileSqlite") {
61-
dependsOn(unzipSQLiteSources)
62-
val targetDirectory = outputDir.get()
63-
val sqliteSource = sqliteSrcFolder.map { it.file("sqlite3.c") }.get()
64-
val output = targetDirectory.file("sqlite3.o")
65-
66-
inputs.file(sqliteSource)
67-
outputs.file(output)
54+
val sqlite3Obj = outputDir.map { it.file("sqlite3.o") }
55+
val archive = outputDir.map { it.file("libsqlite3.a") }
6856

69-
doFirst {
70-
targetDirectory.asFile.mkdirs()
71-
output.asFile.delete()
72-
}
73-
74-
doLast {
75-
val (llvmTarget, sysRoot) = when (target.konanTarget) {
76-
KonanTarget.IOS_X64 -> "x86_64-apple-ios12.0-simulator" to "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
77-
KonanTarget.IOS_ARM64 -> "arm64-apple-ios12.0" to "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk"
78-
KonanTarget.IOS_SIMULATOR_ARM64 -> "arm64-apple-ios14.0-simulator" to "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
79-
KonanTarget.MACOS_ARM64 -> "aarch64-apple-macos" to "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"
80-
KonanTarget.MACOS_X64 -> "x86_64-apple-macos" to "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"
81-
else -> error("Unexpected target $target")
82-
}
57+
val compileSqlite = tasks.register("${name}CompileSqlite", ClangCompile::class) {
58+
inputs.dir(unzipSQLiteSources.map { it.destination })
8359

84-
providers.exec {
85-
executable = "clang"
86-
args(
87-
"-B/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin",
88-
"-fno-stack-protector",
89-
"-target",
90-
llvmTarget,
91-
"-isysroot",
92-
sysRoot,
93-
"-fPIC",
94-
"--compile",
95-
"-I${sqliteSrcFolder.get().asFile.absolutePath}",
96-
sqliteSource.asFile.absolutePath,
97-
"-DHAVE_GETHOSTUUID=0",
98-
"-DSQLITE_ENABLE_DBSTAT_VTAB",
99-
"-DSQLITE_ENABLE_FTS5",
100-
"-DSQLITE_ENABLE_RTREE",
101-
"-O3",
102-
"-o",
103-
"sqlite3.o",
104-
)
105-
106-
workingDir = targetDirectory.asFile
107-
}.result.get()
108-
}
60+
inputFile.set(unzipSQLiteSources.flatMap { it.destination.file("sqlite3.c") })
61+
konanTarget.set(target.konanTarget.name)
62+
include.set(unzipSQLiteSources.flatMap { it.destination })
63+
objectFile.set(sqlite3Obj)
10964
}
11065

111-
val createStaticLibrary = tasks.register("${name}ArchiveSqlite") {
112-
dependsOn(compileSqlite)
113-
val targetDirectory = outputDir.get()
114-
inputs.file(targetDirectory.file("sqlite3.o"))
115-
outputs.file(targetDirectory.file("libsqlite3.a"))
116-
117-
doLast {
118-
providers.exec {
119-
executable = "ar"
120-
args("rc", "libsqlite3.a", "sqlite3.o")
121-
122-
workingDir = targetDirectory.asFile
123-
}.result.get()
124-
}
66+
val createStaticLibrary = tasks.register("${name}ArchiveSqlite", CreateStaticLibrary::class) {
67+
inputs.file(compileSqlite.map { it.objectFile })
68+
objects.from(sqlite3Obj)
69+
staticLibrary.set(archive)
12570
}
12671

127-
val buildCInteropDef = tasks.register("${name}CinteropSqlite") {
128-
dependsOn(createStaticLibrary)
129-
130-
val archive = createStaticLibrary.get().outputs.files.singleFile
131-
inputs.file(archive)
132-
133-
val parent = archive.parentFile
134-
val defFile = File(parent, "sqlite3.def")
135-
outputs.file(defFile)
136-
137-
doFirst {
138-
defFile.writeText(
139-
"""
140-
package = com.powersync.sqlite3
141-
142-
linkerOpts.linux_x64 = -lpthread -ldl
143-
linkerOpts.macos_x64 = -lpthread -ldl
144-
staticLibraries=${archive.name}
145-
libraryPaths=${parent.relativeTo(project.layout.projectDirectory.asFile.canonicalFile)}
146-
""".trimIndent(),
147-
)
148-
}
72+
val buildCInteropDef = tasks.register("${name}CinteropSqlite", CreateSqliteCInterop::class) {
73+
inputs.file(createStaticLibrary.map { it.staticLibrary })
74+
75+
archiveFile.set(archive)
76+
definitionFile.fileProvider(archive.map { File(it.asFile.parentFile, "sqlite3.def") })
14977
}
15078

15179
return buildCInteropDef
@@ -180,10 +108,7 @@ kotlin {
180108

181109
compilations.named("main") {
182110
cinterops.create("sqlite3") {
183-
val cInteropTask = tasks[interopProcessingTaskName]
184-
cInteropTask.dependsOn(compileSqlite3)
185-
definitionFile = compileSqlite3.get().outputs.files.singleFile
186-
includeDirs(sqliteSrcFolder.get())
111+
definitionFile.set(compileSqlite3.flatMap { it.definitionFile })
187112
}
188113
}
189114
}

0 commit comments

Comments
 (0)
Please sign in to comment.