From 008aa9c17a500e35f3285787be23d38d4d3916a5 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 3 Jun 2026 21:55:36 +0200 Subject: [PATCH 01/10] chore: retain native debug symbols --- Justfile | 5 +++ app/build.gradle.kts | 1 + .../bitkit/build/NativeReleaseConfigTest.kt | 37 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt diff --git a/Justfile b/Justfile index a0b861ea27..2b56e83293 100644 --- a/Justfile +++ b/Justfile @@ -137,7 +137,12 @@ build task="assembleDevDebug": {{ gradle }} {{ task }} release: + #!/usr/bin/env sh + set -eu {{ gradle }} assembleMainnetRelease bundleMainnetRelease + symbols="app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip" + test -f "$symbols" + ls -lh "$symbols" install: {{ gradle }} installDevDebug diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e4871a8032..16ba9b0c9b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -230,6 +230,7 @@ android { ) signingConfig = signingConfigs.getByName("release") ndk { + debugSymbolLevel = "FULL" // noinspection ChromeOsAbiSupport abiFilters += listOf("armeabi-v7a", "arm64-v8a") } diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt new file mode 100644 index 0000000000..db775db8b2 --- /dev/null +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -0,0 +1,37 @@ +package to.bitkit.build + +import kotlin.io.path.Path +import kotlin.io.path.exists +import kotlin.io.path.readText +import kotlin.test.Test +import kotlin.test.assertTrue + +class NativeReleaseConfigTest { + + private val repoRoot = generateSequence( + Path(requireNotNull(System.getProperty("user.dir")) { "user.dir is required" }), + ) { it.parent } + .first { it.resolve("gradle/libs.versions.toml").exists() } + + @Test + fun `release build keeps full native debug symbols`() { + val buildFile = repoRoot.resolve("app/build.gradle.kts").readText() + + assertTrue( + buildFile.contains("""debugSymbolLevel = "FULL""""), + "Release builds must keep full native debug symbols for Play crash symbolication.", + ) + } + + @Test + fun `release recipe verifies native debug symbols archive`() { + val justfile = repoRoot.resolve("Justfile").readText() + + assertTrue( + justfile.contains( + """symbols="app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip"""", + ), + "Release builds must verify the native debug symbols archive before publishing.", + ) + } +} From dde2c29537a4532dd0f5a807b1d12cc2161005d3 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 4 Jun 2026 15:41:20 +0200 Subject: [PATCH 02/10] docs: clarify release symbols --- .agents/commands/release.md | 11 +++++++--- Justfile | 2 ++ README.md | 14 +++++++++++++ .../bitkit/build/NativeReleaseConfigTest.kt | 20 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/.agents/commands/release.md b/.agents/commands/release.md index 532790c65d..bb11a96486 100644 --- a/.agents/commands/release.md +++ b/.agents/commands/release.md @@ -210,16 +210,20 @@ just release Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk` Expected AAB path: `app/build/outputs/bundle/mainnetRelease/bitkit-mainnet-release-{newVersionCode}.aab` +Expected native debug symbols path: `app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip` -Verify both files exist. If the build fails, stop and report the error to the user. +Verify all three files exist. The native debug symbols file must be from the same `just release` build as the APK/AAB. Keep the filename `native-debug-symbols.zip`. If the build fails, stop and report the error to the user. -### 8. Upload APK to Draft Release +### 8. Upload APK and Native Symbols to Draft Release ```bash gh release upload v{newVersionName} \ - app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk + app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk \ + app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip ``` +For the Play Store release, upload the AAB as usual and verify Play Console shows native debug symbols for the exact version/build in App bundle explorer. If Play did not pick them up from the AAB, manually upload `native-debug-symbols.zip`. + ### 9. Return to Master ```bash @@ -236,6 +240,7 @@ Release branch: release-{newVersionName} Tag: v{newVersionName} Draft release: {release URL} APK uploaded: bitkit-mainnet-release-{newVersionCode}-universal.apk +Native debug symbols uploaded: native-debug-symbols.zip Store release notes: .ai/release-notes-{newVersionName}.md Next steps: diff --git a/Justfile b/Justfile index 2b56e83293..eb8f0005f3 100644 --- a/Justfile +++ b/Justfile @@ -142,6 +142,8 @@ release: {{ gradle }} assembleMainnetRelease bundleMainnetRelease symbols="app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip" test -f "$symbols" + echo "Native debug symbols: $symbols" + echo "Attach this exact file to GitHub releases and verify Play Console picked it up from the AAB." ls -lh "$symbols" install: diff --git a/README.md b/README.md index 8f1fd45e7a..730be681a7 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,20 @@ To build the mainnet flavor for release run: just release ``` +`just release` builds the mainnet APK, Play Store AAB, and the native debug symbols archive. + +Release artifacts: + +- APK: `app/build/outputs/apk/mainnet/release/` +- AAB: `app/build/outputs/bundle/mainnetRelease/` +- Native debug symbols: `app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip` + +The native debug symbols archive must come from the same `just release` build as the APK/AAB being published. Keep the filename `native-debug-symbols.zip`. + +For Play Store releases, upload the AAB as usual and verify Play Console shows native debug symbols for that exact version/build in App bundle explorer. If Play did not pick them up from the AAB, manually upload `native-debug-symbols.zip`. + +For GitHub releases, attach `native-debug-symbols.zip` alongside the APK so native crashes from GitHub-distributed builds can be symbolicated later. + #### Android App Bundle (AAB) `just release` builds both the mainnet APK and Play Store AAB. AAB is generated in `app/build/outputs/bundle/mainnetRelease/`. diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt index db775db8b2..fe3930fbcc 100644 --- a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -33,5 +33,25 @@ class NativeReleaseConfigTest { ), "Release builds must verify the native debug symbols archive before publishing.", ) + assertTrue( + justfile.contains("Attach this exact file to GitHub releases"), + "Release builds must tell the releaser to attach native debug symbols.", + ) + } + + @Test + fun `release command uploads native debug symbols archive`() { + val releaseCommand = repoRoot.resolve(".agents/commands/release.md").readText() + + assertTrue( + releaseCommand.contains( + "app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip", + ), + "Release command must include the native debug symbols archive path.", + ) + assertTrue( + releaseCommand.contains("Native debug symbols uploaded: native-debug-symbols.zip"), + "Release command summary must report the native debug symbols archive.", + ) } } From a677238d2107b8f71cbf05638c42c98dcd412214 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 4 Jun 2026 15:53:23 +0200 Subject: [PATCH 03/10] fix: create native symbols zip --- .agents/commands/release.md | 2 +- Justfile | 7 +- README.md | 2 +- .../bitkit/build/NativeReleaseConfigTest.kt | 32 +++++++-- scripts/create-native-debug-symbols.sh | 67 +++++++++++++++++++ 5 files changed, 100 insertions(+), 10 deletions(-) create mode 100755 scripts/create-native-debug-symbols.sh diff --git a/.agents/commands/release.md b/.agents/commands/release.md index bb11a96486..5bb9d05877 100644 --- a/.agents/commands/release.md +++ b/.agents/commands/release.md @@ -212,7 +212,7 @@ Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release Expected AAB path: `app/build/outputs/bundle/mainnetRelease/bitkit-mainnet-release-{newVersionCode}.aab` Expected native debug symbols path: `app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip` -Verify all three files exist. The native debug symbols file must be from the same `just release` build as the APK/AAB. Keep the filename `native-debug-symbols.zip`. If the build fails, stop and report the error to the user. +Verify all three files exist. The native debug symbols file must be from the same `just release` build as the APK/AAB. Keep the filename `native-debug-symbols.zip`. If Android Gradle Plugin cannot emit this archive because native dependency metadata is already stripped, `just release` creates it from the exact merged release `.so` files. If the build fails, stop and report the error to the user. ### 8. Upload APK and Native Symbols to Draft Release diff --git a/Justfile b/Justfile index eb8f0005f3..649d66ca94 100644 --- a/Justfile +++ b/Justfile @@ -139,12 +139,11 @@ build task="assembleDevDebug": release: #!/usr/bin/env sh set -eu - {{ gradle }} assembleMainnetRelease bundleMainnetRelease symbols="app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip" - test -f "$symbols" - echo "Native debug symbols: $symbols" + rm -f "$symbols" + {{ gradle }} assembleMainnetRelease bundleMainnetRelease + scripts/create-native-debug-symbols.sh echo "Attach this exact file to GitHub releases and verify Play Console picked it up from the AAB." - ls -lh "$symbols" install: {{ gradle }} installDevDebug diff --git a/README.md b/README.md index 730be681a7..317a0145f7 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ Release artifacts: - AAB: `app/build/outputs/bundle/mainnetRelease/` - Native debug symbols: `app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip` -The native debug symbols archive must come from the same `just release` build as the APK/AAB being published. Keep the filename `native-debug-symbols.zip`. +The native debug symbols archive must come from the same `just release` build as the APK/AAB being published. Keep the filename `native-debug-symbols.zip`. If Android Gradle Plugin cannot emit this archive because native dependency metadata is already stripped, `just release` creates it from the exact merged release `.so` files. For Play Store releases, upload the AAB as usual and verify Play Console shows native debug symbols for that exact version/build in App bundle explorer. If Play did not pick them up from the AAB, manually upload `native-debug-symbols.zip`. diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt index fe3930fbcc..368cd0eec7 100644 --- a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -14,12 +14,12 @@ class NativeReleaseConfigTest { .first { it.resolve("gradle/libs.versions.toml").exists() } @Test - fun `release build keeps full native debug symbols`() { + fun `release build requests full native debug symbols`() { val buildFile = repoRoot.resolve("app/build.gradle.kts").readText() assertTrue( buildFile.contains("""debugSymbolLevel = "FULL""""), - "Release builds must keep full native debug symbols for Play crash symbolication.", + "Release builds must request full native debug symbols for Play crash symbolication.", ) } @@ -29,9 +29,13 @@ class NativeReleaseConfigTest { assertTrue( justfile.contains( - """symbols="app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip"""", + """rm -f "${'$'}symbols"""", ), - "Release builds must verify the native debug symbols archive before publishing.", + "Release builds must remove stale native debug symbols before rebuilding.", + ) + assertTrue( + justfile.contains("scripts/create-native-debug-symbols.sh"), + "Release builds must create the native debug symbols archive before publishing.", ) assertTrue( justfile.contains("Attach this exact file to GitHub releases"), @@ -54,4 +58,24 @@ class NativeReleaseConfigTest { "Release command summary must report the native debug symbols archive.", ) } + + @Test + fun `native debug symbols script archives release libraries`() { + val symbolsScript = repoRoot.resolve("scripts/create-native-debug-symbols.sh").readText() + + assertTrue( + symbolsScript.contains( + "app/build/outputs/native-debug-symbols/${'$'}variant/native-debug-symbols.zip", + ), + "Native debug symbols script must write the canonical archive path.", + ) + assertTrue( + symbolsScript.contains("arm64-v8a armeabi-v7a"), + "Native debug symbols script must archive Play release ABIs.", + ) + assertTrue( + symbolsScript.contains("zip -qr"), + "Native debug symbols script must create a zip archive.", + ) + } } diff --git a/scripts/create-native-debug-symbols.sh b/scripts/create-native-debug-symbols.sh new file mode 100755 index 0000000000..bf35a0cec0 --- /dev/null +++ b/scripts/create-native-debug-symbols.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env sh +set -eu + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/.." && pwd) +cd "$repo_root" + +variant="mainnetRelease" +output="app/build/outputs/native-debug-symbols/$variant/native-debug-symbols.zip" +output_dir=$(dirname "$output") + +if [ -f "$output" ]; then + zip -T "$output" >/dev/null + echo "Native debug symbols: $output" + ls -lh "$output" + exit 0 +fi + +native_lib_dir="" +for candidate in "app/build/intermediates/merged_native_libs/$variant"/*/out/lib; do + if [ -d "$candidate" ]; then + native_lib_dir="$candidate" + break + fi +done + +if [ -z "$native_lib_dir" ]; then + echo "No merged native libraries found for '$variant'." >&2 + exit 1 +fi + +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +for abi in arm64-v8a armeabi-v7a; do + source_dir="$native_lib_dir/$abi" + if [ ! -d "$source_dir" ]; then + echo "Missing native libraries for '$abi' in '$native_lib_dir'." >&2 + exit 1 + fi + + mkdir -p "$tmp_dir/$abi" + found_lib=false + for lib in "$source_dir"/*.so; do + if [ -f "$lib" ]; then + cp "$lib" "$tmp_dir/$abi/" + found_lib=true + fi + done + + if [ "$found_lib" = false ]; then + echo "No native libraries found for '$abi' in '$source_dir'." >&2 + exit 1 + fi +done + +mkdir -p "$output_dir" +rm -f "$output" + +( + cd "$tmp_dir" + zip -qr "$repo_root/$output" arm64-v8a armeabi-v7a +) + +zip -T "$output" >/dev/null +echo "Native debug symbols: $output" +ls -lh "$output" From dbbbf1aa40653657d309274a1bb816666738fed6 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 4 Jun 2026 18:48:04 +0200 Subject: [PATCH 04/10] docs: clarify play symbol upload --- .agents/commands/release.md | 2 +- Justfile | 2 +- README.md | 4 +++- .../java/to/bitkit/build/NativeReleaseConfigTest.kt | 13 +++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.agents/commands/release.md b/.agents/commands/release.md index 5bb9d05877..27fd46768c 100644 --- a/.agents/commands/release.md +++ b/.agents/commands/release.md @@ -222,7 +222,7 @@ gh release upload v{newVersionName} \ app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip ``` -For the Play Store release, upload the AAB as usual and verify Play Console shows native debug symbols for the exact version/build in App bundle explorer. If Play did not pick them up from the AAB, manually upload `native-debug-symbols.zip`. +For the Play Store release, upload the AAB as usual, then upload `native-debug-symbols.zip` for the exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play Console lists the native debug symbols after upload. Do not rely on Play Console to download or recover this file later; GitHub releases or internal release storage must keep the release-built archive. ### 9. Return to Master diff --git a/Justfile b/Justfile index 649d66ca94..9af13b047a 100644 --- a/Justfile +++ b/Justfile @@ -143,7 +143,7 @@ release: rm -f "$symbols" {{ gradle }} assembleMainnetRelease bundleMainnetRelease scripts/create-native-debug-symbols.sh - echo "Attach this exact file to GitHub releases and verify Play Console picked it up from the AAB." + echo "Attach this exact file to GitHub releases and upload it to Play Console for this release." install: {{ gradle }} installDevDebug diff --git a/README.md b/README.md index 317a0145f7..9387b88dc8 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,9 @@ Release artifacts: The native debug symbols archive must come from the same `just release` build as the APK/AAB being published. Keep the filename `native-debug-symbols.zip`. If Android Gradle Plugin cannot emit this archive because native dependency metadata is already stripped, `just release` creates it from the exact merged release `.so` files. -For Play Store releases, upload the AAB as usual and verify Play Console shows native debug symbols for that exact version/build in App bundle explorer. If Play did not pick them up from the AAB, manually upload `native-debug-symbols.zip`. +For Play Store releases, upload the AAB as usual, then upload `native-debug-symbols.zip` for that exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play Console lists the native debug symbols after upload. + +Do not rely on Play Console to download or recover `native-debug-symbols.zip` later. Keep the release-built archive in GitHub releases or internal release storage. For GitHub releases, attach `native-debug-symbols.zip` alongside the APK so native crashes from GitHub-distributed builds can be symbolicated later. diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt index 368cd0eec7..63228e04c5 100644 --- a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -4,6 +4,7 @@ import kotlin.io.path.Path import kotlin.io.path.exists import kotlin.io.path.readText import kotlin.test.Test +import kotlin.test.assertFalse import kotlin.test.assertTrue class NativeReleaseConfigTest { @@ -41,6 +42,14 @@ class NativeReleaseConfigTest { justfile.contains("Attach this exact file to GitHub releases"), "Release builds must tell the releaser to attach native debug symbols.", ) + assertTrue( + justfile.contains("upload it to Play Console for this release"), + "Release builds must tell the releaser to upload native debug symbols to Play.", + ) + assertFalse( + justfile.contains("verify Play Console"), + "Release builds must not imply Play is the source of native debug symbols.", + ) } @Test @@ -57,6 +66,10 @@ class NativeReleaseConfigTest { releaseCommand.contains("Native debug symbols uploaded: native-debug-symbols.zip"), "Release command summary must report the native debug symbols archive.", ) + assertFalse( + releaseCommand.contains("Play " + "did not"), + "Release command must not use stale Play native symbol wording.", + ) } @Test From 4b415f62faeb1a0f2d08db65a499422f57c14f77 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 4 Jun 2026 18:57:54 +0200 Subject: [PATCH 05/10] fix: reject stripped native symbols --- .agents/commands/release.md | 4 +- Justfile | 2 +- README.md | 8 +- .../bitkit/build/NativeReleaseConfigTest.kt | 24 ++++- scripts/create-native-debug-symbols.sh | 97 ++++++++++++++++++- 5 files changed, 123 insertions(+), 12 deletions(-) diff --git a/.agents/commands/release.md b/.agents/commands/release.md index 27fd46768c..3ed078fd8e 100644 --- a/.agents/commands/release.md +++ b/.agents/commands/release.md @@ -212,7 +212,7 @@ Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release Expected AAB path: `app/build/outputs/bundle/mainnetRelease/bitkit-mainnet-release-{newVersionCode}.aab` Expected native debug symbols path: `app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip` -Verify all three files exist. The native debug symbols file must be from the same `just release` build as the APK/AAB. Keep the filename `native-debug-symbols.zip`. If Android Gradle Plugin cannot emit this archive because native dependency metadata is already stripped, `just release` creates it from the exact merged release `.so` files. If the build fails, stop and report the error to the user. +Verify all three files exist. The native debug symbols file must be from the same `just release` build as the APK/AAB. Keep the filename `native-debug-symbols.zip`. If Android Gradle Plugin cannot emit a usable archive because native dependency metadata is already stripped, `just release` fails instead of creating a placeholder zip from stripped `.so` files. Stop the release and publish or consume native dependencies with usable debug metadata first. ### 8. Upload APK and Native Symbols to Draft Release @@ -222,7 +222,7 @@ gh release upload v{newVersionName} \ app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip ``` -For the Play Store release, upload the AAB as usual, then upload `native-debug-symbols.zip` for the exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play Console lists the native debug symbols after upload. Do not rely on Play Console to download or recover this file later; GitHub releases or internal release storage must keep the release-built archive. +For the Play Store release, upload the AAB as usual, then upload `native-debug-symbols.zip` for the exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play lists the native debug symbols after upload. Do not rely on Play Console to download or recover this file later. If Play only shows delete/replace controls for an uploaded symbol file, that is enough for release verification; GitHub releases or internal release storage must keep the release-built archive. ### 9. Return to Master diff --git a/Justfile b/Justfile index 9af13b047a..aa85be1fde 100644 --- a/Justfile +++ b/Justfile @@ -143,7 +143,7 @@ release: rm -f "$symbols" {{ gradle }} assembleMainnetRelease bundleMainnetRelease scripts/create-native-debug-symbols.sh - echo "Attach this exact file to GitHub releases and upload it to Play Console for this release." + echo "Attach this exact file to GitHub releases, upload it to Play Console for this release, and verify Play lists it." install: {{ gradle }} installDevDebug diff --git a/README.md b/README.md index 9387b88dc8..5da0c3238c 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ To build the mainnet flavor for release run: just release ``` -`just release` builds the mainnet APK, Play Store AAB, and the native debug symbols archive. +`just release` builds the mainnet APK, Play Store AAB, and validates the native debug symbols archive. Release artifacts: @@ -184,11 +184,11 @@ Release artifacts: - AAB: `app/build/outputs/bundle/mainnetRelease/` - Native debug symbols: `app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip` -The native debug symbols archive must come from the same `just release` build as the APK/AAB being published. Keep the filename `native-debug-symbols.zip`. If Android Gradle Plugin cannot emit this archive because native dependency metadata is already stripped, `just release` creates it from the exact merged release `.so` files. +The native debug symbols archive must come from the same `just release` build as the APK/AAB being published. Keep the filename `native-debug-symbols.zip`. If Android Gradle Plugin cannot emit a usable archive because native dependency metadata is already stripped, `just release` fails instead of creating a placeholder zip from stripped `.so` files. Stop the release and publish or consume native dependencies with usable debug metadata first. -For Play Store releases, upload the AAB as usual, then upload `native-debug-symbols.zip` for that exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play Console lists the native debug symbols after upload. +For Play Store releases, upload the AAB as usual, then upload `native-debug-symbols.zip` for that exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play lists the native debug symbols after upload. -Do not rely on Play Console to download or recover `native-debug-symbols.zip` later. Keep the release-built archive in GitHub releases or internal release storage. +Do not rely on Play Console to download or recover `native-debug-symbols.zip` later. If Play only shows delete/replace controls for an uploaded symbol file, that is enough for release verification. Keep the release-built archive in GitHub releases or internal release storage. For GitHub releases, attach `native-debug-symbols.zip` alongside the APK so native crashes from GitHub-distributed builds can be symbolicated later. diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt index 63228e04c5..8782545365 100644 --- a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -47,7 +47,7 @@ class NativeReleaseConfigTest { "Release builds must tell the releaser to upload native debug symbols to Play.", ) assertFalse( - justfile.contains("verify Play Console"), + justfile.contains("download"), "Release builds must not imply Play is the source of native debug symbols.", ) } @@ -70,10 +70,18 @@ class NativeReleaseConfigTest { releaseCommand.contains("Play " + "did not"), "Release command must not use stale Play native symbol wording.", ) + assertTrue( + releaseCommand.contains("fails instead of creating a placeholder zip from stripped `.so` files"), + "Release command must fail instead of publishing fake native debug symbols.", + ) + assertTrue( + releaseCommand.contains("If Play only shows delete/replace controls"), + "Release command must document the verified Play Console behavior.", + ) } @Test - fun `native debug symbols script archives release libraries`() { + fun `native debug symbols script rejects stripped release libraries`() { val symbolsScript = repoRoot.resolve("scripts/create-native-debug-symbols.sh").readText() assertTrue( @@ -90,5 +98,17 @@ class NativeReleaseConfigTest { symbolsScript.contains("zip -qr"), "Native debug symbols script must create a zip archive.", ) + assertTrue( + symbolsScript.contains("""required_libs="libbitkitcore.so libldk_node.so""""), + "Native debug symbols script must validate crash-relevant native libraries.", + ) + assertTrue( + symbolsScript.contains("""grep -Eq '\.(symtab|debug_|gnu_debugdata)'"""), + "Native debug symbols script must validate usable debug metadata before zipping.", + ) + assertTrue( + symbolsScript.contains("Refusing to create '${'$'}output' from stripped native libraries."), + "Native debug symbols script must refuse placeholder archives.", + ) } } diff --git a/scripts/create-native-debug-symbols.sh b/scripts/create-native-debug-symbols.sh index bf35a0cec0..f1b722cbe5 100755 --- a/scripts/create-native-debug-symbols.sh +++ b/scripts/create-native-debug-symbols.sh @@ -8,9 +8,99 @@ cd "$repo_root" variant="mainnetRelease" output="app/build/outputs/native-debug-symbols/$variant/native-debug-symbols.zip" output_dir=$(dirname "$output") +required_libs="libbitkitcore.so libldk_node.so" + +tmp_dirs="" +cleanup() { + for dir in $tmp_dirs; do + rm -rf "$dir" + done +} +trap cleanup EXIT + +make_tmp_dir() { + dir=$(mktemp -d) + tmp_dirs="$tmp_dirs $dir" + echo "$dir" +} + +find_readelf() { + for sdk_dir in "${ANDROID_NDK_HOME:-}" "${ANDROID_HOME:-}/ndk" "${ANDROID_SDK_ROOT:-}/ndk"; do + if [ -z "$sdk_dir" ]; then + continue + fi + + for candidate in \ + "$sdk_dir"/toolchains/llvm/prebuilt/*/bin/llvm-readelf \ + "$sdk_dir"/*/toolchains/llvm/prebuilt/*/bin/llvm-readelf; do + if [ -x "$candidate" ]; then + echo "$candidate" + return + fi + done + done + + 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 + + echo "llvm-readelf or readelf is required to validate native debug symbols." >&2 + exit 1 +} + +readelf_bin=$(find_readelf) + +has_debug_metadata() { + "$readelf_bin" -S "$1" | grep -Eq '\.(symtab|debug_|gnu_debugdata)' +} + +validate_symbol_tree() { + root="$1" + + for abi in arm64-v8a armeabi-v7a; do + for lib_name in $required_libs; do + lib="$root/$abi/$lib_name" + if [ ! -f "$lib" ]; then + echo "Missing required native symbol library '$abi/$lib_name'." >&2 + exit 1 + fi + + if ! has_debug_metadata "$lib"; then + echo "Native debug symbols unavailable: '$abi/$lib_name' has no .symtab, .debug_*, or .gnu_debugdata sections." >&2 + echo "Refusing to create '$output' from stripped native libraries." >&2 + echo "Publish or consume native dependencies with usable debug metadata before releasing." >&2 + exit 1 + fi + done + done +} + +validate_output_zip() { + archive="$1" + zip -T "$archive" >/dev/null + + tmp_dir=$(make_tmp_dir) + for abi in arm64-v8a armeabi-v7a; do + for lib_name in $required_libs; do + entry="$abi/$lib_name" + if ! unzip -q "$archive" "$entry" -d "$tmp_dir"; then + echo "Native debug symbols archive is missing '$entry'." >&2 + exit 1 + fi + done + done + + validate_symbol_tree "$tmp_dir" +} if [ -f "$output" ]; then - zip -T "$output" >/dev/null + validate_output_zip "$output" echo "Native debug symbols: $output" ls -lh "$output" exit 0 @@ -29,8 +119,7 @@ if [ -z "$native_lib_dir" ]; then exit 1 fi -tmp_dir=$(mktemp -d) -trap 'rm -rf "$tmp_dir"' EXIT +tmp_dir=$(make_tmp_dir) for abi in arm64-v8a armeabi-v7a; do source_dir="$native_lib_dir/$abi" @@ -54,6 +143,8 @@ for abi in arm64-v8a armeabi-v7a; do fi done +validate_symbol_tree "$tmp_dir" + mkdir -p "$output_dir" rm -f "$output" From 3bc63bee5cda0dfe0d9b54df27fa7d55d1f97e01 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 4 Jun 2026 20:13:29 +0200 Subject: [PATCH 06/10] fix: accept agp symbol entries --- .../bitkit/build/NativeReleaseConfigTest.kt | 4 +++ scripts/create-native-debug-symbols.sh | 30 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt index 8782545365..48e7ceb4cf 100644 --- a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -102,6 +102,10 @@ class NativeReleaseConfigTest { symbolsScript.contains("""required_libs="libbitkitcore.so libldk_node.so""""), "Native debug symbols script must validate crash-relevant native libraries.", ) + assertTrue( + symbolsScript.contains("""archive_symbol_suffixes=".dbg .sym""""), + "Native debug symbols script must accept AGP native debug symbol entry suffixes.", + ) assertTrue( symbolsScript.contains("""grep -Eq '\.(symtab|debug_|gnu_debugdata)'"""), "Native debug symbols script must validate usable debug metadata before zipping.", diff --git a/scripts/create-native-debug-symbols.sh b/scripts/create-native-debug-symbols.sh index f1b722cbe5..5079102e3a 100755 --- a/scripts/create-native-debug-symbols.sh +++ b/scripts/create-native-debug-symbols.sh @@ -9,6 +9,7 @@ variant="mainnetRelease" output="app/build/outputs/native-debug-symbols/$variant/native-debug-symbols.zip" output_dir=$(dirname "$output") required_libs="libbitkitcore.so libldk_node.so" +archive_symbol_suffixes=".dbg .sym" tmp_dirs="" cleanup() { @@ -81,6 +82,29 @@ validate_symbol_tree() { done } +extract_archive_lib() { + archive="$1" + tmp_dir="$2" + abi="$3" + lib_name="$4" + + entry="$abi/$lib_name" + if unzip -q "$archive" "$entry" -d "$tmp_dir" 2>/dev/null; then + return + fi + + for suffix in $archive_symbol_suffixes; do + entry="$abi/$lib_name$suffix" + if unzip -q "$archive" "$entry" -d "$tmp_dir" 2>/dev/null; then + mv "$tmp_dir/$entry" "$tmp_dir/$abi/$lib_name" + return + fi + done + + echo "Native debug symbols archive is missing '$abi/$lib_name' or accepted AGP variants '$abi/$lib_name.dbg' / '$abi/$lib_name.sym'." >&2 + exit 1 +} + validate_output_zip() { archive="$1" zip -T "$archive" >/dev/null @@ -88,11 +112,7 @@ validate_output_zip() { tmp_dir=$(make_tmp_dir) for abi in arm64-v8a armeabi-v7a; do for lib_name in $required_libs; do - entry="$abi/$lib_name" - if ! unzip -q "$archive" "$entry" -d "$tmp_dir"; then - echo "Native debug symbols archive is missing '$entry'." >&2 - exit 1 - fi + extract_archive_lib "$archive" "$tmp_dir" "$abi" "$lib_name" done done From d80980fdc91a58da5257f4baebc2a280682fd992 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 4 Jun 2026 20:19:18 +0200 Subject: [PATCH 07/10] docs: clean release wording --- .agents/commands/release.md | 2 +- README.md | 2 +- .../test/java/to/bitkit/build/NativeReleaseConfigTest.kt | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.agents/commands/release.md b/.agents/commands/release.md index 3ed078fd8e..ede096e48f 100644 --- a/.agents/commands/release.md +++ b/.agents/commands/release.md @@ -222,7 +222,7 @@ gh release upload v{newVersionName} \ app/build/outputs/native-debug-symbols/mainnetRelease/native-debug-symbols.zip ``` -For the Play Store release, upload the AAB as usual, then upload `native-debug-symbols.zip` for the exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play lists the native debug symbols after upload. Do not rely on Play Console to download or recover this file later. If Play only shows delete/replace controls for an uploaded symbol file, that is enough for release verification; GitHub releases or internal release storage must keep the release-built archive. +For the Play Store release, upload the AAB as usual, then upload `native-debug-symbols.zip` for the exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play lists the native debug symbols after upload. Keep the release-built archive in GitHub releases or internal release storage; Play Console may only show delete/replace controls after upload, which is enough for release verification. ### 9. Return to Master diff --git a/README.md b/README.md index 5da0c3238c..c45e93ffd1 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ The native debug symbols archive must come from the same `just release` build as For Play Store releases, upload the AAB as usual, then upload `native-debug-symbols.zip` for that exact version/build in Play Console: App bundle explorer → Downloads → Assets. Verify Play lists the native debug symbols after upload. -Do not rely on Play Console to download or recover `native-debug-symbols.zip` later. If Play only shows delete/replace controls for an uploaded symbol file, that is enough for release verification. Keep the release-built archive in GitHub releases or internal release storage. +Keep the release-built `native-debug-symbols.zip` in GitHub releases or internal release storage. Play Console may only show delete/replace controls after upload, which is enough for release verification. For GitHub releases, attach `native-debug-symbols.zip` alongside the APK so native crashes from GitHub-distributed builds can be symbolicated later. diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt index 48e7ceb4cf..cf9b4bee3e 100644 --- a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -48,7 +48,7 @@ class NativeReleaseConfigTest { ) assertFalse( justfile.contains("download"), - "Release builds must not imply Play is the source of native debug symbols.", + "Release builds should keep native debug symbols in release storage.", ) } @@ -68,14 +68,14 @@ class NativeReleaseConfigTest { ) assertFalse( releaseCommand.contains("Play " + "did not"), - "Release command must not use stale Play native symbol wording.", + "Release command should use current Play native symbol wording.", ) assertTrue( releaseCommand.contains("fails instead of creating a placeholder zip from stripped `.so` files"), "Release command must fail instead of publishing fake native debug symbols.", ) assertTrue( - releaseCommand.contains("If Play only shows delete/replace controls"), + releaseCommand.contains("Play Console may only show delete/replace controls"), "Release command must document the verified Play Console behavior.", ) } From 875101bc8468de0fe0ff00f1fc8c74f34ae14488 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 4 Jun 2026 22:46:27 +0200 Subject: [PATCH 08/10] fix: gate vss native symbols --- app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt | 4 ++-- scripts/create-native-debug-symbols.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt index cf9b4bee3e..089e1bfdb0 100644 --- a/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt +++ b/app/src/test/java/to/bitkit/build/NativeReleaseConfigTest.kt @@ -99,8 +99,8 @@ class NativeReleaseConfigTest { "Native debug symbols script must create a zip archive.", ) assertTrue( - symbolsScript.contains("""required_libs="libbitkitcore.so libldk_node.so""""), - "Native debug symbols script must validate crash-relevant native libraries.", + symbolsScript.contains("""required_libs="libbitkitcore.so libldk_node.so libvss_rust_client_ffi.so""""), + "Native debug symbols script must validate Rust native libraries.", ) assertTrue( symbolsScript.contains("""archive_symbol_suffixes=".dbg .sym""""), diff --git a/scripts/create-native-debug-symbols.sh b/scripts/create-native-debug-symbols.sh index 5079102e3a..f1ac99b2d8 100755 --- a/scripts/create-native-debug-symbols.sh +++ b/scripts/create-native-debug-symbols.sh @@ -8,7 +8,7 @@ cd "$repo_root" variant="mainnetRelease" output="app/build/outputs/native-debug-symbols/$variant/native-debug-symbols.zip" output_dir=$(dirname "$output") -required_libs="libbitkitcore.so libldk_node.so" +required_libs="libbitkitcore.so libldk_node.so libvss_rust_client_ffi.so" archive_symbol_suffixes=".dbg .sym" tmp_dirs="" From a3bca9b657b1cb0af01f9f620610ca9a485d7451 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Fri, 5 Jun 2026 01:21:08 +0200 Subject: [PATCH 09/10] fix: use native symbol releases --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e8659a9550..2e12b371f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ activity-compose = { module = "androidx.activity:activity-compose", version = "1 appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" } barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version = "17.3.0" } biometric = { module = "androidx.biometric:biometric", version = "1.4.0-alpha05" } -bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.67" } +bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.69" } paykit = { module = "com.synonym:paykit-android", version = "0.1.0-rc8" } bouncycastle-provider-jdk = { module = "org.bouncycastle:bcprov-jdk18on", version = "1.83" } camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" } @@ -64,7 +64,7 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } -ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.46" } +ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.47" } lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" } lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } @@ -88,7 +88,7 @@ test-junit-ext = { module = "androidx.test.ext:junit", version = "1.3.0" } test-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "6.2.2" } test-robolectric = { module = "org.robolectric:robolectric", version = "4.16.1" } test-turbine = { group = "app.cash.turbine", name = "turbine", version = "1.2.1" } -vss-client = { module = "com.synonym:vss-client-android", version = "0.5.12" } +vss-client = { module = "com.synonym:vss-client-android", version = "0.5.16" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version = "2.11.0" } zxing = { module = "com.google.zxing:core", version = "3.5.4" } lottie = { module = "com.airbnb.android:lottie-compose", version = "6.7.1" } From 092330e11e6400237441dd302461b4a6038d3a10 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Fri, 5 Jun 2026 20:28:09 +0200 Subject: [PATCH 10/10] fix: consume native symbol packages --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e12b371f9..f2ec409608 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ activity-compose = { module = "androidx.activity:activity-compose", version = "1 appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" } barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version = "17.3.0" } biometric = { module = "androidx.biometric:biometric", version = "1.4.0-alpha05" } -bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.69" } +bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.70" } paykit = { module = "com.synonym:paykit-android", version = "0.1.0-rc8" } bouncycastle-provider-jdk = { module = "org.bouncycastle:bcprov-jdk18on", version = "1.83" } camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" } @@ -64,7 +64,7 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } -ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.47" } +ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.48" } lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" } lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } @@ -88,7 +88,7 @@ test-junit-ext = { module = "androidx.test.ext:junit", version = "1.3.0" } test-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "6.2.2" } test-robolectric = { module = "org.robolectric:robolectric", version = "4.16.1" } test-turbine = { group = "app.cash.turbine", name = "turbine", version = "1.2.1" } -vss-client = { module = "com.synonym:vss-client-android", version = "0.5.16" } +vss-client = { module = "com.synonym:vss-client-android", version = "0.5.17" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version = "2.11.0" } zxing = { module = "com.google.zxing:core", version = "3.5.4" } lottie = { module = "com.airbnb.android:lottie-compose", version = "6.7.1" }