diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index a765d20..72baa92 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -21,6 +21,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.event.release.tag_name }} - name: Set up JDK 17 uses: actions/setup-java@v4 with: @@ -32,6 +34,22 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + - name: Install native build dependencies + run: sudo apt-get update && sudo apt-get install -y pkg-config protobuf-compiler + + - name: Set up Android SDK + uses: android-actions/setup-android@v3 + + - name: Set up Android NDK + id: setup-ndk + uses: nttld/setup-ndk@v1 + with: + ndk-version: r28c + add-to-path: true + + - name: Export Android NDK root + run: echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> "$GITHUB_ENV" + - name: Extract version from input or tag id: version shell: bash @@ -42,6 +60,9 @@ jobs: fi echo "version=${VERSION#v}" >> $GITHUB_OUTPUT + - name: Generate Android bindings + run: ./build_android.sh + - name: Build with Gradle working-directory: bindings/android run: ./gradlew build -Pversion=${{ steps.version.outputs.version }} @@ -50,6 +71,6 @@ jobs: working-directory: bindings/android env: GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TOKEN: ${{ secrets.ORG_PACKAGES_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} GITHUB_REPO: ${{ github.repository }} run: ./gradlew publish -Pversion=${{ steps.version.outputs.version }} diff --git a/Cargo.lock b/Cargo.lock index f5bd9f7..e3d83a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2013,7 +2013,7 @@ dependencies = [ [[package]] name = "vss-rust-client-ffi" -version = "0.5.13" +version = "0.5.17" dependencies = [ "bip39", "bitcoin", diff --git a/Cargo.toml b/Cargo.toml index 1e06238..4895b2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vss-rust-client-ffi" -version = "0.5.13" +version = "0.5.17" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/Package.swift b/Package.swift index 5d214f9..55986fc 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.5.13" -let checksum = "5b0143f05a1f49c580b8442b43e18293c5a7af6e97071221ca4899877898a1ad" +let tag = "v0.5.17" +let checksum = "92c01c752f505d47e22a8ea7b4b3c6ec47b60a9dd877459d9c628d743ce723bf" let url = "https://github.com/synonymdev/vss-rust-client-ffi/releases/download/\(tag)/VssRustClientFfi.xcframework.zip" let package = Package( diff --git a/bindings/android/README.md b/bindings/android/README.md index 1be3ab5..65fef59 100644 --- a/bindings/android/README.md +++ b/bindings/android/README.md @@ -86,6 +86,9 @@ Or trigger the "Gradle Publish" workflow manually. ### Terminal ```sh -cd bindings/android +./build_android.sh +cd ./bindings/android ./gradlew publish -Pversion=0.1.0 ``` + +Run `./build_android.sh` before any direct Gradle publish so `jniLibs` is regenerated with native debug metadata and 16 KB page-size alignment. diff --git a/bindings/android/build.gradle.kts b/bindings/android/build.gradle.kts index 4afb9d1..a3a27b8 100644 --- a/bindings/android/build.gradle.kts +++ b/bindings/android/build.gradle.kts @@ -1,4 +1,5 @@ -import java.util.Properties +import java.io.ByteArrayOutputStream +import java.io.File plugins { id("com.android.library") version "8.5.2" @@ -31,6 +32,11 @@ android { kotlinOptions { jvmTarget = "11" } + packaging { + jniLibs { + keepDebugSymbols += listOf("**/libvss_rust_client_ffi.so") + } + } publishing { singleVariant("release") { withSourcesJar() @@ -44,6 +50,104 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") } +val androidNativeAbis = listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") + +fun executableFromPath(name: String): String? { + return System.getenv("PATH") + ?.split(File.pathSeparator) + ?.asSequence() + ?.map { File(it, name) } + ?.firstOrNull { it.canExecute() } + ?.absolutePath +} + +fun findReadelf(): String { + executableFromPath("llvm-readelf")?.let { return it } + executableFromPath("readelf")?.let { return it } + + return listOf("ANDROID_NDK_ROOT", "ANDROID_NDK_HOME", "NDK_HOME") + .mapNotNull { System.getenv(it) } + .map { File(it, "toolchains/llvm/prebuilt") } + .firstNotNullOfOrNull { prebuiltDir -> + if (!prebuiltDir.isDirectory) return@firstNotNullOfOrNull null + + prebuiltDir + .walkTopDown() + .firstOrNull { it.name == "llvm-readelf" && it.canExecute() } + ?.absolutePath + } + ?: throw GradleException( + "llvm-readelf or readelf is required to validate Android native debug symbols" + ) +} + +fun Project.runReadelf(readelf: String, vararg args: String): Pair { + val stdout = ByteArrayOutputStream() + val stderr = ByteArrayOutputStream() + val result = exec { + commandLine(readelf, *args) + standardOutput = stdout + errorOutput = stderr + isIgnoreExitValue = true + } + + return result.exitValue to stdout.toString().ifBlank { stderr.toString() } +} + +fun String.parseElfAlignment(): Long { + return if (startsWith("0x")) { + removePrefix("0x").toLong(16) + } else { + toLong() + } +} + +val validateReleaseNativeLibraries by tasks.registering { + group = "verification" + description = "Validates release JNI libraries keep full DWARF metadata and 16 KB LOAD alignment." + + doLast { + val readelf = findReadelf() + val loadAlignmentRegex = Regex("""^\s*LOAD\s+.*\s+(0x[0-9a-fA-F]+|\d+)\s*$""") + + androidNativeAbis.forEach { abi -> + val lib = layout.projectDirectory.file("src/main/jniLibs/$abi/libvss_rust_client_ffi.so").asFile + if (!lib.isFile) { + throw GradleException("Android native library missing at '${lib.path}'") + } + + val (sectionsExit, sections) = runReadelf(readelf, "-S", lib.absolutePath) + if (sectionsExit != 0 || !Regex("""\.debug_info""").containsMatchIn(sections)) { + throw GradleException("Android native library has no .debug_info DWARF metadata: '${lib.path}'") + } + + val wideHeaders = runReadelf(readelf, "-W", "-l", lib.absolutePath) + val headers = if (wideHeaders.first == 0) { + wideHeaders.second + } else { + val fallbackHeaders = runReadelf(readelf, "-l", lib.absolutePath) + if (fallbackHeaders.first != 0) { + throw GradleException("Unable to inspect Android native library headers: '${lib.path}'") + } + fallbackHeaders.second + } + + val alignments = headers + .lineSequence() + .mapNotNull { loadAlignmentRegex.matchEntire(it)?.groupValues?.get(1)?.parseElfAlignment() } + .toList() + + if (alignments.isEmpty() || alignments.any { it < 16_384 }) { + throw GradleException("Android native library is not 16 KB page-size aligned: '${lib.path}'") + } + } + } +} + +tasks.matching { it.name == "bundleReleaseAar" || it.name.startsWith("publish") }.configureEach { + dependsOn(validateReleaseNativeLibraries) +} + afterEvaluate { publishing { publications { diff --git a/bindings/android/gradle.properties b/bindings/android/gradle.properties index d4bab37..acbca45 100644 --- a/bindings/android/gradle.properties +++ b/bindings/android/gradle.properties @@ -3,4 +3,4 @@ android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official # project settings: -version=0.5.12 +version=0.5.17 diff --git a/bindings/android/src/main/jniLibs/arm64-v8a/libvss_rust_client_ffi.so b/bindings/android/src/main/jniLibs/arm64-v8a/libvss_rust_client_ffi.so index 86d73c6..e1bb02b 100755 Binary files a/bindings/android/src/main/jniLibs/arm64-v8a/libvss_rust_client_ffi.so and b/bindings/android/src/main/jniLibs/arm64-v8a/libvss_rust_client_ffi.so differ diff --git a/bindings/android/src/main/jniLibs/armeabi-v7a/libvss_rust_client_ffi.so b/bindings/android/src/main/jniLibs/armeabi-v7a/libvss_rust_client_ffi.so index 4b99730..c6fae6c 100755 Binary files a/bindings/android/src/main/jniLibs/armeabi-v7a/libvss_rust_client_ffi.so and b/bindings/android/src/main/jniLibs/armeabi-v7a/libvss_rust_client_ffi.so differ diff --git a/bindings/android/src/main/jniLibs/x86/libvss_rust_client_ffi.so b/bindings/android/src/main/jniLibs/x86/libvss_rust_client_ffi.so index c9f8f28..7825641 100755 Binary files a/bindings/android/src/main/jniLibs/x86/libvss_rust_client_ffi.so and b/bindings/android/src/main/jniLibs/x86/libvss_rust_client_ffi.so differ diff --git a/bindings/android/src/main/jniLibs/x86_64/libvss_rust_client_ffi.so b/bindings/android/src/main/jniLibs/x86_64/libvss_rust_client_ffi.so index 5aaef88..665104c 100755 Binary files a/bindings/android/src/main/jniLibs/x86_64/libvss_rust_client_ffi.so and b/bindings/android/src/main/jniLibs/x86_64/libvss_rust_client_ffi.so differ diff --git a/bindings/android/src/main/kotlin/com/synonym/vssclient/vss_rust_client_ffi.kt b/bindings/android/src/main/kotlin/com/synonym/vssclient/vss_rust_client_ffi.kt index abbf49d..ad51481 100644 --- a/bindings/android/src/main/kotlin/com/synonym/vssclient/vss_rust_client_ffi.kt +++ b/bindings/android/src/main/kotlin/com/synonym/vssclient/vss_rust_client_ffi.kt @@ -757,20 +757,97 @@ internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { ) } +// For large crates we prevent `MethodTooLargeException` (see #2340) +// N.B. the name of the extension is very misleading, since it is +// rather `InterfaceTooLargeException`, caused by too many methods +// in the interface for large crates. +// +// By splitting the otherwise huge interface into two parts +// * UniffiLib +// * IntegrityCheckingUniffiLib (this) +// we allow for ~2x as many methods in the UniffiLib interface. +// +// The `ffi_uniffi_contract_version` method and all checksum methods are put +// into `IntegrityCheckingUniffiLib` and these methods are called only once, +// when the library is loaded. +internal interface IntegrityCheckingUniffiLib : Library { + // Integrity check functions only + fun uniffi_vss_rust_client_ffi_checksum_func_vss_delete(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_derive_store_id(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_get(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_delete(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_get(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_list_all_keys(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_list_keys(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_store(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_list(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_list_keys(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_new_client(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_new_client_with_lnurl_auth(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_new_ldk_client_with_lnurl_auth(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_put_with_key_prefix(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_shutdown_client(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_shutdown_ldk_client(): Short + + fun uniffi_vss_rust_client_ffi_checksum_func_vss_store(): Short + + fun ffi_vss_rust_client_ffi_uniffi_contract_version(): Int +} + // A JNA Library to expose the extern-C FFI definitions. // This is an implementation detail which will be called internally by the public API. - internal interface UniffiLib : Library { companion object { internal val INSTANCE: UniffiLib by lazy { - loadIndirect(componentName = "vss_rust_client_ffi") - .also { lib: UniffiLib -> + val componentName = "vss_rust_client_ffi" + // For large crates we prevent `MethodTooLargeException` (see #2340) + // N.B. the name of the extension is very misleading, since it is + // rather `InterfaceTooLargeException`, caused by too many methods + // in the interface for large crates. + // + // By splitting the otherwise huge interface into two parts + // * UniffiLib (this) + // * IntegrityCheckingUniffiLib + // And all checksum methods are put into `IntegrityCheckingUniffiLib` + // we allow for ~2x as many methods in the UniffiLib interface. + // + // Thus we first load the library with `loadIndirect` as `IntegrityCheckingUniffiLib` + // so that we can (optionally!) call `uniffiCheckApiChecksums`... + loadIndirect(componentName) + .also { lib: IntegrityCheckingUniffiLib -> uniffiCheckContractApiVersion(lib) uniffiCheckApiChecksums(lib) } + // ... and then we load the library as `UniffiLib` + // N.B. we cannot use `loadIndirect` once and then try to cast it to `UniffiLib` + // => results in `java.lang.ClassCastException: com.sun.proxy.$Proxy cannot be cast to ...` + // error. So we must call `loadIndirect` twice. For crates large enough + // to trigger this issue, the performance impact is negligible, running on + // a macOS M1 machine the `loadIndirect` call takes ~50ms. + val lib = loadIndirect(componentName) + // No need to check the contract version and checksums, since + // we already did that with `IntegrityCheckingUniffiLib` above. + // Loading of library with integrity check done. + lib } } + // FFI functions fun uniffi_vss_rust_client_ffi_fn_func_vss_delete(`key`: RustBuffer.ByValue): Long fun uniffi_vss_rust_client_ffi_fn_func_vss_derive_store_id( @@ -1053,47 +1130,11 @@ internal interface UniffiLib : Library { `handle`: Long, uniffi_out_err: UniffiRustCallStatus, ): Unit - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_delete(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_derive_store_id(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_get(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_delete(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_get(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_list_all_keys(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_list_keys(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_ldk_store(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_list(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_list_keys(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_new_client(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_new_client_with_lnurl_auth(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_new_ldk_client_with_lnurl_auth(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_put_with_key_prefix(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_shutdown_client(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_shutdown_ldk_client(): Short - - fun uniffi_vss_rust_client_ffi_checksum_func_vss_store(): Short - - fun ffi_vss_rust_client_ffi_uniffi_contract_version(): Int } -private fun uniffiCheckContractApiVersion(lib: UniffiLib) { +private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) { // Get the bindings contract version from our ComponentInterface - val bindings_contract_version = 26 + val bindings_contract_version = 29 // Get the scaffolding contract version by calling the into the dylib val scaffolding_contract_version = lib.ffi_vss_rust_client_ffi_uniffi_contract_version() if (bindings_contract_version != scaffolding_contract_version) { @@ -1102,7 +1143,7 @@ private fun uniffiCheckContractApiVersion(lib: UniffiLib) { } @Suppress("UNUSED_PARAMETER") -private fun uniffiCheckApiChecksums(lib: UniffiLib) { +private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { if (lib.uniffi_vss_rust_client_ffi_checksum_func_vss_delete() != 13005.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1156,6 +1197,13 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { } } +/** + * @suppress + */ +public fun uniffiEnsureInitialized() { + UniffiLib.INSTANCE +} + // Async support // Async return type handlers @@ -1217,9 +1265,38 @@ interface Disposable { companion object { fun destroy(vararg args: Any?) { - args - .filterIsInstance() - .forEach(Disposable::destroy) + for (arg in args) { + when (arg) { + is Disposable -> { + arg.destroy() + } + + is ArrayList<*> -> { + for (idx in arg.indices) { + val element = arg[idx] + if (element is Disposable) { + element.destroy() + } + } + } + + is Map<*, *> -> { + for (element in arg.values) { + if (element is Disposable) { + element.destroy() + } + } + } + + is Iterable<*> -> { + for (element in arg) { + if (element is Disposable) { + element.destroy() + } + } + } + } + } } } } diff --git a/bindings/ios/VssRustClientFfi.xcframework.zip b/bindings/ios/VssRustClientFfi.xcframework.zip index 6de7927..2a2d103 100644 Binary files a/bindings/ios/VssRustClientFfi.xcframework.zip and b/bindings/ios/VssRustClientFfi.xcframework.zip differ diff --git a/bindings/ios/VssRustClientFfi.xcframework/ios-arm64-simulator/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI b/bindings/ios/VssRustClientFfi.xcframework/ios-arm64-simulator/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI index c021287..419fa44 100644 Binary files a/bindings/ios/VssRustClientFfi.xcframework/ios-arm64-simulator/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI and b/bindings/ios/VssRustClientFfi.xcframework/ios-arm64-simulator/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI differ diff --git a/bindings/ios/VssRustClientFfi.xcframework/ios-arm64/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI b/bindings/ios/VssRustClientFfi.xcframework/ios-arm64/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI index cb44d69..bcf21a6 100644 Binary files a/bindings/ios/VssRustClientFfi.xcframework/ios-arm64/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI and b/bindings/ios/VssRustClientFfi.xcframework/ios-arm64/vss_rust_client_ffiFFI.framework/vss_rust_client_ffiFFI differ diff --git a/bindings/python/vss_rust_client_ffi/libvss_rust_client_ffi.dylib b/bindings/python/vss_rust_client_ffi/libvss_rust_client_ffi.dylib index f8286a5..c92e99e 100755 Binary files a/bindings/python/vss_rust_client_ffi/libvss_rust_client_ffi.dylib and b/bindings/python/vss_rust_client_ffi/libvss_rust_client_ffi.dylib differ diff --git a/bindings/python/vss_rust_client_ffi/vss_rust_client_ffi.py b/bindings/python/vss_rust_client_ffi/vss_rust_client_ffi.py index e9cf79a..ab4d317 100644 --- a/bindings/python/vss_rust_client_ffi/vss_rust_client_ffi.py +++ b/bindings/python/vss_rust_client_ffi/vss_rust_client_ffi.py @@ -454,7 +454,7 @@ def _uniffi_load_indirect(): def _uniffi_check_contract_api_version(lib): # Get the bindings contract version from our ComponentInterface - bindings_contract_version = 26 + bindings_contract_version = 29 # Get the scaffolding contract version by calling the into the dylib scaffolding_contract_version = lib.ffi_vss_rust_client_ffi_uniffi_contract_version() if bindings_contract_version != scaffolding_contract_version: @@ -1262,7 +1262,7 @@ def __str__(self): return "LdkNamespace.DEFAULT()".format() def __eq__(self, other): - if not other.is_default(): + if not other.is_DEFAULT(): return False return True @@ -1275,7 +1275,7 @@ def __str__(self): return "LdkNamespace.MONITORS()".format() def __eq__(self, other): - if not other.is_monitors(): + if not other.is_MONITORS(): return False return True @@ -1289,7 +1289,7 @@ def __str__(self): return "LdkNamespace.MONITOR_UPDATES(monitor_id={})".format(self.monitor_id) def __eq__(self, other): - if not other.is_monitor_updates(): + if not other.is_MONITOR_UPDATES(): return False if self.monitor_id != other.monitor_id: return False @@ -1304,20 +1304,28 @@ def __str__(self): return "LdkNamespace.ARCHIVED_MONITORS()".format() def __eq__(self, other): - if not other.is_archived_monitors(): + if not other.is_ARCHIVED_MONITORS(): return False return True - # For each variant, we have an `is_NAME` method for easily checking + # For each variant, we have `is_NAME` and `is_name` methods for easily checking # whether an instance is that variant. + def is_DEFAULT(self) -> bool: + return isinstance(self, LdkNamespace.DEFAULT) def is_default(self) -> bool: return isinstance(self, LdkNamespace.DEFAULT) + def is_MONITORS(self) -> bool: + return isinstance(self, LdkNamespace.MONITORS) def is_monitors(self) -> bool: return isinstance(self, LdkNamespace.MONITORS) + def is_MONITOR_UPDATES(self) -> bool: + return isinstance(self, LdkNamespace.MONITOR_UPDATES) def is_monitor_updates(self) -> bool: return isinstance(self, LdkNamespace.MONITOR_UPDATES) + def is_ARCHIVED_MONITORS(self) -> bool: + return isinstance(self, LdkNamespace.ARCHIVED_MONITORS) def is_archived_monitors(self) -> bool: return isinstance(self, LdkNamespace.ARCHIVED_MONITORS) @@ -1354,27 +1362,27 @@ def read(buf): @staticmethod def check_lower(value): - if value.is_default(): + if value.is_DEFAULT(): return - if value.is_monitors(): + if value.is_MONITORS(): return - if value.is_monitor_updates(): + if value.is_MONITOR_UPDATES(): _UniffiConverterString.check_lower(value.monitor_id) return - if value.is_archived_monitors(): + if value.is_ARCHIVED_MONITORS(): return raise ValueError(value) @staticmethod def write(value, buf): - if value.is_default(): + if value.is_DEFAULT(): buf.write_i32(1) - if value.is_monitors(): + if value.is_MONITORS(): buf.write_i32(2) - if value.is_monitor_updates(): + if value.is_MONITOR_UPDATES(): buf.write_i32(3) _UniffiConverterString.write(value.monitor_id, buf) - if value.is_archived_monitors(): + if value.is_ARCHIVED_MONITORS(): buf.write_i32(4) @@ -1776,6 +1784,8 @@ def read(cls, buf): _UniffiConverterTypeVssItem.read(buf) for i in range(count) ] +# objects. + # Async support# RustFuturePoll values _UNIFFI_RUST_FUTURE_POLL_READY = 0 _UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 diff --git a/build_android.sh b/build_android.sh index a525440..0cb9711 100755 --- a/build_android.sh +++ b/build_android.sh @@ -24,17 +24,21 @@ cargo build # Temporarily set crate-type for Android (restored at end) cp Cargo.toml Cargo.toml.bak -sed -i '' 's/crate-type = .*/crate-type = ["cdylib"]/' Cargo.toml +sed -i.bak 's/crate-type = .*/crate-type = ["cdylib"]/' Cargo.toml +rm -f Cargo.toml.bak.bak trap 'mv Cargo.toml.bak Cargo.toml' EXIT # Build release echo "Building release version..." cargo build --release -# Install cargo-ndk if not already installed -if ! command -v cargo-ndk &> /dev/null; then - echo "Installing cargo-ndk..." - cargo install cargo-ndk +export CARGO_PROFILE_RELEASE_STRIP=false + +# Install the cargo-ndk version used by the mobile release scripts. +CARGO_NDK_VERSION="3.5.4" +if ! command -v cargo-ndk &> /dev/null || ! cargo ndk --version | grep -q "cargo-ndk $CARGO_NDK_VERSION"; then + echo "Installing cargo-ndk $CARGO_NDK_VERSION..." + cargo install cargo-ndk --version "$CARGO_NDK_VERSION" --locked --force fi # Check if Android NDK is available @@ -74,8 +78,122 @@ rustup target add \ # Build for all Android architectures echo "Building for Android architectures..." +export RUSTFLAGS="-C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" +find_readelf() { + if command -v llvm-readelf >/dev/null 2>&1; then + command -v llvm-readelf + return + fi + + if command -v readelf >/dev/null 2>&1; then + command -v readelf + return + fi + + for ndk_dir in "${ANDROID_NDK_ROOT:-}" "${ANDROID_NDK_HOME:-}" "${NDK_HOME:-}"; do + if [ -z "$ndk_dir" ] || [ ! -d "$ndk_dir/toolchains/llvm/prebuilt" ]; then + continue + fi + + ndk_readelf=$(find "$ndk_dir/toolchains/llvm/prebuilt" -path '*/bin/llvm-readelf' | head -n 1) + if [ -n "$ndk_readelf" ]; then + echo "$ndk_readelf" + return + fi + done + + echo "Error: llvm-readelf or readelf is required to validate Android native debug symbols" + exit 1 +} + +has_dwarf_debug_metadata() { + "$READELF_BIN" -S "$1" | grep -Eq '\.debug_info' +} + +readelf_program_headers() { + if "$READELF_BIN" -W -l "$1" >/dev/null 2>&1; then + "$READELF_BIN" -W -l "$1" + return + fi + + "$READELF_BIN" -l "$1" +} + +has_16kb_load_alignment() { + alignments=$(readelf_program_headers "$1" | awk '$1 == "LOAD" { print $NF }') + if [ -z "$alignments" ]; then + return 1 + fi + + while read -r alignment; do + if [ -z "$alignment" ]; then + continue + fi + + if [ "$((alignment))" -lt 16384 ]; then + return 1 + fi + done <