Skip to content

Commit c76cec1

Browse files
committed
Include bootstrap zips as shared libraries
1 parent 5ba3f7c commit c76cec1

File tree

6 files changed

+131
-31
lines changed

6 files changed

+131
-31
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ build/
88
*.so
99
.externalNativeBuild
1010
.cxx
11+
*.zip
1112

1213
# Crashlytics configuations
1314
com_crashlytics_export_strings.xml

app/build.gradle

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
apply plugin: 'com.android.application'
1+
plugins {
2+
id "com.android.application"
3+
}
24

35
android {
46
compileSdkVersion 28
@@ -12,10 +14,21 @@ android {
1214

1315
defaultConfig {
1416
applicationId "com.termux"
15-
minSdkVersion 21
17+
minSdkVersion 24
1618
targetSdkVersion 28
1719
versionCode 75
1820
versionName "0.75"
21+
22+
externalNativeBuild {
23+
ndkBuild {
24+
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
25+
}
26+
}
27+
28+
ndk {
29+
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
30+
}
31+
1932
}
2033

2134
signingConfigs {
@@ -43,6 +56,12 @@ android {
4356
sourceCompatibility JavaVersion.VERSION_1_8
4457
targetCompatibility JavaVersion.VERSION_1_8
4558
}
59+
60+
externalNativeBuild {
61+
ndkBuild {
62+
path "src/main/cpp/Android.mk"
63+
}
64+
}
4665
}
4766

4867
dependencies {
@@ -54,3 +73,69 @@ task versionName {
5473
print android.defaultConfig.versionName
5574
}
5675
}
76+
77+
def downloadBootstrap(String arch, String expectedChecksum, int version) {
78+
def digest = java.security.MessageDigest.getInstance("SHA-256")
79+
80+
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
81+
def file = new File(projectDir, localUrl)
82+
if (file.exists()) {
83+
def buffer = new byte[8192]
84+
def input = new FileInputStream(file)
85+
while (true) {
86+
def readBytes = input.read(buffer)
87+
if (readBytes < 0) break
88+
digest.update(buffer, 0, readBytes)
89+
}
90+
def checksum = new BigInteger(1, digest.digest()).toString(16)
91+
if (checksum == expectedChecksum) {
92+
return
93+
} else {
94+
logger.quiet("Deleting old local file with wrong hash: " + localUrl)
95+
file.delete()
96+
}
97+
}
98+
99+
def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip"
100+
logger.quiet("Downloading " + remoteUrl + " ...")
101+
102+
file.parentFile.mkdirs()
103+
def out = new BufferedOutputStream(new FileOutputStream(file))
104+
105+
def connection = new URL(remoteUrl).openConnection()
106+
connection.setInstanceFollowRedirects(true)
107+
def digestStream = new java.security.DigestInputStream(connection.inputStream, digest)
108+
out << digestStream
109+
out.close()
110+
111+
def checksum = new BigInteger(1, digest.digest()).toString(16)
112+
if (checksum != expectedChecksum) {
113+
file.delete()
114+
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
115+
}
116+
}
117+
118+
clean {
119+
doLast {
120+
def tree = fileTree(new File(projectDir, 'src/main/cpp'))
121+
tree.include 'bootstrap-*.zip'
122+
tree.each { it.delete() }
123+
}
124+
}
125+
126+
task downloadBootstraps(){
127+
doLast {
128+
def version = 17
129+
downloadBootstrap("aarch64", "1846c453e7d4399559683e9a74eb8db048d1e88872668361652752b40ea2f3a2", version)
130+
downloadBootstrap("arm", "2f5a8061a1c13d48b659cb57c3767d59f5151f247ea032e2401135701dbd8058", version)
131+
downloadBootstrap("i686", "ca0e3559c5ac925528a7c8725a83a752ca63d5c1e0ff306e5ecd6ea9dd4c1de5", version)
132+
downloadBootstrap("x86_64", "9de2ac75ed0f3370418d3c55b470c176f69180079ba9461034bc7f6195fc5872", version)
133+
}
134+
}
135+
136+
afterEvaluate {
137+
android.applicationVariants.all { variant ->
138+
variant.javaCompiler.dependsOn(downloadBootstraps)
139+
}
140+
}
141+

app/src/main/cpp/Android.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
LOCAL_PATH:= $(call my-dir)
2+
include $(CLEAR_VARS)
3+
LOCAL_MODULE := libtermux-bootstrap
4+
LOCAL_SRC_FILES := termux-bootstrap-zip.S termux-bootstrap.c
5+
include $(BUILD_SHARED_LIBRARY)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.global blob
2+
.global blob_size
3+
.section .rodata
4+
blob:
5+
#if defined __i686__
6+
.incbin "bootstrap-i686.zip"
7+
#elif defined __x86_64__
8+
.incbin "bootstrap-x86_64.zip"
9+
#elif defined __aarch64__
10+
.incbin "bootstrap-aarch64.zip"
11+
#elif defined __arm__
12+
.incbin "bootstrap-arm.zip"
13+
#else
14+
# error Unsupported arch
15+
#endif
16+
1:
17+
blob_size:
18+
.int 1b - blob

app/src/main/cpp/termux-bootstrap.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <jni.h>
2+
3+
extern jbyte blob[];
4+
extern int blob_size;
5+
6+
JNIEXPORT jbyteArray JNICALL Java_com_termux_app_TermuxInstaller_getZip(JNIEnv *env, __attribute__((__unused__)) jobject This)
7+
{
8+
jbyteArray ret = (*env)->NewByteArray(env, blob_size);
9+
(*env)->SetByteArrayRegion(env, ret, 0, blob_size, blob);
10+
return ret;
11+
}

app/src/main/java/com/termux/app/TermuxInstaller.java

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.termux.terminal.EmulatorDebug;
1717

1818
import java.io.BufferedReader;
19+
import java.io.ByteArrayInputStream;
1920
import java.io.File;
2021
import java.io.FileOutputStream;
2122
import java.io.IOException;
@@ -38,7 +39,7 @@
3839
* <p/>
3940
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
4041
* <p/>
41-
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
42+
* (4) The zip file is loaded from a shared library.
4243
* <p/>
4344
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
4445
* continuously encountering zip file entries:
@@ -82,8 +83,8 @@ public void run() {
8283
final byte[] buffer = new byte[8096];
8384
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
8485

85-
final URL zipUrl = determineZipUrl();
86-
try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
86+
final byte[] zipBytes = loadZipBytes();
87+
try (ZipInputStream zipInput = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
8788
ZipEntry zipEntry;
8889
while ((zipEntry = zipInput.getNextEntry()) != null) {
8990
if (zipEntry.getName().equals("SYMLINKS.txt")) {
@@ -167,34 +168,13 @@ private static void ensureDirectoryExists(File directory) {
167168
}
168169
}
169170

170-
/** Get bootstrap zip url for this systems cpu architecture. */
171-
private static URL determineZipUrl() throws MalformedURLException {
172-
String archName = determineTermuxArchName();
173-
String url = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
174-
? "https://termux.org/bootstrap-" + archName + ".zip"
175-
: "https://termux.net/bootstrap/bootstrap-" + archName + ".zip";
176-
return new URL(url);
171+
public static byte[] loadZipBytes() {
172+
// Only load the shared library when necessary to save memory usage.
173+
System.loadLibrary("termux-bootstrap");
174+
return getZip();
177175
}
178176

179-
private static String determineTermuxArchName() {
180-
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
181-
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
182-
// Instead we search through the supported abi:s on the device, see:
183-
// http://developer.android.com/ndk/guides/abis.html
184-
// Note that we search for abi:s in preferred order (the ordering of the
185-
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
186-
// emulation is available.
187-
for (String androidArch : Build.SUPPORTED_ABIS) {
188-
switch (androidArch) {
189-
case "arm64-v8a": return "aarch64";
190-
case "armeabi-v7a": return "arm";
191-
case "x86_64": return "x86_64";
192-
case "x86": return "i686";
193-
}
194-
}
195-
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
196-
Arrays.toString(Build.SUPPORTED_ABIS));
197-
}
177+
public static native byte[] getZip();
198178

199179
/** Delete a folder and all its content or throw. Don't follow symlinks. */
200180
static void deleteFolder(File fileOrDirectory) throws IOException {

0 commit comments

Comments
 (0)