Skip to content

Commit 53003dc

Browse files
authored
Merge pull request #84 from synonymdev/ovi/fix-android-native-symbols
fix: publish android debug symbols
2 parents 7991621 + 4f9e466 commit 53003dc

19 files changed

Lines changed: 551 additions & 188 deletions

File tree

.github/workflows/publish-android.yml

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,32 @@ on:
66
workflow_dispatch:
77
inputs:
88
version:
9-
description: "Version to publish (e.g., v0.1.0)"
9+
description: "Version/tag to publish (e.g., v0.7.0-rc.50 or 0.7.0-rc.50)"
1010
required: true
1111

1212
jobs:
1313
publish:
1414
runs-on: ubuntu-latest
1515
permissions:
16-
contents: read
16+
contents: write
1717
packages: write
1818

1919
steps:
20+
- name: Normalize release version
21+
id: version
22+
shell: bash
23+
run: |
24+
VERSION="${{ github.event.inputs.version || github.event.release.tag_name || github.ref_name }}"
25+
TAG="$VERSION"
26+
if [[ "$TAG" != v* ]]; then
27+
TAG="v$TAG"
28+
fi
29+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
30+
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
31+
2032
- uses: actions/checkout@v4
33+
with:
34+
ref: ${{ steps.version.outputs.tag }}
2135
- name: Set up JDK 17
2236
uses: actions/setup-java@v4
2337
with:
@@ -29,24 +43,44 @@ jobs:
2943
- name: Setup Gradle
3044
uses: gradle/actions/setup-gradle@v4
3145

32-
- name: Extract version from input or tag
33-
id: version
34-
shell: bash
35-
run: |
36-
VERSION="${{ inputs.version }}"
37-
if [[ -z "$VERSION" ]]; then
38-
VERSION="$GITHUB_REF_NAME"
39-
fi
40-
echo "version=${VERSION#v}" >> $GITHUB_OUTPUT
46+
- name: Install native build dependencies
47+
run: sudo apt-get update && sudo apt-get install -y pkg-config protobuf-compiler
48+
49+
- name: Set up Android SDK
50+
uses: android-actions/setup-android@v3
51+
52+
- name: Set up Android NDK
53+
id: setup-ndk
54+
uses: nttld/setup-ndk@v1
55+
with:
56+
ndk-version: r28c
57+
add-to-path: true
58+
59+
- name: Export Android NDK root
60+
run: echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> "$GITHUB_ENV"
61+
62+
- name: Generate Android bindings
63+
run: scripts/uniffi_bindgen_generate_kotlin_android.sh
4164

4265
- name: Build with Gradle
4366
working-directory: bindings/kotlin/ldk-node-android
4467
run: ./gradlew build -Pversion=${{ steps.version.outputs.version }}
4568

69+
- name: Upload native debug symbols artifact
70+
uses: actions/upload-artifact@v4
71+
with:
72+
name: ldk-node-native-debug-symbols-${{ steps.version.outputs.version }}
73+
path: bindings/kotlin/ldk-node-android/native-debug-symbols.zip
74+
75+
- name: Upload native debug symbols to release
76+
env:
77+
GH_TOKEN: ${{ github.token }}
78+
run: gh release upload "${{ steps.version.outputs.tag }}" bindings/kotlin/ldk-node-android/native-debug-symbols.zip --clobber
79+
4680
- name: Publish to GitHub Packages
4781
working-directory: bindings/kotlin/ldk-node-android
4882
env:
4983
GITHUB_ACTOR: ${{ github.actor }}
50-
GITHUB_TOKEN: ${{ secrets.ORG_PACKAGES_TOKEN }}
84+
GITHUB_TOKEN: ${{ github.token }}
5185
GITHUB_REPO: ${{ github.repository }}
5286
run: ./gradlew publish -Pversion=${{ steps.version.outputs.version }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# will have compiled files and executables
33
/target/
44
/bindings/uniffi-bindgen/target/
5+
/bindings/kotlin/ldk-node-android/native-debug-symbols.zip
56

67
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
78
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
# 0.7.0-rc.46 (Synonym Fork)
1+
# 0.7.0-rc.51 (Synonym Fork)
22

33
## Bug Fixes
44

5+
- Published stripped `ldk-node-android` artifacts with separate native debug symbols
6+
and failed Android binding generation when generated JNI libraries lack usable symbols.
57
- Persist missing announced channel peers from the network graph during
68
build-time restore and after Rapid Gossip Sync graph updates. Automatic
79
recovery respects explicit disconnects and last-channel closes during the

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exclude = ["bindings/uniffi-bindgen"]
44

55
[package]
66
name = "ldk-node"
7-
version = "0.7.0-rc.46"
7+
version = "0.7.0-rc.51"
88
authors = ["Elias Rohrer <dev@tnull.de>"]
99
homepage = "https://lightningdevkit.org/"
1010
license = "MIT OR Apache-2.0"

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
import PackageDescription
55

6-
let tag = "v0.7.0-rc.46"
7-
let checksum = "06558c2834c186d3da188389ef197942e948ac4f6f3261746e3594420ab0ee23"
6+
let tag = "v0.7.0-rc.51"
7+
let checksum = "efeec8d0480f1400c6a4203232359f5b3c4c338a4ba13826538cac12b8fe1bac"
88
let url = "https://github.com/synonymdev/ldk-node/releases/download/\(tag)/LDKNodeFFI.xcframework.zip"
99

1010
let package = Package(

bindings/kotlin/ldk-node-android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ android.useAndroidX=true
33
android.enableJetifier=true
44
kotlin.code.style=official
55
group=com.synonym
6-
version=0.7.0-rc.46
6+
version=0.7.0-rc.51

bindings/kotlin/ldk-node-android/lib/build.gradle.kts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import java.io.ByteArrayOutputStream
2+
import java.io.File
3+
14
plugins {
25
id("com.android.library")
36
kotlin("android")
@@ -58,6 +61,107 @@ dependencies {
5861
api("org.slf4j:slf4j-api:1.7.30")
5962
}
6063

64+
val androidNativeAbis = listOf("armeabi-v7a", "arm64-v8a", "x86_64")
65+
66+
fun executableFromPath(name: String): String? {
67+
return System.getenv("PATH")
68+
?.split(File.pathSeparator)
69+
?.asSequence()
70+
?.map { File(it, name) }
71+
?.firstOrNull { it.canExecute() }
72+
?.absolutePath
73+
}
74+
75+
fun findReadelf(): String {
76+
executableFromPath("llvm-readelf")?.let { return it }
77+
executableFromPath("readelf")?.let { return it }
78+
79+
return listOf("ANDROID_NDK_ROOT", "ANDROID_NDK_HOME", "NDK_HOME")
80+
.mapNotNull { System.getenv(it) }
81+
.map { File(it, "toolchains/llvm/prebuilt") }
82+
.firstNotNullOfOrNull { prebuiltDir ->
83+
if (!prebuiltDir.isDirectory) return@firstNotNullOfOrNull null
84+
85+
prebuiltDir
86+
.walkTopDown()
87+
.firstOrNull { it.name == "llvm-readelf" && it.canExecute() }
88+
?.absolutePath
89+
}
90+
?: throw GradleException(
91+
"llvm-readelf or readelf is required to validate Android native debug symbols"
92+
)
93+
}
94+
95+
fun Project.runReadelf(readelf: String, vararg args: String): Pair<Int, String> {
96+
val stdout = ByteArrayOutputStream()
97+
val stderr = ByteArrayOutputStream()
98+
val result = exec {
99+
commandLine(readelf, *args)
100+
standardOutput = stdout
101+
errorOutput = stderr
102+
isIgnoreExitValue = true
103+
}
104+
105+
return result.exitValue to stdout.toString().ifBlank { stderr.toString() }
106+
}
107+
108+
fun String.parseElfAlignment(): Long {
109+
return if (startsWith("0x")) {
110+
removePrefix("0x").toLong(16)
111+
} else {
112+
toLong()
113+
}
114+
}
115+
116+
val validateReleaseNativeLibraries by tasks.registering {
117+
group = "verification"
118+
description = "Validates release JNI libraries are stripped and keep 16 KB LOAD alignment."
119+
120+
doLast {
121+
val readelf = findReadelf()
122+
val loadAlignmentRegex = Regex("""^\s*LOAD\s+.*\s+(0x[0-9a-fA-F]+|\d+)\s*$""")
123+
124+
androidNativeAbis.forEach { abi ->
125+
val lib = layout.projectDirectory.file("src/main/jniLibs/$abi/libldk_node.so").asFile
126+
if (!lib.isFile) {
127+
throw GradleException("Android native library missing at '${lib.path}'")
128+
}
129+
130+
val (sectionsExit, sections) = runReadelf(readelf, "-S", lib.absolutePath)
131+
if (sectionsExit != 0) {
132+
throw GradleException("Unable to inspect Android native library sections: '${lib.path}'")
133+
}
134+
if (Regex("""\.debug_""").containsMatchIn(sections)) {
135+
throw GradleException("Android release native library still contains .debug_* sections: '${lib.path}'")
136+
}
137+
138+
val wideHeaders = runReadelf(readelf, "-W", "-l", lib.absolutePath)
139+
val headers = if (wideHeaders.first == 0) {
140+
wideHeaders.second
141+
} else {
142+
val fallbackHeaders = runReadelf(readelf, "-l", lib.absolutePath)
143+
if (fallbackHeaders.first != 0) {
144+
throw GradleException("Unable to inspect Android native library headers: '${lib.path}'")
145+
}
146+
fallbackHeaders.second
147+
}
148+
149+
val alignments = headers
150+
.lineSequence()
151+
.mapNotNull { loadAlignmentRegex.matchEntire(it)?.groupValues?.get(1)?.parseElfAlignment() }
152+
.toList()
153+
154+
if (alignments.isEmpty() || alignments.any { it < 16_384 }) {
155+
throw GradleException("Android native library is not 16 KB page-size aligned: '${lib.path}'")
156+
}
157+
}
158+
}
159+
}
160+
161+
tasks.matching { it.name == "bundleReleaseAar" || it.name.startsWith("publish") }.configureEach {
162+
dependsOn(validateReleaseNativeLibraries)
163+
}
164+
61165
afterEvaluate {
62166
publishing {
63167
publications {
@@ -68,6 +172,10 @@ afterEvaluate {
68172
version = providers.gradleProperty("version").orNull ?: "0.0.0"
69173

70174
from(components["release"])
175+
artifact(rootProject.layout.projectDirectory.file("native-debug-symbols.zip")) {
176+
classifier = "native-debug-symbols"
177+
extension = "zip"
178+
}
71179
pom {
72180
name.set(mavenArtifactId)
73181
description.set("LDK Node Android bindings (Synonym fork).")
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)