plugins { id 'com.android.application' id 'com.chaquo.python' } def repoRoot = rootProject.projectDir.parentFile def meshchatSourceDir = new File(repoRoot, "meshchatx") 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.4.6-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 1 versionName "3.1.0" 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("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_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")) } tasks.configureEach { task -> if (task.name.toLowerCase().contains("merge") && task.name.toLowerCase().contains("pythonsources")) { task.dependsOn(tasks.named("syncMeshchatPython")) } } 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.13.3" install "rns>=1.2.0" install "bleak==3.0.1" install "lxmf>=0.9.4" 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 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==46.0.7" install "pycparser>=3.0" install "pyserial>=3.5" install "jaraco.context>=6.1.1" install "ply>=3.11,<4.0" } } } 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' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' }