diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..b3865cb --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,27 @@ +[target.armv7-linux-androideabi] +rustflags = [ + "-C", "link-arg=-Wl,--allow-multiple-definition", + "-C", "link-arg=-Wl,-z,max-page-size=16384,-z,common-page-size=16384", +] + +[target.aarch64-linux-android] +rustflags = [ + "-C", "link-arg=-Wl,--allow-multiple-definition", + "-C", "link-arg=-Wl,-z,max-page-size=16384,-z,common-page-size=16384", +] + +[target.i686-linux-android] +rustflags = [ + "-C", "link-arg=-Wl,--allow-multiple-definition", + "-C", "link-arg=-Wl,-z,max-page-size=16384,-z,common-page-size=16384", +] + +[target.x86_64-linux-android] +rustflags = [ + "-C", "link-arg=-Wl,--allow-multiple-definition", + "-C", "link-arg=-Wl,-z,max-page-size=16384,-z,common-page-size=16384", +] + +[env] +OPENSSL_STATIC = "1" +OPENSSL_NO_VENDOR = "0" diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index c934597..a593f92 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -34,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 libdbus-1-dev 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 @@ -44,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 }} diff --git a/Cargo.lock b/Cargo.lock index ebb12cd..ab772a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "bitkitcore" -version = "0.1.68" +version = "0.1.70" dependencies = [ "android_logger", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index aed980a..bbda54a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitkitcore" -version = "0.1.68" +version = "0.1.70" edition = "2021" [lib] @@ -67,17 +67,6 @@ tempfile = "3.2" serial_test = "3.2.0" rust_decimal = "1.30" -[target.armv7-linux-androideabi] -rustflags = [ - "-C", "link-arg=-Wl,--allow-multiple-definition", -] - -[target.aarch64-linux-android] -rustflags = [ - "-C", "link-arg=-Wl,--allow-multiple-definition", -] - - [[bin]] name = "example" path = "example/main.rs" diff --git a/Package.swift b/Package.swift index 1f820b1..3163275 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.1.68" -let checksum = "cce415718a78a491fb37714d7ffb9a667787be6f5c0379236973ead2bde41f51" +let tag = "v0.1.70" +let checksum = "3717f97d4c184509f8093a87f6d8a5c4085d771264d789d1102ab6321fbb3ed6" let url = "https://github.com/synonymdev/bitkit-core/releases/download/\(tag)/BitkitCore.xcframework.zip" let package = Package( diff --git a/bindings/android/README.md b/bindings/android/README.md index b8b50bc..edd8b2d 100644 --- a/bindings/android/README.md +++ b/bindings/android/README.md @@ -85,6 +85,9 @@ Create a GitHub Release with a new tag like `v0.1.0`. The workflow `gradle-publi ### 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/gradle.properties b/bindings/android/gradle.properties index e7cf710..8dcd120 100644 --- a/bindings/android/gradle.properties +++ b/bindings/android/gradle.properties @@ -3,4 +3,4 @@ android.useAndroidX=true android.enableJetifier=true kotlin.code.style=official group=com.synonym -version=0.1.68 +version=0.1.70 diff --git a/bindings/android/lib/build.gradle.kts b/bindings/android/lib/build.gradle.kts index c75175d..f46a587 100644 --- a/bindings/android/lib/build.gradle.kts +++ b/bindings/android/lib/build.gradle.kts @@ -1,3 +1,6 @@ +import java.io.ByteArrayOutputStream +import java.io.File + plugins { id("com.android.library") kotlin("android") @@ -39,6 +42,12 @@ android { } } + packaging { + jniLibs { + keepDebugSymbols += "**/libbitkitcore.so" + } + } + publishing { singleVariant("release") { withSourcesJar() @@ -58,6 +67,104 @@ dependencies { api("org.slf4j:slf4j-api:1.7.36") } +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 debug 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/libbitkitcore.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("""\.(symtab|debug_|gnu_debugdata)""").containsMatchIn(sections)) { + throw GradleException("Android native library has no usable debug 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/ios/BitkitCore.xcframework.zip b/bindings/ios/BitkitCore.xcframework.zip index 497605f..5d3050e 100644 Binary files a/bindings/ios/BitkitCore.xcframework.zip and b/bindings/ios/BitkitCore.xcframework.zip differ diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a index be58e63..f67c8fe 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a differ diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a index 9289463..37a7154 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a differ diff --git a/build_android.sh b/build_android.sh index 24ead74..b42a5f0 100755 --- a/build_android.sh +++ b/build_android.sh @@ -62,6 +62,8 @@ fi # Set OpenSSL environment variables export OPENSSL_STATIC=1 export OPENSSL_NO_VENDOR=0 +export CARGO_PROFILE_RELEASE_DEBUG=2 +export CARGO_PROFILE_RELEASE_STRIP=false # Define output directories ANDROID_LIB_DIR="./bindings/android" @@ -72,6 +74,133 @@ JNILIBS_DIR="$ANDROID_LIB_DIR/lib/src/main/jniLibs" mkdir -p "$BASE_DIR" mkdir -p "$JNILIBS_DIR" +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_debug_metadata() { + "$READELF_BIN" -S "$1" | grep -Eq '\.(symtab|debug_|gnu_debugdata)' +} + +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 <&2 + exit 1 + ;; + esac +} + # Remove previous build echo "Removing previous build..." rm -rf "$BASE_DIR"/* @@ -81,18 +210,20 @@ rm -rf "$JNILIBS_DIR"/* echo "Building Rust libraries..." cargo build -# Modify Cargo.toml -echo "Updating Cargo.toml..." -sed -i '' 's/crate_type = .*/crate_type = ["cdylib"]/' Cargo.toml - # 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 +# 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 + +CARGO_NDK_NO_STRIP_ARGS=() +if cargo ndk --help 2>&1 | grep -q -- '--no-strip'; then + CARGO_NDK_NO_STRIP_ARGS+=(--no-strip) fi # Add Android targets @@ -107,6 +238,7 @@ rustup target add \ echo "Building for Android architectures..." cargo ndk \ -o "$JNILIBS_DIR" \ + "${CARGO_NDK_NO_STRIP_ARGS[@]}" \ --manifest-path ./Cargo.toml \ -t armeabi-v7a \ -t arm64-v8a \ @@ -114,9 +246,11 @@ cargo ndk \ -t x86_64 \ build --release +validate_android_symbols + # Generate Kotlin bindings echo "Generating Kotlin bindings..." -LIBRARY_PATH="./target/release/libbitkitcore.dylib" +LIBRARY_PATH=$(host_library_path) # Check if the library file exists if [ ! -f "$LIBRARY_PATH" ]; then @@ -163,5 +297,6 @@ rm -f "$ANDROID_LIB_DIR/gradle.properties.bak" # Verify android library publish echo "Testing android library publish to Maven Local..." "$ANDROID_LIB_DIR"/gradlew --project-dir "$ANDROID_LIB_DIR" clean publishToMavenLocal +validate_android_aar_symbols echo "Android build process completed successfully!" diff --git a/cargo/config.toml b/cargo/config.toml deleted file mode 100644 index 0b8cdf4..0000000 --- a/cargo/config.toml +++ /dev/null @@ -1,15 +0,0 @@ -[target.armv7-linux-androideabi] -rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] - -[target.aarch64-linux-android] -rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] - -[target.i686-linux-android] -rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] - -[target.x86_64-linux-android] -rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] - -[env] -OPENSSL_STATIC = "1" -OPENSSL_NO_VENDOR = "0" \ No newline at end of file