1+ import java.io.ByteArrayOutputStream
2+
13plugins {
24 id(" com.android.application" )
35 id(" org.jetbrains.kotlin.android" )
@@ -29,6 +31,7 @@ val isReleaseTaskRequested = gradle.startParameter.taskNames.any { task ->
2931}
3032
3133val missingReleaseSigningEnvText = missingReleaseSigningEnv.joinToString(separator = " , " )
34+ val supportedAbis = listOf (" arm64-v8a" , " x86_64" )
3235
3336android {
3437 namespace = " com.google.ai.sample"
@@ -41,7 +44,7 @@ android {
4144 versionCode = 1
4245 versionName = " 1.0"
4346 ndk {
44- abiFilters + = listOf ( " arm64-v8a " , " x86_64 " )
47+ abiFilters + = supportedAbis
4548 }
4649
4750 testInstrumentationRunner = " androidx.test.runner.AndroidJUnitRunner"
@@ -93,6 +96,92 @@ android {
9396 composeOptions {
9497 kotlinCompilerExtensionVersion = " 1.5.4"
9598 }
99+ packaging {
100+ jniLibs {
101+ useLegacyPackaging = false
102+ }
103+ }
104+ }
105+
106+ fun parseLoadAlignments (readelfOutput : String ): List <Long > {
107+ val lines = readelfOutput.lineSequence().toList()
108+ val alignments = mutableListOf<Long >()
109+ for (index in 0 until lines.lastIndex) {
110+ if (! lines[index].trimStart().startsWith(" LOAD" )) continue
111+ val alignToken = lines[index + 1 ].trim().split(Regex (" \\ s+" )).lastOrNull() ? : continue
112+ val alignValue = alignToken.removePrefix(" 0x" ).toLongOrNull(16 ) ? : continue
113+ alignments + = alignValue
114+ }
115+ return alignments
116+ }
117+
118+ androidComponents {
119+ onVariants(selector().all()) { variant ->
120+ val variantName = variant.name
121+ val variantNameCap = variantName.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
122+ val mergeNativeTaskName = " merge${variantNameCap} NativeLibs"
123+ val verifyTaskName = " verify${variantNameCap} Native16KbAlignment"
124+
125+ val verifyTask = tasks.register(verifyTaskName) {
126+ group = " verification"
127+ description = " Verifies that all merged native libs for $variantName use at least 16KB PT_LOAD alignment."
128+ dependsOn(mergeNativeTaskName)
129+
130+ doLast {
131+ val nativeOutDir = layout.buildDirectory
132+ .dir(" intermediates/merged_native_libs/$variantName /$mergeNativeTaskName /out/lib" )
133+ .get()
134+ .asFile
135+
136+ if (! nativeOutDir.exists()) {
137+ throw GradleException (" Native lib output directory not found: ${nativeOutDir.absolutePath} " )
138+ }
139+
140+ val soFiles = nativeOutDir.walkTopDown().filter { it.isFile && it.extension == " so" }.toList()
141+ val filteredSoFiles = soFiles.filter { soFile ->
142+ val abiDir = soFile.parentFile?.name
143+ abiDir in supportedAbis
144+ }
145+ if (filteredSoFiles.isEmpty()) {
146+ logger.lifecycle(" No native .so files found under ${nativeOutDir.absolutePath} for variant $variantName ." )
147+ return @doLast
148+ }
149+
150+ val invalidLibraries = mutableListOf<String >()
151+ filteredSoFiles.forEach { soFile ->
152+ val stdout = ByteArrayOutputStream ()
153+ val execResult = exec {
154+ commandLine(" readelf" , " -l" , soFile.absolutePath)
155+ standardOutput = stdout
156+ isIgnoreExitValue = false
157+ }
158+ if (execResult.exitValue != 0 ) {
159+ throw GradleException (" readelf failed for ${soFile.absolutePath} " )
160+ }
161+
162+ val alignments = parseLoadAlignments(stdout.toString())
163+ if (alignments.isEmpty() || alignments.any { it < 0x4000L }) {
164+ val relativePath = soFile.relativeTo(nativeOutDir).path
165+ val shownAlignments = if (alignments.isEmpty()) " none" else alignments.joinToString(" , " ) { " 0x${it.toString(16 )} " }
166+ invalidLibraries + = " $relativePath (PT_LOAD alignments: $shownAlignments )"
167+ }
168+ }
169+
170+ if (invalidLibraries.isNotEmpty()) {
171+ throw GradleException (
172+ " Found native libraries without required 16KB alignment in variant '$variantName ':\n " +
173+ invalidLibraries.joinToString(" \n " )
174+ )
175+ }
176+ }
177+ }
178+
179+ tasks.configureEach {
180+ if (name == " assemble$variantNameCap " ) {
181+ dependsOn(verifyTask)
182+ }
183+ }
184+ }
96185}
97186
98187if (isReleaseTaskRequested && missingReleaseSigningEnv.isNotEmpty()) {
0 commit comments