mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
404 lines
15 KiB
Groovy
404 lines
15 KiB
Groovy
plugins {
|
|
id 'com.android.application'
|
|
id 'com.chaquo.python'
|
|
}
|
|
|
|
def repoRoot = rootProject.projectDir.parentFile
|
|
def meshchatSourceDir = new File(repoRoot, "meshchatx")
|
|
def lxmfySourceDir = new File(repoRoot, "vendor/lxmfy/lxmfy")
|
|
def rnsFilesyncSourceDir = new File(repoRoot, "vendor/rns_filesync/rns_filesync")
|
|
def repositoryBundledWheelsDir = new File(meshchatSourceDir, "public/repository-server-bundled/bundled")
|
|
def chaquopyBuildPython = System.getenv("CHAQUOPY_BUILD_PYTHON")
|
|
def pythonTempDir = new File(buildDir, "python-tmp")
|
|
def vendorWheelDir = new File(rootProject.projectDir, "vendor")
|
|
def lxstPatchedWheel = new File(rootProject.projectDir, "vendor/lxst-0.5.1-py3-none-any.whl")
|
|
def bleakPatchedWheel = new File(rootProject.projectDir, "vendor/bleak-3.0.2-py3-none-any.whl")
|
|
def rnsPatchedWheel = new File(rootProject.projectDir, "vendor/rns-1.4.2-py3-none-any.whl")
|
|
def allAndroidAbis = ["arm64-v8a", "x86_64", "armeabi-v7a"]
|
|
def selectedAndroidAbis = (
|
|
project.findProperty("meshchatxAbis")
|
|
?: System.getenv("MESHCHATX_ABIS")
|
|
?: allAndroidAbis.join(",")
|
|
).split(",").collect { it.trim() }.findAll { it }
|
|
|
|
if (selectedAndroidAbis.isEmpty()) {
|
|
throw new org.gradle.api.GradleException("No Android ABIs selected. Use -PmeshchatxAbis or MESHCHATX_ABIS.")
|
|
}
|
|
def invalidAndroidAbis = selectedAndroidAbis.findAll { !allAndroidAbis.contains(it) }
|
|
if (!invalidAndroidAbis.isEmpty()) {
|
|
throw new org.gradle.api.GradleException("Unsupported ABIs: ${invalidAndroidAbis}. Supported: ${allAndroidAbis}")
|
|
}
|
|
|
|
def meshchatxAbiPackaging = (
|
|
project.findProperty("meshchatxAbiPackaging")
|
|
?: System.getenv("MESHCHATX_ABI_PACKAGING")
|
|
?: "universal"
|
|
).toString().trim().toLowerCase()
|
|
if (meshchatxAbiPackaging != "universal" && meshchatxAbiPackaging != "split") {
|
|
throw new org.gradle.api.GradleException(
|
|
"meshchatxAbiPackaging must be 'universal' or 'split' (got '${meshchatxAbiPackaging}'). " +
|
|
"Use -PmeshchatxAbiPackaging=... or MESHCHATX_ABI_PACKAGING."
|
|
)
|
|
}
|
|
def enableAbiSplits = meshchatxAbiPackaging == "split" && selectedAndroidAbis.size() > 1
|
|
|
|
android {
|
|
namespace 'com.meshchatx'
|
|
compileSdk 35
|
|
|
|
defaultConfig {
|
|
applicationId "com.meshchatx"
|
|
minSdk 24
|
|
targetSdk 35
|
|
versionCode 48002
|
|
versionName "4.8.2"
|
|
|
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
ndk {
|
|
abiFilters(*selectedAndroidAbis as String[])
|
|
}
|
|
}
|
|
|
|
splits {
|
|
abi {
|
|
enable enableAbiSplits
|
|
reset()
|
|
if (enableAbiSplits) {
|
|
include(*selectedAndroidAbis as String[])
|
|
}
|
|
universalApk enableAbiSplits
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
minifyEnabled true
|
|
shrinkResources true
|
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility JavaVersion.VERSION_1_8
|
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
}
|
|
|
|
packagingOptions {
|
|
jniLibs {
|
|
useLegacyPackaging = true
|
|
}
|
|
}
|
|
|
|
lint {
|
|
abortOnError true
|
|
checkReleaseBuilds false
|
|
}
|
|
}
|
|
|
|
tasks.register("syncCodec2JniLibs", Exec) {
|
|
workingDir = repoRoot
|
|
def abiArg = selectedAndroidAbis.join(",")
|
|
commandLine(
|
|
"bash",
|
|
"scripts/android/sync-codec2-jni-libs.sh",
|
|
vendorWheelDir.absolutePath,
|
|
"${projectDir}/src/main/jniLibs",
|
|
abiArg
|
|
)
|
|
doFirst {
|
|
if (!vendorWheelDir.isDirectory()) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing android/vendor directory at ${vendorWheelDir}. " +
|
|
"Run: bash scripts/build-android-wheels-local.sh"
|
|
)
|
|
}
|
|
def wheels = vendorWheelDir.list()?.toList() ?: []
|
|
if (!wheels.any { it.startsWith("chaquopy_libcodec2-") }) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing chaquopy_libcodec2 wheels in ${vendorWheelDir}. " +
|
|
"Run: bash scripts/build-android-wheels-local.sh"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.register("verifyVendorWheels") {
|
|
doLast {
|
|
def vendorPackages = [
|
|
"aiohttp": "3.14.3",
|
|
"cbor2": "5.6.5",
|
|
"cryptography": "50.0.0",
|
|
]
|
|
def purePythonVendorPrefixes = [
|
|
"httpx-0.28.1-",
|
|
"httpcore-",
|
|
"h2-",
|
|
"h11-",
|
|
"anyio-",
|
|
"hpack-",
|
|
"hyperframe-",
|
|
]
|
|
def wheels = vendorWheelDir.list()?.toList() ?: []
|
|
purePythonVendorPrefixes.each { prefix ->
|
|
if (!wheels.any { it.startsWith(prefix) && it.endsWith(".whl") }) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing ${prefix}*.whl in ${vendorWheelDir} (httpx/RNS-over-HTTP). " +
|
|
"Run: bash scripts/build-android-wheels-local.sh"
|
|
)
|
|
}
|
|
}
|
|
selectedAndroidAbis.each { abi ->
|
|
def abiTag = abi.replace("-", "_")
|
|
vendorPackages.each { pkg, ver ->
|
|
def prefix = "${pkg}-${ver}-"
|
|
if (!wheels.any { it.startsWith(prefix) && it.contains("android_24_${abiTag}") }) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing ${pkg} ${ver} wheel for ${abi} in ${vendorWheelDir}. " +
|
|
"Run: bash scripts/build-android-wheels-local.sh --only-recipes ${pkg}-*"
|
|
)
|
|
}
|
|
}
|
|
if (!wheels.any { it.startsWith("pycodec2-") && it.contains("android_24_${abiTag}") }) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing pycodec2 wheel for ${abi} in ${vendorWheelDir}. " +
|
|
"Run: bash scripts/build-android-wheels-local.sh"
|
|
)
|
|
}
|
|
if (!wheels.any { it.startsWith("chaquopy_libcodec2-") && it.contains("android_24_${abiTag}") }) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing chaquopy_libcodec2 wheel for ${abi} in ${vendorWheelDir}. " +
|
|
"Run: bash scripts/build-android-wheels-local.sh"
|
|
)
|
|
}
|
|
def jniLib = file("${projectDir}/src/main/jniLibs/${abi}/libcodec2.so")
|
|
if (!jniLib.isFile() || jniLib.length() < 100_000) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing or tiny jniLibs/${abi}/libcodec2.so after sync. " +
|
|
"Expected a real Codec2 shared library for Android calls."
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.configureEach { task ->
|
|
if (task.name == "preBuild") {
|
|
task.dependsOn(tasks.named("syncCodec2JniLibs"), tasks.named("verifyVendorWheels"))
|
|
}
|
|
}
|
|
|
|
tasks.register("repackAndroidPycodec2Wheels", Exec) {
|
|
workingDir = repoRoot
|
|
def pyExe = (System.getenv("MESHCHATX_REPACK_PYTHON") ?: "python3")
|
|
commandLine(pyExe, "scripts/repack-android-pycodec2-wheels.py", "--vendor-dir", vendorWheelDir.absolutePath)
|
|
doFirst {
|
|
if (!vendorWheelDir.isDirectory()) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing android/vendor directory at ${vendorWheelDir}"
|
|
)
|
|
}
|
|
def wheels = vendorWheelDir.list()?.toList() ?: []
|
|
if (!wheels.any { it.startsWith("pycodec2-") && it.endsWith(".whl") }) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing pycodec2 wheels in ${vendorWheelDir}. " +
|
|
"Run: bash scripts/build-android-wheels-local.sh"
|
|
)
|
|
}
|
|
if (!wheels.any { it.startsWith("chaquopy_libcodec2-") && it.endsWith(".whl") }) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing chaquopy_libcodec2 wheels needed to repack pycodec2 in ${vendorWheelDir}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.named("syncCodec2JniLibs").configure {
|
|
dependsOn(tasks.named("repackAndroidPycodec2Wheels"))
|
|
}
|
|
|
|
tasks.named("verifyVendorWheels").configure {
|
|
dependsOn(tasks.named("syncCodec2JniLibs"))
|
|
}
|
|
|
|
tasks.register("fetchRepositoryBundledWheels", Exec) {
|
|
workingDir = repoRoot
|
|
def pyExe = (System.getenv("MESHCHATX_FETCH_PYTHON") ?: "python3")
|
|
commandLine(pyExe, "scripts/build/fetch_repository_wheels.py")
|
|
ignoreExitValue = false
|
|
onlyIf {
|
|
if (System.getenv("MESHCHATX_OFFLINE_BUILD") == "1") {
|
|
if (!repositoryBundledWheelsDir.isDirectory() || !repositoryBundledWheelsDir.list()?.any { it.endsWith(".whl") }) {
|
|
throw new org.gradle.api.GradleException("MESHCHATX_OFFLINE_BUILD=1 but repository bundled wheels are missing at ${repositoryBundledWheelsDir}")
|
|
}
|
|
return false
|
|
}
|
|
if (System.getenv("MESHCHATX_SKIP_REPOSITORY_WHEELS_FETCH") == "1") {
|
|
return false
|
|
}
|
|
if (!repositoryBundledWheelsDir.isDirectory()) {
|
|
return true
|
|
}
|
|
def names = repositoryBundledWheelsDir.list()
|
|
return names == null || !names.any { it.endsWith(".whl") }
|
|
}
|
|
}
|
|
|
|
tasks.register("syncMeshchatPython", Sync) {
|
|
dependsOn("fetchRepositoryBundledWheels")
|
|
if (!meshchatSourceDir.exists()) {
|
|
throw new org.gradle.api.GradleException("Missing meshchatx source directory at ${meshchatSourceDir}")
|
|
}
|
|
doFirst {
|
|
delete(
|
|
file("$projectDir/src/main/python/meshchatx"),
|
|
file("$projectDir/src/main/python/LXST"),
|
|
file("$projectDir/src/main/python/LXMF"),
|
|
file("$projectDir/src/main/python/RNS")
|
|
)
|
|
}
|
|
from(meshchatSourceDir)
|
|
includeEmptyDirs = false
|
|
from(new File(repoRoot, "CHANGELOG.md"))
|
|
into(file("$projectDir/src/main/python/meshchatx"))
|
|
}
|
|
|
|
/*
|
|
* Desktop installs lxmfy via setuptools (pyproject where=[".", "vendor/lxmfy"]).
|
|
* Chaquopy only sees android/app/src/main/python plus pip installs, so the
|
|
* vendored package must be synced here or bot_templates import fails at boot.
|
|
*/
|
|
tasks.register("syncLxmfyPython", Sync) {
|
|
if (!lxmfySourceDir.exists()) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing vendored lxmfy package at ${lxmfySourceDir}. " +
|
|
"Expected vendor/lxmfy/lxmfy from the MeshChatX tree."
|
|
)
|
|
}
|
|
def initPy = new File(lxmfySourceDir, "__init__.py")
|
|
if (!initPy.exists()) {
|
|
throw new org.gradle.api.GradleException("Missing ${initPy}; vendored lxmfy tree looks incomplete.")
|
|
}
|
|
doFirst {
|
|
delete(file("$projectDir/src/main/python/lxmfy"))
|
|
}
|
|
from(lxmfySourceDir)
|
|
includeEmptyDirs = false
|
|
into(file("$projectDir/src/main/python/lxmfy"))
|
|
}
|
|
|
|
/*
|
|
* Desktop installs rns_filesync via setuptools (pyproject where includes vendor/rns_filesync).
|
|
* Chaquopy only sees android/app/src/main/python plus pip installs, so the
|
|
* vendored package must be synced here or FileSync imports fail at boot.
|
|
*/
|
|
tasks.register("syncRnsFilesyncPython", Sync) {
|
|
if (!rnsFilesyncSourceDir.exists()) {
|
|
throw new org.gradle.api.GradleException(
|
|
"Missing vendored rns_filesync package at ${rnsFilesyncSourceDir}. " +
|
|
"Expected vendor/rns_filesync/rns_filesync from the MeshChatX tree."
|
|
)
|
|
}
|
|
def initPy = new File(rnsFilesyncSourceDir, "__init__.py")
|
|
if (!initPy.exists()) {
|
|
throw new org.gradle.api.GradleException("Missing ${initPy}; vendored rns_filesync tree looks incomplete.")
|
|
}
|
|
doFirst {
|
|
delete(file("$projectDir/src/main/python/rns_filesync"))
|
|
}
|
|
from(rnsFilesyncSourceDir)
|
|
includeEmptyDirs = false
|
|
into(file("$projectDir/src/main/python/rns_filesync"))
|
|
}
|
|
|
|
tasks.configureEach { task ->
|
|
if (task.name.toLowerCase().contains("merge") && task.name.toLowerCase().contains("pythonsources")) {
|
|
task.dependsOn(
|
|
tasks.named("syncMeshchatPython"),
|
|
tasks.named("syncLxmfyPython"),
|
|
tasks.named("syncRnsFilesyncPython"),
|
|
)
|
|
}
|
|
}
|
|
|
|
tasks.configureEach { task ->
|
|
if (task.name.toLowerCase().contains("python")) {
|
|
task.doFirst {
|
|
if (!pythonTempDir.exists()) {
|
|
pythonTempDir.mkdirs()
|
|
}
|
|
if (task.metaClass.respondsTo(task, "environment", String, Object)) {
|
|
task.environment("TMPDIR", pythonTempDir.absolutePath)
|
|
task.environment("TEMP", pythonTempDir.absolutePath)
|
|
task.environment("TMP", pythonTempDir.absolutePath)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
chaquopy {
|
|
defaultConfig {
|
|
version "3.11"
|
|
|
|
if (chaquopyBuildPython != null && !chaquopyBuildPython.trim().isEmpty()) {
|
|
buildPython chaquopyBuildPython
|
|
}
|
|
|
|
pip {
|
|
if (!vendorWheelDir.exists()) {
|
|
throw new org.gradle.api.GradleException("Missing vendor wheel directory at ${vendorWheelDir}")
|
|
}
|
|
options "--find-links", vendorWheelDir.absolutePath
|
|
install "packaging>=23"
|
|
install "aiohttp==3.14.3"
|
|
if (!rnsPatchedWheel.exists()) {
|
|
throw new org.gradle.api.GradleException("Missing patched RNS wheel at ${rnsPatchedWheel}")
|
|
}
|
|
// Patched so RNS's Android RNodeInterface never calls RNS.panic() (os._exit)
|
|
// when usbserial4a/jnius are missing. MeshChatX also ships a Chaquopy jnius
|
|
// shim, usb4a context bridge, and able BLE stack so USB/BT/BLE RNode can
|
|
// run. RNode over TCP needs neither native module and always works.
|
|
install rnsPatchedWheel.absolutePath
|
|
install "lxmf>=1.1.1"
|
|
install "numpy==1.26.2"
|
|
install "chaquopy-libcodec2==1.2.0"
|
|
install "pycodec2==4.1.1"
|
|
install "miniaudio==1.70"
|
|
if (!lxstPatchedWheel.exists()) {
|
|
throw new org.gradle.api.GradleException("Missing patched LXST wheel at ${lxstPatchedWheel}")
|
|
}
|
|
install lxstPatchedWheel.absolutePath
|
|
if (!bleakPatchedWheel.exists()) {
|
|
throw new org.gradle.api.GradleException("Missing patched bleak wheel at ${bleakPatchedWheel}")
|
|
}
|
|
install bleakPatchedWheel.absolutePath
|
|
install "psutil>=7.2.2"
|
|
install "websockets>=16.0"
|
|
install "bcrypt==3.1.7"
|
|
install "aiohttp-session>=2.12.1,<3.0.0"
|
|
install "cryptography==50.0.0"
|
|
install "pycparser>=3.0"
|
|
install "pyserial>=3.5"
|
|
install "usbserial4a==0.4.0"
|
|
install "ply>=3.11,<4.0"
|
|
install "cbor2==5.6.5"
|
|
// Pure-python; wheels are vendored by scripts/build-android-wheels-local.sh
|
|
// for offline/find-links installs (HTTPInterface / RNS-over-HTTP).
|
|
install "httpx[http2]==0.28.1"
|
|
install "altcha==2.0.2"
|
|
}
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'androidx.core:core:1.16.0'
|
|
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
|
implementation 'androidx.appcompat:appcompat:1.7.1'
|
|
implementation 'com.google.android.material:material:1.13.0'
|
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
|
implementation 'androidx.webkit:webkit:1.14.0'
|
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
|
|
implementation 'com.github.mik3y:usb-serial-for-android:3.8.1'
|
|
testImplementation 'junit:junit:4.13.2'
|
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
|
}
|