Skip to content

gl-sdk: Bundle the desktop libraries in the jvm jar, update Gradle and AGP - #765

Open
angelix wants to merge 4 commits into
Blockstream:mainfrom
angelix:ave-native-support
Open

angelix wants to merge 4 commits into
Blockstream:mainfrom
angelix:ave-native-support

Conversation

@angelix

@angelix angelix commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #762. This branch carries its two commits, rebased onto main because the Windows arm64 build needs d6fb51df (see below). Only the last two commits are new here.

Problem

  • The JVM artifact has no native library. glsdk-jvm 0.5.0 on Maven Central contains no libglsdk for any platform, so the first SDK call on a desktop JVM fails with UnsatisfiedLinkError: Unable to load library 'glsdk'. The jvm() target is declared, but task build:kotlin never builds a desktop library.
  • The JVM classes target the JDK that builds them. jvm() sets no jvmTarget, so the bytecode version follows the JDK running Gradle. 0.5.0 happens to be Java 17; a JDK 25 machine produces Java 24 classes (major version 68).
  • Gradle and AGP are behind. Gradle 9.8.2 and AGP 9.5.1 are out, and lib/build.gradle.kts reads libraryVersion with the by project delegate that Gradle 9.6 deprecates.

Change

gl-sdk-android: Update Gradle to 9.8.2 and AGP to 9.5.1

  • Gradle 9.7.1 → 9.8.2, with the wrapper regenerated by 9.8.2 itself. AGP 9.4.1 → 9.5.1: AGP 9.5 needs Gradle 9.6.0 or newer and fixes the 7 Configuration.setVisible() deprecations AGP 9.4.1 triggers on Gradle 9.8.
  • libraryVersion is read with project.property().
  • Kotlin stays at 2.2.21, as KT-88548 is fixed only in Kotlin 2.5.0 (still RC). Every Gradle deprecation warning left comes from the Kotlin Gradle plugin 2.2.21. The other dependencies are at their latest stable or held back by Kotlin.

gl-sdk: Bundle the desktop libraries in the jvm jar

  • A jvm task, which kotlin depends on, builds libglsdk for macOS, Linux and Windows on x86_64 and arm64, in release and unwinding on panic through gl-sdk: build the Android libraries in release mode, and unwind on panic on iOS too #762's sdk_env. It puts each in jvmMain/resources/<platform>/, where JNA looks for it.
  • macOS: the dylibs are stripped with Apple's /usr/bin/strip -x instead of cargo's rust-objcopy. In rustc 1.91, rust-objcopy does not pad the string table to 8 bytes after an indirect symbol table with an odd number of entries, and dyld on macOS 27 refuses such a dylib. LWK hit this with 291 entries; gl-sdk has 280 today, so it only loads by luck. The full path matters: the strip first on PATH can be llvm-strip, with the same bug, or GNU strip, which doesn't handle Mach-O.
  • Linux: cross compiled from macOS with cargo-zigbuild 0.24.0 against glibc 2.17.
  • Windows: cross compiled with cargo-xwin 0.23.1, linking the C runtime statically. The +crt-static flag is appended to RUSTFLAGS, because cargo-xwin, like cargo, ignores the target rustflags when RUSTFLAGS is set, and a caller's RUSTFLAGS would drop it. The MSVC CRT (14.44.17.14) and Windows SDK (10.0.26100) are pinned, each version pair in its own cache directory, the one LWK uses too: cargo-xwin reuses an existing cache that has the needed arches whatever its versions, which would ignore a plain pin. On arm64, ring compiles its C code with plain clang, which can't read the clang-cl /imsvc flags cargo-xwin passes the SDK headers with. The headers go in INCLUDE instead, and only CFLAGS loses the flags, since CXXFLAGS keeps /EHsc, which bitcoinconsensus needs. This build needs ring 0.17: ring 0.16, which main dropped in d6fb51df, has no aarch64-pc-windows-msvc support.
  • The two Windows builds run one after the other. Every cargo-xwin run recreates the clang-cl symlink in its shared cache, so Task's parallel deps made them fail with Failed to setup clang-cl symlink ... File exists.
  • The jvm classes target Java 17 and compile against its API (-Xjdk-release=17). The jvm tests run with --enable-native-access=ALL-UNNAMED, because Java 24 or later warns when JNA calls System::load. The README tells applications to pass the same flag.
  • CI: the GitHub workflow runs :lib:jvmTest and builds with Rust 1.91.0, since cargo-xwin needs 1.89 or later. Both CI jobs get the desktop targets, zig, and the llvm-tools component: cargo-xwin links its llvm-ar as llvm-lib, the archiver it gives cc-rs, and silently skips the link without it, so ring's build script fails with failed to find tool "llvm-lib". The GitLab job's before_script expects rustup and brew on the m4 runner, as LWK's does; I can't check that runner.
  • Changelog, README section on building the Kotlin Multiplatform package, and *.dll in .gitignore.

This mirrors LWK's desktop support, without its simplicity-sys NDEBUG workaround and with a narrower Windows arm64 recipe, as LWK also had to handle psm and aws-lc.

Testing

macOS 27 on arm64, rustc 1.98.1:

Check Result
task build:kotlin Exits 0 and produces the Android, iOS and six desktop libraries
task build:jvm, cached, 5 runs 4 failed with concurrent Windows builds, 0 with them in sequence
:lib:jvmTest, JDK 25 Fails on main with UnsatisfiedLinkError, passes here with no native-access warning
CommonTest against the built lib-jvm.jar, eclipse-temurin:17-jre on linux/arm64 and linux/amd64 Passes
darwin-x86-64 dylib under Rosetta Loads, ffi_glsdk_uniffi_contract_version returns 29
Linux libraries Highest symbol version GLIBC_2.17; they need only libc, libm, libdl and libpthread
Windows DLLs Import only Windows system DLLs, no VCRUNTIME140, ucrtbase or api-ms-win-crt-*
Windows DLLs built with a caller RUSTFLAGS="--cfg glsdk_probe" Keep +crt-static and the caller's flag. Before appending to RUSTFLAGS, both imported VCRUNTIME140, MSVCP140 and api-ms-win-crt-*
Pinned vs unpinned Windows DLLs .text identical; about 110 bytes of build metadata differ: generated-source paths in panic locations, whose hash follows the rustflags, the linker timestamp and the PDB GUID
All six libraries Export the same 124 uniffi_glsdk symbols
macOS dylib with cargo's default strip, rustc 1.91.0 and 1.98.1 Both load today: 280 indirect symbols, string table 8-byte aligned
:lib:assemble Succeeds, no compiler warnings
:lib:iosSimulatorArm64Test 2/2 pass
:lib:connectedAndroidDeviceTest, arm64 emulator 39/39 pass
publishToMavenLocal, into a scratch repository glsdk-jvm-0.5.0.jar contains all six libraries
  • The JVM, iOS simulator and Android device tests pass both with this branch's release libraries from task build:kotlin and with the libraries built from main.
  • On both this branch and main, the Android device tests print Unable to strip ... libglsdk.so (AGP's default NDK isn't installed) and, on JDK 25, a sun.misc.Unsafe warning from protobuf in AGP's test runner.
  • The Windows x86_64 link prints lld-link LNK4099 warnings: the static CRT's objects reference debug symbol files on Microsoft's build machines (D:\a\_work\...\libcmt.amd64.pdb). They don't affect the DLL.
  • With a wrong or delisted CRT/SDK version, cargo-xwin fails its setup and retries it. Here a retry twice hung on aka.ms with no timeout, while LWK's runs failed with unable to locate SDK. If Microsoft drops the pinned versions, CI may hang until its job timeout rather than fail.
  • Not tested: the Windows DLLs at runtime, a system with glibc 2.17 (only the symbol versions were checked), and the GitLab m4 runner.

The workspace's release profile sets panic = "abort", so on iOS a Rust
panic in the SDK aborts the app instead of reaching Kotlin or Swift as
an exception. The Android, iOS and iOS simulator tasks override it with
CARGO_PROFILE_RELEASE_PANIC=unwind. The Android libraries are debug
builds and already unwind; the override keeps them unwinding once they
build in release.
The Android tasks ran `cargo ndk ... build` without `--release`, so the
Kotlin package shipped dev-profile libraries: unoptimized, with debug
assertions, and almost three times the size of a release build
(arm64-v8a libglsdk.so: 29.9 MB, 11.0 MB in release). The iOS tasks
already build in release.

The Kotlin bindings are generated from the release arm64 library, which
yields the same Kotlin as the debug one.
- AGP 9.5 requires Gradle 9.6.0 or newer and fixes the
  Configuration.setVisible() deprecation warnings that AGP 9.4.1
  triggers on Gradle 9.8
- Read libraryVersion with project.property(): Gradle 9.6 deprecates
  the 'val name: Type by project' delegate, which Gradle 10 removes

Kotlin stays at 2.2.21, as KT-88548 is fixed only in Kotlin 2.5.0. The
Gradle deprecation warnings left come from its Gradle plugin.
@angelix
angelix force-pushed the ave-native-support branch 2 times, most recently from 3fb7a26 to ed105e6 Compare November 13, 2026 15:15
The glsdk-jvm artifact shipped without a native library, so loading
the SDK on a desktop JVM failed with UnsatisfiedLinkError.

- Build the library for macOS, Linux and Windows, each on x86_64 and
  arm64, in release mode and unwinding on panic like the mobile
  libraries, and put each in the jvm resources directory where JNA
  looks for it
- Strip the macOS dylibs with Apple's /usr/bin/strip instead of
  cargo's rust-objcopy, which in rustc 1.91 does not pad the string
  table to 8 bytes after an indirect symbol table with an odd number
  of entries: dyld on macOS 27 refuses such a dylib (seen in LWK, 291
  entries). gl-sdk has 280 entries today, so it only loads by luck.
  The strip first on PATH can be llvm-strip, with the same bug, or GNU
  strip, without Mach-O support. Stripping the local symbols too
  almost halves the arm64 dylib.
- Cross compile Linux from macOS with cargo-zigbuild against glibc
  2.17, so the publish job keeps running on a single runner. The
  libraries depend only on libc, libm, libdl and libpthread, and the
  newer glibc functions Rust's std uses are weak symbols.
- Cross compile Windows with cargo-xwin and link the C runtime
  statically: the DLLs depend only on Windows system DLLs and load
  without the Visual C++ Redistributable. The flag is appended to
  RUSTFLAGS, as cargo-xwin, like cargo, ignores the target rustflags
  when RUSTFLAGS is set, and a caller's RUSTFLAGS would drop it.
- Pin the MSVC CRT (14.44.17.14) and Windows SDK (10.0.26100)
  cargo-xwin downloads, in a cache directory per version pair, the one
  LWK's build uses too: cargo-xwin reuses an existing cache that has
  the needed arches whatever its versions, which would ignore the pin
- On Windows arm64 ring compiles its C code with clang, which reads
  the clang-cl /imsvc flags cargo-xwin passes the SDK headers with as
  file names. The headers go in INCLUDE instead, which clang and
  clang-cl both search, and only CFLAGS loses the flags: CXXFLAGS
  keeps /EHsc, without which bitcoinconsensus fails to compile.
- cargo xwin env folds RUSTFLAGS into the target rustflags and exports
  an empty RUSTFLAGS, which cargo would use instead: unset it. Its
  output is assigned before the eval, so a failing cargo xwin env
  stops the build.
- Build the two Windows libraries one after the other: every
  cargo-xwin run recreates the clang-cl symlink in its shared cache,
  and on a fresh cache unpacks the SDK there, so two runs at once
  fail with "Failed to setup clang-cl symlink" or "failed to splat"
- Install cargo-zigbuild and cargo-xwin under target/cross-tools once
  per build. cargo-zigbuild needs zig, which the CI jobs install, the
  GitLab one only when the runner lacks it. cargo-xwin needs the
  llvm-tools component, which both CI jobs install: it links its
  llvm-ar as llvm-lib, the archiver it gives cc-rs, and silently skips
  the link without it, so ring's build script fails to find llvm-lib.
- Target Java 17 and compile against its API: the classes otherwise
  get the bytecode version of the JDK running Gradle, Java 24 on a
  JDK 25 machine
- Pass --enable-native-access=ALL-UNNAMED to the jvm tests: on Java 24
  or later JNA loading the library with System::load, a restricted
  method, prints a warning. A library jar cannot grant native access
  itself, so the README tells applications to pass the flag.
- Run the jvm tests in the GitHub workflow and build the Kotlin
  bindings there with Rust 1.91.0, as cargo-xwin needs 1.89 or later.
  Add the desktop targets to both CI jobs building the bindings.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant