Skip to content

Commit

Permalink
feat: add JVM support (#88)
Browse files Browse the repository at this point in the history
* JVM support

* Allow to manually set CMake path in local.properties.

* Only builds system arch by default & introduces the powersync.binaries.cross-arch to cross-compile architecture binaries.

---------

Co-authored-by: Ralf Kistner <ralf@journeyapps.com>
  • Loading branch information
SalomonBrys and rkistner authored Jan 7, 2025
1 parent 7e79c11 commit b029624
Show file tree
Hide file tree
Showing 29 changed files with 661 additions and 70 deletions.
64 changes: 44 additions & 20 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,43 @@ permissions:
contents: read

jobs:
build:
uses: ./.github/workflows/gradle.yml
deploy:
needs: build
test:
uses: ./.github/workflows/test.yml
build-native:
name: Build native lib
strategy:
matrix:
include:
- target: publishAllPublicationsToSonatypeRepository
os: macos-latest
# FIXME: Our custom gradle plugin does not currently setup platform specific tasks
# - target: publishIosArm64PublicationToSonatypeRepository
# os: macos-latest
# - target: publishAndroidDebugPublicationToSonatypeRepository
# os: ubuntu-latest
# - target: publishAndroidReleasePublicationToSonatypeRepository
# os: ubuntu-latest
# - target: publishKotlinMultiplatformPublicationToSonatypeRepository
# os: ubuntu-latest
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
- name: Install cross-compiler
if: ${{ matrix.os == 'ubuntu-latest' }}
run: sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- name: Build native lib
run: |
./gradlew \
-PGITHUB_PUBLISH_TOKEN=${{ secrets.GITHUB_TOKEN }} \
-Ppowersync.binaries.cross-arch=true \
:core:cmakeJvmBuild
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.os }}
path: core/build/binaries/desktop/sqlite/
deploy:
needs: [test, build-native]
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Validate Gradle Wrapper
Expand All @@ -39,20 +57,26 @@ jobs:
with:
java-version: '17'
distribution: 'temurin'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3

- name: Download native binaries
uses: actions/download-artifact@v4
with:
path: core/binaries/desktop
merge-multiple: true
- name: Display downloaded files
run: ls -lR core/binaries/desktop
- name: Gradle publish
run: |
./gradlew \
${{ matrix.target }} \
-PGITHUB_PUBLISH_TOKEN="${{ secrets.GITHUB_TOKEN }}" \
-PsigningInMemoryKey="${{ secrets.SIGNING_KEY }}" \
-PsigningInMemoryKeyId="${{ secrets.SIGNING_KEY_ID }}" \
-PsigningInMemoryKeyPassword="${{ secrets.SIGNING_PASSWORD }}" \
-PcentralPortal.username="${{secrets.SONATYPE_USERNAME}}" \
-PcentralPortal.password="${{secrets.SONATYPE_PASSWORD}}"
-PcentralPortal.password="${{secrets.SONATYPE_PASSWORD}}" \
-Ppowersync.binaries.provided="true" \
publishAllPublicationsToSonatypeRepository
# This will change Package.swift in Github packages to direct to new maven central KMMBridge zip file
call-kmmbridge-publish:
needs: deploy
Expand Down
File renamed without changes.
7 changes: 5 additions & 2 deletions PowerSyncKotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import co.touchlab.faktory.artifactmanager.ArtifactManager
import co.touchlab.faktory.capitalized
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.ir.backend.js.compile
import java.net.URL
import java.security.MessageDigest

Expand Down Expand Up @@ -28,8 +29,10 @@ kotlin {
explicitApi()

targets.withType<KotlinNativeTarget> {
compilations.getByName("main") {
compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc")
compilations.named("main") {
compileTaskProvider {
compilerOptions.freeCompilerArgs.add("-Xexport-kdoc")
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ kotlin {
publishLibraryVariants("release", "debug")
}

jvm()

iosX64()
iosArm64()
iosSimulatorArm64()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.powersync.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.powersync.DatabaseDriverFactory

@Composable
public actual fun rememberDatabaseDriverFactory(): DatabaseDriverFactory =
remember {
DatabaseDriverFactory()
}
8 changes: 6 additions & 2 deletions connectors/supabase/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ kotlin {
publishLibraryVariants("release", "debug")
}

jvm()

targets.withType<KotlinNativeTarget> {
compilations.getByName("main") {
compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc")
compilations.named("main") {
compileTaskProvider {
compilerOptions.freeCompilerArgs.add("-Xexport-kdoc")
}
}
}

Expand Down
1 change: 1 addition & 0 deletions core/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
binaries/
174 changes: 168 additions & 6 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import app.cash.sqldelight.core.capitalize
import com.powersync.plugins.sonatype.setupGithubRepository
import de.undercouch.gradle.tasks.download.Download
import org.gradle.internal.os.OperatingSystem
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import java.util.*

plugins {
alias(libs.plugins.kotlinMultiplatform)
Expand All @@ -13,12 +16,12 @@ plugins {
alias(libs.plugins.mokkery)
}

val sqliteVersion = "3450000"
val sqliteVersion = "3450200"
val sqliteReleaseYear = "2024"

val sqliteSrcFolder =
project.layout.buildDirectory
.dir("interop/sqlite")
.dir("native/sqlite")
.get()

val downloadSQLiteSources by tasks.registering(Download::class) {
Expand Down Expand Up @@ -50,8 +53,13 @@ val unzipSQLiteSources by tasks.registering(Copy::class) {
val buildCInteropDef by tasks.registering {
dependsOn(unzipSQLiteSources)

val interopFolder =
project.layout.buildDirectory
.dir("interop/sqlite")
.get()

val cFile = sqliteSrcFolder.file("sqlite3.c").asFile
val defFile = sqliteSrcFolder.file("sqlite3.def").asFile
val defFile = interopFolder.file("sqlite3.def").asFile

doFirst {
defFile.writeText(
Expand All @@ -69,18 +77,21 @@ kotlin {
androidTarget {
publishLibraryVariants("release", "debug")
}
jvm()

iosX64()
iosArm64()
iosSimulatorArm64()

targets.withType<KotlinNativeTarget> {
compilations.getByName("main") {
compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc")
compilations.named("main") {
compileTaskProvider {
compilerOptions.freeCompilerArgs.add("-Xexport-kdoc")
}
cinterops.create("sqlite") {
val cInteropTask = tasks[interopProcessingTaskName]
cInteropTask.dependsOn(buildCInteropDef)
defFile =
definitionFile =
buildCInteropDef
.get()
.outputs.files.singleFile
Expand Down Expand Up @@ -118,6 +129,11 @@ kotlin {
implementation(libs.ktor.client.okhttp)
}

jvmMain.dependencies {
implementation(libs.ktor.client.okhttp)
implementation(libs.sqlite.jdbc)
}

iosMain.dependencies {
implementation(libs.ktor.client.ios)
}
Expand Down Expand Up @@ -159,6 +175,7 @@ android {
.get()
.toInt()

@Suppress("UnstableApiUsage")
externalNativeBuild {
cmake {
arguments.addAll(
Expand All @@ -177,6 +194,151 @@ android {
}
}

val os = OperatingSystem.current()
val binariesAreProvided = project.findProperty("powersync.binaries.provided") == "true"
val crossArch = project.findProperty("powersync.binaries.cross-arch") == "true"
val binariesFolder = project.layout.buildDirectory.dir("binaries/desktop")

if (binariesAreProvided && crossArch) {
error("powersync.binaries.provided and powersync.binaries.cross-arch must not be both defined.")
}

val getBinaries = if (binariesAreProvided) {
// Binaries for all OS must be provided (manually or by the CI) in binaries/desktop

val verifyPowersyncBinaries = tasks.register("verifyPowersyncBinaries") {
val directory = projectDir.resolve("binaries/desktop")
val binaries = listOf(
directory.resolve("libpowersync-sqlite_aarch64.so"),
directory.resolve("libpowersync-sqlite_x64.so"),
directory.resolve("libpowersync-sqlite_aarch64.dylib"),
directory.resolve("libpowersync-sqlite_x64.dylib"),
directory.resolve("powersync-sqlite_x64.dll"),
)
doLast {
binaries.forEach {
if (!it.exists()) error("File $it does not exist")
if (!it.isFile) error("File $it is not a regular file")
}
}
outputs.files(*binaries.toTypedArray())
}
verifyPowersyncBinaries
} else {
// Building locally for the current OS

val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { localProperties.load(it) }
}
val cmakeExecutable = localProperties.getProperty("cmake.path") ?: "cmake"

fun registerCMakeTasks(
suffix: String,
vararg defines: String,
): TaskProvider<Exec> {
val cmakeConfigure = tasks.register<Exec>("cmakeJvmConfigure${suffix.capitalize()}") {
dependsOn(unzipSQLiteSources)
group = "cmake"
workingDir = layout.buildDirectory.dir("cmake/$suffix").get().asFile
inputs.files(
"src/jvmMain/cpp",
"src/jvmNative/cpp",
sqliteSrcFolder,
)
outputs.dir(workingDir)
executable = cmakeExecutable
args(listOf(file("src/jvmMain/cpp/CMakeLists.txt").absolutePath, "-DSUFFIX=$suffix", "-DCMAKE_BUILD_TYPE=Release") + defines.map { "-D$it" })
doFirst {
workingDir.mkdirs()
}
}

val cmakeBuild = tasks.register<Exec>("cmakeJvmBuild${suffix.capitalize()}") {
dependsOn(cmakeConfigure)
group = "cmake"
workingDir = layout.buildDirectory.dir("cmake/$suffix").get().asFile
inputs.files(
"src/jvmMain/cpp",
"src/jvmNative/cpp",
sqliteSrcFolder,
workingDir,
)
outputs.dir(workingDir.resolve(if (os.isWindows) "output/Release" else "output"))
executable = cmakeExecutable
args("--build", ".", "--config", "Release")
}

return cmakeBuild
}

val (aarch64, x64) = when {
os.isMacOsX -> {
val aarch64 = registerCMakeTasks("aarch64", "CMAKE_OSX_ARCHITECTURES=arm64")
val x64 = registerCMakeTasks("x64", "CMAKE_OSX_ARCHITECTURES=x86_64")
aarch64 to x64
}
os.isLinux -> {
val aarch64 = registerCMakeTasks("aarch64", "CMAKE_C_COMPILER=aarch64-linux-gnu-gcc", "CMAKE_CXX_COMPILER=aarch64-linux-gnu-g++")
val x64 = registerCMakeTasks("x64", "CMAKE_C_COMPILER=x86_64-linux-gnu-gcc", "CMAKE_CXX_COMPILER=x86_64-linux-gnu-g++")
aarch64 to x64
}
os.isWindows -> {
val x64 = registerCMakeTasks("x64")
null to x64
}
else -> error("Unknown operating system: $os")
}

val arch = System.getProperty("os.arch")
val cmakeJvmBuilds = when {
crossArch -> listOfNotNull(aarch64, x64)
arch == "aarch64" -> listOfNotNull(aarch64)
arch == "amd64" -> listOfNotNull(x64)
else -> error("Unknown architecture: $arch")
}

tasks.register<Copy>("cmakeJvmBuild") {
dependsOn(cmakeJvmBuilds)
group = "cmake"
from(cmakeJvmBuilds)
into(binariesFolder.map { it.dir("sqlite") })
}
}

val downloadPowersyncDesktopBinaries = tasks.register<Download>("downloadPowersyncDesktopBinaries") {
val coreVersion = libs.versions.powersync.core.get()
val linux_aarch64 = "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.so"
val linux_x64 = "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.so"
val macos_aarch64 = "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.dylib"
val macos_x64 = "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.dylib"
val windows_x64 = "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/powersync_x64.dll"
if (binariesAreProvided) {
src(listOf(linux_aarch64, linux_x64, macos_aarch64, macos_x64, windows_x64))
} else {
val (aarch64, x64) = when {
os.isLinux -> linux_aarch64 to linux_x64
os.isMacOsX -> macos_aarch64 to macos_x64
os.isWindows -> null to windows_x64
else -> error("Unknown operating system: $os")
}
val arch = System.getProperty("os.arch")
src(when {
crossArch -> listOfNotNull(aarch64, x64)
arch == "aarch64" -> listOfNotNull(aarch64)
arch == "amd64" -> listOfNotNull(x64)
else -> error("Unknown architecture: $arch")
})
}
dest(binariesFolder.map { it.dir("powersync") })
onlyIfModified(true)
}

tasks.named<ProcessResources>(kotlin.jvm().compilations["main"].processResourcesTaskName) {
from(getBinaries, downloadPowersyncDesktopBinaries)
}

afterEvaluate {
val buildTasks =
tasks.matching {
Expand Down
2 changes: 1 addition & 1 deletion core/src/androidMain/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
add_library(
${PACKAGE_NAME}
SHARED
sqlite_bindings.cpp
../../jvmNative/cpp/sqlite_bindings.cpp
"${SQLITE3_SRC_DIR}/sqlite3.c"
)

Expand Down
Loading

0 comments on commit b029624

Please sign in to comment.