Skip to content
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
26 changes: 20 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,39 @@ jobs:
strategy:
fail-fast: false
matrix:
runner:
- macos-15-intel
- ubuntu-24.04-arm
- ubuntu-latest
- windows-latest
include:
- runner: macos-15-intel
- runner: windows-latest

# Linux dynamic libraries are built with `cross` (in Docker containers) to link against
# an older glibc than provided by the GitHub runners, for compatibility with older Linux
# distributions (e.g. Raspberry Pi OS).
# https://github.com/JuulLabs/kable/issues/990
- runner: ubuntu-latest
cargo: cross
target: x86_64-unknown-linux-gnu
- runner: ubuntu-latest
cargo: cross
target: aarch64-unknown-linux-gnu
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
java-version-file: .sdkmanrc
- uses: gradle/actions/setup-gradle@v6
- if: matrix.cargo == 'cross'
run: cargo install cross --git https://github.com/cross-rs/cross
- run: ./gradlew :kable-core:compileKotlinJvm
env:
UNIFFI_CARGO: ${{ matrix.cargo }}
UNIFFI_TARGET: ${{ matrix.target }}
- run: unzip kable-btleplug-ffi.jar && rm -rf META-INF com *.jar
working-directory: kable-btleplug-ffi/build/libs
shell: bash
- uses: actions/upload-artifact@v7
with:
name: kable-btleplug-ffi-${{ matrix.runner }}
name: kable-btleplug-ffi-${{ matrix.target || matrix.runner }}
path: kable-btleplug-ffi/build/libs/
if-no-files-found: error
retention-days: 1
Expand Down
26 changes: 20 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,39 @@ jobs:
strategy:
fail-fast: false
matrix:
runner:
- macos-15-intel
- ubuntu-24.04-arm
- ubuntu-latest
- windows-latest
include:
- runner: macos-15-intel
- runner: windows-latest

# Linux dynamic libraries are built with `cross` (in Docker containers) to link against
# an older glibc than provided by the GitHub runners, for compatibility with older Linux
# distributions (e.g. Raspberry Pi OS).
# https://github.com/JuulLabs/kable/issues/990
- runner: ubuntu-latest
cargo: cross
target: x86_64-unknown-linux-gnu
- runner: ubuntu-latest
cargo: cross
target: aarch64-unknown-linux-gnu
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
java-version-file: .sdkmanrc
- uses: gradle/actions/setup-gradle@v6
- if: matrix.cargo == 'cross'
run: cargo install cross --git https://github.com/cross-rs/cross
- run: ./gradlew :kable-btleplug-ffi:assemble
env:
UNIFFI_CARGO: ${{ matrix.cargo }}
UNIFFI_TARGET: ${{ matrix.target }}
- run: unzip kable-btleplug-ffi.jar && rm -rf META-INF com *.jar
working-directory: kable-btleplug-ffi/build/libs
shell: bash
- uses: actions/upload-artifact@v7
with:
name: kable-btleplug-ffi-${{ matrix.runner }}
name: kable-btleplug-ffi-${{ matrix.target || matrix.runner }}
path: kable-btleplug-ffi/build/libs/
if-no-files-found: error
retention-days: 1
Expand Down
9 changes: 9 additions & 0 deletions kable-btleplug-ffi/Cross.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Build Linux dynamic libraries against CentOS 7's glibc 2.17 (rather than the glibc of the default
# `cross` images) for compatibility with older Linux distributions (e.g. Raspberry Pi OS).
# https://github.com/JuulLabs/kable/issues/990

[target.x86_64-unknown-linux-gnu]
image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main-centos"

[target.aarch64-unknown-linux-gnu]
image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main-centos"
7 changes: 7 additions & 0 deletions kable-btleplug-ffi/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,11 @@ kotlin {
uniffiKotlin {
optimized = System.getenv("CI").toBoolean()
packageName = "com.juul.kable.btleplug.ffi"

// CI builds Linux dynamic libraries with `cross` (https://github.com/cross-rs/cross) so that
// they link against an older glibc than what is available on the GitHub runners, for
// compatibility with older Linux distributions (e.g. Raspberry Pi OS).
// https://github.com/JuulLabs/kable/issues/990
System.getenv("UNIFFI_CARGO")?.takeIf(String::isNotBlank)?.let { cargoCommand = it }
System.getenv("UNIFFI_TARGET")?.takeIf(String::isNotBlank)?.let { target = it }
}
1 change: 1 addition & 0 deletions uniffi-plugin/src/main/kotlin/TaskExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fun TaskInputs.rustSources() {
// Optional
files(
"Cargo.lock",
"Cross.toml",
"rust-toolchain.toml",
"rustfmt.toml",
)
Expand Down
20 changes: 20 additions & 0 deletions uniffi-plugin/src/main/kotlin/UniffiKotlinExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,39 @@ import org.gradle.api.provider.Property
import org.gradle.api.tasks.Optional

interface UniffiKotlinExtension {
/**
* Command used to build the dynamic library, for example `cross`
* (https://github.com/cross-rs/cross). Defaults to `cargo`.
*/
@get:Optional
val cargoCommand: Property<String>

/** When true, adds the `--release` tag to cargo builds. */
val optimized: Property<Boolean>

/** Package name to use for generated kotlin code. */
val packageName: Property<String>

/**
* Rust target triple (e.g. `aarch64-unknown-linux-gnu`) to build the dynamic library for.
* Defaults to the host's target.
*/
@get:Optional
val target: Property<String>
}

internal class UniffiKotlinExtensionAccessor(
private val extension: UniffiKotlinExtension,
) {
val cargoCommand: String
get() = extension.cargoCommand.getOrElse("cargo")

val optimized: Boolean
get() = extension.optimized.get()

val packageName: String
get() = extension.packageName.get()

val target: UniffiTarget
get() = extension.target.orNull?.let(UniffiTarget::parse) ?: UniffiTarget.current
}
8 changes: 8 additions & 0 deletions uniffi-plugin/src/main/kotlin/UniffiTarget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,13 @@ data class UniffiTarget(val arch: UniffiArch, val os: UniffiOs) {
val current: UniffiTarget by lazy {
UniffiTarget(UniffiArch.current, UniffiOs.current)
}

fun parse(triple: String): UniffiTarget {
val arch = UniffiArch.entries.singleOrNull { triple.startsWith("${it.tripleComponent}-") }
?: error("Unsupported architecture in target triple: $triple")
val os = UniffiOs.entries.singleOrNull { triple.endsWith("-${it.tripleComponent}") }
?: error("Unsupported OS in target triple: $triple")
return UniffiTarget(arch, os)
}
}
}
9 changes: 5 additions & 4 deletions uniffi-plugin/src/main/kotlin/tasks/CargoBuildTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.juul.kable.uniffi.plugin.tasks

import com.juul.kable.uniffi.plugin.UNIFFI_TASK_GROUP
import com.juul.kable.uniffi.plugin.UniffiKotlinExtensionAccessor
import com.juul.kable.uniffi.plugin.UniffiTarget
import com.juul.kable.uniffi.plugin.cargoBuild
import com.juul.kable.uniffi.plugin.rustSources
import org.gradle.api.tasks.Exec
Expand All @@ -13,12 +12,14 @@ internal fun TaskContainer.registerCargoBuildTask(accessor: UniffiKotlinExtensio
register<Exec>("cargoBuild") {
group = UNIFFI_TASK_GROUP

inputs.property("cargoCommand", accessor.cargoCommand)
inputs.property("optimized", accessor.optimized)
inputs.property("target", accessor.target.triple)
inputs.rustSources()
outputs.cargoBuild(UniffiTarget.current, accessor.optimized)
outputs.cargoBuild(accessor.target, accessor.optimized)

commandLine("cargo", "build")
args("--target", UniffiTarget.current.triple)
commandLine(accessor.cargoCommand, "build")
args("--target", accessor.target.triple)
if (accessor.optimized) {
args("--release")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.juul.kable.uniffi.plugin.tasks

import com.juul.kable.uniffi.plugin.KOTLIN_SOURCE_SET
import com.juul.kable.uniffi.plugin.UniffiKotlinExtensionAccessor
import com.juul.kable.uniffi.plugin.UniffiTarget
import com.juul.kable.uniffi.plugin.cargoBuild
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskContainer
Expand All @@ -19,7 +18,7 @@ internal fun TaskContainer.registerCopyDynamicLibraryResourcesTask(accessor: Uni
.resolve("resources")
.resolve(KOTLIN_SOURCE_SET)

val target = UniffiTarget.current
val target = accessor.target
inputs.cargoBuild(target, accessor.optimized)
from(target.buildDirectory(accessor.optimized)) {
include { it.name.matches(target.os.library) }
Expand Down
9 changes: 4 additions & 5 deletions uniffi-plugin/src/main/kotlin/tasks/UniffiBindgenTasks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package com.juul.kable.uniffi.plugin.tasks

import com.juul.kable.uniffi.plugin.UNIFFI_TASK_GROUP
import com.juul.kable.uniffi.plugin.UniffiKotlinExtensionAccessor
import com.juul.kable.uniffi.plugin.UniffiOs
import com.juul.kable.uniffi.plugin.UniffiTarget
import com.juul.kable.uniffi.plugin.cargoBuild
import com.juul.kable.uniffi.plugin.uniffiBindgenProject
import com.juul.kable.uniffi.plugin.uniffiOutputDirectory
Expand Down Expand Up @@ -82,7 +80,7 @@ internal fun TaskContainer.registerUniffiBindgenTasks(accessor: UniffiKotlinExte

inputs.property("optimized", accessor.optimized)

inputs.cargoBuild(UniffiTarget.current, accessor.optimized)
inputs.cargoBuild(accessor.target, accessor.optimized)
inputs.dir(project.uniffiBindgenProject)
outputs.dir(project.uniffiOutputDirectory)

Expand All @@ -93,8 +91,9 @@ internal fun TaskContainer.registerUniffiBindgenTasks(accessor: UniffiKotlinExte
args("--out-dir", project.uniffiOutputDirectory.absolutePath)
args("--no-format")
doFirst {
val directory = project.file(UniffiTarget.current.buildDirectory(accessor.optimized))
val file = directory.list().orEmpty().single { it.matches(UniffiOs.current.library) }
val target = accessor.target
val directory = project.file(target.buildDirectory(accessor.optimized))
val file = directory.list().orEmpty().single { it.matches(target.os.library) }
args("--library", directory.resolve(file).absolutePath)
}
}
Expand Down
Loading