1+ import java.io.ByteArrayOutputStream
2+ import java.io.File
3+
14plugins {
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+
61165afterEvaluate {
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)." )
0 commit comments