Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 92 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.io.ByteArrayOutputStream

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
Expand Down Expand Up @@ -29,6 +31,7 @@ val isReleaseTaskRequested = gradle.startParameter.taskNames.any { task ->
}

val missingReleaseSigningEnvText = missingReleaseSigningEnv.joinToString(separator = ", ")
val supportedAbis = listOf("arm64-v8a", "x86_64")

android {
namespace = "com.google.ai.sample"
Expand All @@ -41,7 +44,7 @@ android {
versionCode = 1
versionName = "1.0"
ndk {
abiFilters += listOf("arm64-v8a", "x86_64")
abiFilters += supportedAbis
}

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
Expand Down Expand Up @@ -93,6 +96,92 @@ android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.4"
}
packaging {
jniLibs {
useLegacyPackaging = false
}
}
}

fun parseLoadAlignments(readelfOutput: String): List<Long> {
val lines = readelfOutput.lineSequence().toList()
val alignments = mutableListOf<Long>()
for (index in 0 until lines.lastIndex) {
if (!lines[index].trimStart().startsWith("LOAD")) continue
val alignToken = lines[index + 1].trim().split(Regex("\\s+")).lastOrNull() ?: continue
val alignValue = alignToken.removePrefix("0x").toLongOrNull(16) ?: continue
alignments += alignValue
}
return alignments
}

androidComponents {
onVariants(selector().all()) { variant ->
val variantName = variant.name
val variantNameCap = variantName.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
val mergeNativeTaskName = "merge${variantNameCap}NativeLibs"
val verifyTaskName = "verify${variantNameCap}Native16KbAlignment"

val verifyTask = tasks.register(verifyTaskName) {
group = "verification"
description = "Verifies that all merged native libs for $variantName use at least 16KB PT_LOAD alignment."
dependsOn(mergeNativeTaskName)

doLast {
val nativeOutDir = layout.buildDirectory
.dir("intermediates/merged_native_libs/$variantName/$mergeNativeTaskName/out/lib")
.get()
.asFile

if (!nativeOutDir.exists()) {
throw GradleException("Native lib output directory not found: ${nativeOutDir.absolutePath}")
}

val soFiles = nativeOutDir.walkTopDown().filter { it.isFile && it.extension == "so" }.toList()
val filteredSoFiles = soFiles.filter { soFile ->
val abiDir = soFile.parentFile?.name
abiDir in supportedAbis
}
if (filteredSoFiles.isEmpty()) {
logger.lifecycle("No native .so files found under ${nativeOutDir.absolutePath} for variant $variantName.")
return@doLast
}

val invalidLibraries = mutableListOf<String>()
filteredSoFiles.forEach { soFile ->
val stdout = ByteArrayOutputStream()
val execResult = exec {
commandLine("readelf", "-l", soFile.absolutePath)
standardOutput = stdout
isIgnoreExitValue = false
}
if (execResult.exitValue != 0) {
throw GradleException("readelf failed for ${soFile.absolutePath}")
}

val alignments = parseLoadAlignments(stdout.toString())
if (alignments.isEmpty() || alignments.any { it < 0x4000L }) {
val relativePath = soFile.relativeTo(nativeOutDir).path
val shownAlignments = if (alignments.isEmpty()) "none" else alignments.joinToString(", ") { "0x${it.toString(16)}" }
invalidLibraries += "$relativePath (PT_LOAD alignments: $shownAlignments)"
}
}

if (invalidLibraries.isNotEmpty()) {
throw GradleException(
"Found native libraries without required 16KB alignment in variant '$variantName':\n" +
invalidLibraries.joinToString("\n")
)
}
}
}

tasks.configureEach {
if (name == "assemble$variantNameCap") {
dependsOn(verifyTask)
}
}
}
}

if (isReleaseTaskRequested && missingReleaseSigningEnv.isNotEmpty()) {
Expand Down Expand Up @@ -156,10 +245,10 @@ dependencies {
implementation("com.google.ai.edge.litertlm:litertlm-android:0.10.0")

// Camera Core to potentially fix missing JNI lib issue
implementation("androidx.camera:camera-core:1.4.0")
implementation("androidx.camera:camera-core:1.4.2")

// WebRTC
implementation("io.getstream:stream-webrtc-android:1.1.1")
implementation("io.getstream:stream-webrtc-android:1.3.10")

// WebSocket for signaling
implementation("com.squareup.okhttp3:okhttp:4.12.0")
Expand Down
94 changes: 93 additions & 1 deletion humanoperator/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.io.ByteArrayOutputStream

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
Expand All @@ -20,6 +22,7 @@ val isReleaseTaskRequested = gradle.startParameter.taskNames.any { task ->
}

val missingReleaseSigningEnvText = missingReleaseSigningEnv.joinToString(separator = ", ")
val supportedAbis = listOf("arm64-v8a", "x86_64")

android {
namespace = "com.screenoperator.humanoperator"
Expand All @@ -31,6 +34,9 @@ android {
targetSdk = 35
versionCode = 1
versionName = "1.0"
ndk {
abiFilters += supportedAbis
}

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down Expand Up @@ -70,6 +76,92 @@ android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.4"
}
packaging {
jniLibs {
useLegacyPackaging = false
}
}
}

fun parseLoadAlignments(readelfOutput: String): List<Long> {
val lines = readelfOutput.lineSequence().toList()
val alignments = mutableListOf<Long>()
for (index in 0 until lines.lastIndex) {
if (!lines[index].trimStart().startsWith("LOAD")) continue
val alignToken = lines[index + 1].trim().split(Regex("\\s+")).lastOrNull() ?: continue
val alignValue = alignToken.removePrefix("0x").toLongOrNull(16) ?: continue
alignments += alignValue
}
return alignments
}

androidComponents {
onVariants(selector().all()) { variant ->
val variantName = variant.name
val variantNameCap = variantName.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
val mergeNativeTaskName = "merge${variantNameCap}NativeLibs"
val verifyTaskName = "verify${variantNameCap}Native16KbAlignment"

val verifyTask = tasks.register(verifyTaskName) {
group = "verification"
description = "Verifies that all merged native libs for $variantName use at least 16KB PT_LOAD alignment."
dependsOn(mergeNativeTaskName)

doLast {
val nativeOutDir = layout.buildDirectory
.dir("intermediates/merged_native_libs/$variantName/$mergeNativeTaskName/out/lib")
.get()
.asFile

if (!nativeOutDir.exists()) {
throw GradleException("Native lib output directory not found: ${nativeOutDir.absolutePath}")
}

val soFiles = nativeOutDir.walkTopDown().filter { it.isFile && it.extension == "so" }.toList()
val filteredSoFiles = soFiles.filter { soFile ->
val abiDir = soFile.parentFile?.name
abiDir in supportedAbis
}
if (filteredSoFiles.isEmpty()) {
logger.lifecycle("No native .so files found under ${nativeOutDir.absolutePath} for variant $variantName.")
return@doLast
}

val invalidLibraries = mutableListOf<String>()
filteredSoFiles.forEach { soFile ->
val stdout = ByteArrayOutputStream()
val execResult = exec {
commandLine("readelf", "-l", soFile.absolutePath)
standardOutput = stdout
isIgnoreExitValue = false
}
if (execResult.exitValue != 0) {
throw GradleException("readelf failed for ${soFile.absolutePath}")
}

val alignments = parseLoadAlignments(stdout.toString())
if (alignments.isEmpty() || alignments.any { it < 0x4000L }) {
val relativePath = soFile.relativeTo(nativeOutDir).path
val shownAlignments = if (alignments.isEmpty()) "none" else alignments.joinToString(", ") { "0x${it.toString(16)}" }
invalidLibraries += "$relativePath (PT_LOAD alignments: $shownAlignments)"
}
}

if (invalidLibraries.isNotEmpty()) {
throw GradleException(
"Found native libraries without required 16KB alignment in variant '$variantName':\n" +
invalidLibraries.joinToString("\n")
)
}
}
}

tasks.configureEach {
if (name == "assemble$variantNameCap") {
dependsOn(verifyTask)
}
}
}
}

if (isReleaseTaskRequested && missingReleaseSigningEnv.isNotEmpty()) {
Expand All @@ -93,7 +185,7 @@ dependencies {
implementation("androidx.compose.material:material-icons-extended")

// WebRTC
implementation("io.getstream:stream-webrtc-android:1.1.1")
implementation("io.getstream:stream-webrtc-android:1.3.10")

// WebSocket for signaling
implementation("com.squareup.okhttp3:okhttp:4.12.0")
Expand Down
Loading