1- import java.security.DigestInputStream
2- import java.util.zip.ZipEntry
3- import java.util.zip.ZipInputStream
4-
5-
61plugins {
72 id " com.android.application"
83}
9- apply plugin : ' kotlin-android'
4+
5+ ext {
6+ // The packageVariant defines the bootstrap variant that will be included in the app APK.
7+ // This must be supported by com.termux.shared.termux.TermuxBootstrap.PackageVariant or app will
8+ // crash at startup.
9+ // Bootstrap of a different variant must not be manually installed by the user after app installation
10+ // by replacing $PREFIX since app code is dependant on the variant used to build the APK.
11+ // Currently supported values are: [ "apt-android-7" "apt-android-5" ]
12+ packageVariant = System . getenv(" TERMUX_PACKAGE_VARIANT" ) ?: " apt-android-7" // Default: "apt-android-7"
13+ }
1014
1115android {
1216 namespace " com.termux"
@@ -37,20 +41,39 @@ android {
3741 }
3842
3943 defaultConfig {
40- applicationId " com.termux"
41- minSdkVersion 21
42- targetSdkVersion 28
43- versionCode 83
44- versionName " 0.83"
44+ minSdkVersion project. properties. minSdkVersion. toInteger()
45+ targetSdkVersion project. properties. targetSdkVersion. toInteger()
46+ versionCode 118
47+ versionName " 0.118.0"
48+
49+ if (appVersionName) versionName = appVersionName
50+ validateVersionName(versionName)
51+
52+ buildConfigField " String" , " TERMUX_PACKAGE_VARIANT" , " \" " + project. ext. packageVariant + " \" " // Used by TermuxApplication class
53+
54+ manifestPlaceholders. TERMUX_PACKAGE_NAME = " com.termux"
55+ manifestPlaceholders. TERMUX_APP_NAME = " Termux"
56+ manifestPlaceholders. TERMUX_API_APP_NAME = " Termux:API"
57+ manifestPlaceholders. TERMUX_BOOT_APP_NAME = " Termux:Boot"
58+ manifestPlaceholders. TERMUX_FLOAT_APP_NAME = " Termux:Float"
59+ manifestPlaceholders. TERMUX_STYLING_APP_NAME = " Termux:Styling"
60+ manifestPlaceholders. TERMUX_TASKER_APP_NAME = " Termux:Tasker"
61+ manifestPlaceholders. TERMUX_WIDGET_APP_NAME = " Termux:Widget"
4562
4663 externalNativeBuild {
4764 ndkBuild {
4865 cFlags " -std=c11" , " -Wall" , " -Wextra" , " -Werror" , " -Os" , " -fno-stack-protector" , " -Wl,--gc-sections"
4966 }
5067 }
5168
52- ndk {
53- abiFilters ' x86' , ' x86_64' , ' armeabi-v7a' , ' arm64-v8a'
69+ splits {
70+ abi {
71+ enable ((gradle. startParameter. taskNames. any { it. contains(" Debug" ) } && splitAPKsForDebugBuilds == " 1" ) ||
72+ (gradle. startParameter. taskNames. any { it. contains(" Release" ) } && splitAPKsForReleaseBuilds == " 1" ))
73+ reset ()
74+ include ' x86' , ' x86_64' , ' armeabi-v7a' , ' arm64-v8a'
75+ universalApk true
76+ }
5477 }
5578 }
5679
@@ -82,8 +105,11 @@ android {
82105 sourceCompatibility JavaVersion . VERSION_17
83106 targetCompatibility JavaVersion . VERSION_17
84107 }
85- kotlinOptions {
86- jvmTarget = JavaVersion . VERSION_1_8
108+
109+ externalNativeBuild {
110+ ndkBuild {
111+ path " src/main/cpp/Android.mk"
112+ }
87113 }
88114
89115 lint {
@@ -120,15 +146,9 @@ android {
120146}
121147
122148dependencies {
123- testImplementation ' junit:junit:4.13'
124- testImplementation ' org.robolectric:robolectric:4.3.1'
125-
126- // kotlin
127- implementation " androidx.core:core-ktx:1.3.2"
128- implementation " org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version "
129- implementation ' org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
130- implementation ' org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
131-
149+ testImplementation " junit:junit:4.13.2"
150+ testImplementation " org.robolectric:robolectric:4.10"
151+ coreLibraryDesugaring " com.android.tools:desugar_jdk_libs:1.1.5"
132152}
133153
134154task versionName {
@@ -137,117 +157,87 @@ task versionName {
137157 }
138158}
139159
140- def setupBootstrap (String arch , String expectedChecksum , int version ) {
141- def digest = java.security.MessageDigest . getInstance(" SHA-256" )
160+ def validateVersionName (String versionName ) {
161+ // https://semver.org/spec/v2.0.0.html#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
162+ // ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
163+ if (! java.util.regex.Pattern . matches(" ^(0|[1-9]\\ d*)\\ .(0|[1-9]\\ d*)\\ .(0|[1-9]\\ d*)(?:-((?:0|[1-9]\\ d*|\\ d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\ .(?:0|[1-9]\\ d*|\\ d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\ +([0-9a-zA-Z-]+(?:\\ .[0-9a-zA-Z-]+)*))?\$ " , versionName))
164+ throw new GradleException (" The versionName '" + versionName + " ' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html." )
165+ }
142166
143- def zipDownloadFile = new File (project. buildDir, " ./gradle/bootstrap-" + arch + " -" + version + " .zip" )
167+ def downloadBootstrap (String arch , String expectedChecksum , String version ) {
168+ def digest = java.security.MessageDigest . getInstance(" SHA-256" )
144169
145- if (zipDownloadFile. exists()) {
170+ def localUrl = " src/main/cpp/bootstrap-" + arch + " .zip"
171+ def file = new File (projectDir, localUrl)
172+ if (file. exists()) {
146173 def buffer = new byte [8192 ]
147- def input = new FileInputStream (zipDownloadFile )
174+ def input = new FileInputStream (file )
148175 while (true ) {
149176 def readBytes = input. read(buffer)
150177 if (readBytes < 0 ) break
151178 digest. update(buffer, 0 , readBytes)
152179 }
153180 def checksum = new BigInteger (1 , digest. digest()). toString(16 )
154- if (checksum != expectedChecksum) {
155- logger. quiet(" Deleting old local file with wrong hash: " + zipDownloadFile. getAbsolutePath())
156- zipDownloadFile. delete()
181+ while (checksum. length() < 64 ) { checksum = " 0" + checksum }
182+ if (checksum == expectedChecksum) {
183+ return
184+ } else {
185+ logger. quiet(" Deleting old local file with wrong hash: " + localUrl + " : expected: " + expectedChecksum + " , actual: " + checksum)
186+ file. delete()
157187 }
158188 }
159189
160- if (! zipDownloadFile. exists()) {
161- def remoteUrl = " https://bintray.com/termux/bootstrap/download_file?file_path=android10-v" + version + " -bootstrap-" + arch + " .zip"
162- logger. quiet(" Downloading " + remoteUrl + " ..." )
190+ def remoteUrl = " https://github.com/termux/termux-packages/releases/download/bootstrap-" + version + " /bootstrap-" + arch + " .zip"
191+ logger. quiet(" Downloading " + remoteUrl + " ..." )
163192
164- zipDownloadFile . parentFile. mkdirs()
165- def out = new BufferedOutputStream (new FileOutputStream (zipDownloadFile ))
193+ file . parentFile. mkdirs()
194+ def out = new BufferedOutputStream (new FileOutputStream (file ))
166195
167- def connection = new URL (remoteUrl). openConnection()
168- connection. setInstanceFollowRedirects(true )
169- def digestStream = new DigestInputStream (connection. inputStream, digest)
170- out << digestStream
171- out. close()
196+ def connection = new URL (remoteUrl). openConnection()
197+ connection. setInstanceFollowRedirects(true )
198+ def digestStream = new java.security. DigestInputStream (connection. inputStream, digest)
199+ out << digestStream
200+ out. close()
172201
173- def checksum = new BigInteger (1 , digest. digest()). toString(16 )
174- if ( checksum != expectedChecksum) {
175- zipDownloadFile . delete()
176- throw new GradleException ( " Wrong checksum for " + remoteUrl + " : expected: " + expectedChecksum + " , actual: " + checksum )
177- }
202+ def checksum = new BigInteger (1 , digest. digest()). toString(16 )
203+ while (checksum . length() < 64 ) { checksum = " 0 " + checksum }
204+ if (checksum != expectedChecksum) {
205+ file . delete( )
206+ throw new GradleException ( " Wrong checksum for " + remoteUrl + " : expected: " + expectedChecksum + " , actual: " + checksum)
178207 }
208+ }
179209
180- def doneMarkerFile = new File (zipDownloadFile. getAbsolutePath() + " ." + expectedChecksum + " .done" )
181-
182- if (doneMarkerFile. exists()) return
183-
184- def archDirName
185- if (arch == " aarch64" ) archDirName = " arm64-v8a" ;
186- if (arch == " arm" ) archDirName = " armeabi-v7a" ;
187- if (arch == " i686" ) archDirName = " x86" ;
188- if (arch == " x86_64" ) archDirName = " x86_64" ;
189-
190- def outputPath = project. getRootDir(). getAbsolutePath() + " /app/src/main/jniLibs/" + archDirName + " /"
191- def outputDir = new File (outputPath). getAbsoluteFile()
192- if (! outputDir. exists()) outputDir. mkdirs()
193-
194- def symlinksFile = new File (outputDir, " libsymlinks.so" ). getAbsoluteFile()
195- if (symlinksFile. exists()) symlinksFile. delete();
196-
197- def mappingsFile = new File (outputDir, " libfiles.so" ). getAbsoluteFile()
198- if (mappingsFile. exists()) mappingsFile. delete()
199- mappingsFile. createNewFile()
200- def mappingsFileWriter = new BufferedWriter (new FileWriter (mappingsFile))
201-
202- def counter = 100
203- new ZipInputStream (new FileInputStream (zipDownloadFile)). withCloseable { zipInput ->
204- ZipEntry zipEntry
205- while ((zipEntry = zipInput. getNextEntry()) != null ) {
206- if (zipEntry. getName() == " SYMLINKS.txt" ) {
207- zipInput. transferTo(new FileOutputStream (symlinksFile))
208- } else if (! zipEntry. isDirectory()) {
209- def soName = " lib" + counter + " .so"
210- def targetFile = new File (outputDir, soName). getAbsoluteFile()
211-
212- println " target file path is ${ targetFile} "
213-
214- try {
215- zipInput. transferTo(new FileOutputStream (targetFile))
216- } catch (Exception e) {
217- println " Error ${ e} "
218- }
219-
220-
221- if (zipEntry. getName(). endsWith(" /pkg" )) {
222- def pkgScript = new FileInputStream (project. getRootDir(). getAbsolutePath() + " /pkg.sh" )
223- pkgScript. transferTo(new FileOutputStream (targetFile))
224- }
225-
226- mappingsFileWriter. writeLine(soName + " ←" + zipEntry. getName())
227- counter++
228- }
229- }
210+ clean {
211+ doLast {
212+ def tree = fileTree(new File (projectDir, ' src/main/cpp' ))
213+ tree. include ' bootstrap-*.zip'
214+ tree. each { it. delete() }
230215 }
231-
232- mappingsFileWriter. close()
233- doneMarkerFile. createNewFile()
234216}
235217
236- task setupBootstraps () {
218+ task downloadBootstraps () {
237219 doLast {
238- def version = 12
239- setupBootstrap(" aarch64" , " 5e07239cad78050f56a28f9f88a0b485cead45864c6c00e1a654c728152b0244" , version)
240- setupBootstrap(" arm" , " fc72279c480c1eea46b6f0fcf78dc57599116c16dcf3b2b970a9ef828f0ec30b" , version)
241- setupBootstrap(" i686" , " 895680fc967aecfa4ed77b9dc03aab95d86345be69df48402c63bfc0178337f6" , version)
242- setupBootstrap(" x86_64" , " 8714ab8a5ff4e1f5f3ec01e7d0294776bfcffb187c84fa95270ec67ede8f682e" , version)
220+ def packageVariant = project. ext. packageVariant
221+ if (packageVariant == " apt-android-7" ) {
222+ def version = " 2026.02.12-r1" + " %2B" + " apt.android-7"
223+ downloadBootstrap(" aarch64" , " ea2aeba8819e517db711f8c32369e89e7c52cee73e07930ff91185e1ab93f4f3" , version)
224+ downloadBootstrap(" arm" , " a38f4d3b2f735f83be2bf54eff463e86dc32a3e2f9f861c1557c4378d249c018" , version)
225+ downloadBootstrap(" i686" , " f5bc0b025b9f3b420b5fcaeefc064f888f5f22a0d6fd7090f4aac0c33eb3555b" , version)
226+ downloadBootstrap(" x86_64" , " b7fd0f2e3a4de534be3144f9f91acc768630fc463eaf134ab2e64c545e834f7a" , version)
227+ } else if (packageVariant == " apt-android-5" ) {
228+ def version = " 2022.04.28-r6" + " +" + packageVariant
229+ downloadBootstrap(" aarch64" , " 913609d439415c828c5640be1b0561467e539cb1c7080662decaaca2fb4820e7" , version)
230+ downloadBootstrap(" arm" , " 26bfb45304c946170db69108e5eb6e3641aad751406ce106c80df80cad2eccf8" , version)
231+ downloadBootstrap(" i686" , " 46dcfeb5eef67ba765498db9fe4c50dc4690805139aa0dd141a9d8ee0693cd27" , version)
232+ downloadBootstrap(" x86_64" , " 615b590679ee6cd885b7fd2ff9473c845e920f9b422f790bb158c63fe42b8481" , version)
233+ } else {
234+ throw new GradleException (" Unsupported TERMUX_PACKAGE_VARIANT \" " + packageVariant + " \" " )
235+ }
243236 }
244237}
245238
246239afterEvaluate {
247240 android. applicationVariants. all { variant ->
248- variant. javaCompileProvider. get(). dependsOn(setupBootstraps )
241+ variant. javaCompileProvider. get(). dependsOn(downloadBootstraps )
249242 }
250243}
251- repositories {
252- mavenCentral()
253- }
0 commit comments