From f7c8f00fe70a9586da5ee46219c5493e54cc1236 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Wed, 15 Jan 2020 03:07:35 +0100 Subject: [PATCH 01/12] Example of support for packages in APK --- .../java/com/termux/app/TermuxInstaller.java | 2 +- .../termux/app/TermuxPackageInstaller.java | 131 ++++++++++++++++++ .../java/com/termux/app/TermuxService.java | 13 ++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/termux/app/TermuxPackageInstaller.java diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index 6e50b22dd6..0723f31af1 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -158,7 +158,7 @@ public void run() { }.start(); } - private static void ensureDirectoryExists(File directory) { + static void ensureDirectoryExists(File directory) { if (!directory.isDirectory() && !directory.mkdirs()) { throw new RuntimeException("Unable to create directory: " + directory.getAbsolutePath()); } diff --git a/app/src/main/java/com/termux/app/TermuxPackageInstaller.java b/app/src/main/java/com/termux/app/TermuxPackageInstaller.java new file mode 100644 index 0000000000..0602870f4b --- /dev/null +++ b/app/src/main/java/com/termux/app/TermuxPackageInstaller.java @@ -0,0 +1,131 @@ +package com.termux.app; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Process; +import android.system.Os; +import android.util.Log; + +import com.termux.terminal.EmulatorDebug; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class TermuxPackageInstaller extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + try { + String packageName = intent.getData().getSchemeSpecificPart(); + String action = intent.getAction(); + PackageManager packageManager = context.getPackageManager(); + + if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + ApplicationInfo info = packageManager.getApplicationInfo(packageName, 0); + if (Process.myUid() == info.uid) { + installPackage(info); + } + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + if (Process.myUid() == intent.getIntExtra(Intent.EXTRA_UID, -1)) { + uninstallPackage(packageName); + } + + } + } catch (Exception e) { + Log.e("termux", "Error in package management: " + e); + } + } + + private static void installPackage(ApplicationInfo info) throws Exception { + File filesMappingFile = new File(info.nativeLibraryDir, "files.so"); + if (!filesMappingFile.exists()) { + Log.e("termux", "No file mapping at " + filesMappingFile.getAbsolutePath()); + return; + } + + Log.e("termux", "Installing: " + info.packageName); + BufferedReader reader = new BufferedReader(new FileReader(filesMappingFile)); + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split("←"); + if (parts.length != 2) { + Log.e(EmulatorDebug.LOG_TAG, "Malformed line " + line + " in " + filesMappingFile.getAbsolutePath()); + continue; + } + + String oldPath = info.nativeLibraryDir + "/" + parts[0]; + String newPath = TermuxService.PREFIX_PATH + "/" + parts[1]; + + TermuxInstaller.ensureDirectoryExists(new File(newPath).getParentFile()); + + Log.e(EmulatorDebug.LOG_TAG, "About to setup link: " + oldPath + " ← " + newPath); + new File(newPath).delete(); + Os.symlink(oldPath, newPath); + } + + File symlinksFile = new File(info.nativeLibraryDir, "symlinks.so"); + if (!symlinksFile.exists()) { + Log.e("termux", "No symlinks mapping at " + symlinksFile.getAbsolutePath()); + } + + reader = new BufferedReader(new FileReader(symlinksFile)); + while ((line = reader.readLine()) != null) { + String[] parts = line.split("←"); + if (parts.length != 2) { + Log.e(EmulatorDebug.LOG_TAG, "Malformed line " + line + " in " + symlinksFile.getAbsolutePath()); + continue; + } + + String oldPath = parts[0]; + String newPath = TermuxService.PREFIX_PATH + "/" + parts[1]; + + TermuxInstaller.ensureDirectoryExists(new File(newPath).getParentFile()); + + Log.e(EmulatorDebug.LOG_TAG, "About to setup link: " + oldPath + " ← " + newPath); + new File(newPath).delete(); + Os.symlink(oldPath, newPath); + } + } + + private static void uninstallPackage(String packageName) throws IOException { + Log.e("termux", "Uninstalling: " + packageName); + // We're currently visiting the whole $PREFIX. + // If we store installed symlinks in installPackage() we could just visit those, + // at the cost of increased complexity and risk for errors. + File prefixDir = new File(TermuxService.PREFIX_PATH); + removeBrokenSymlinks(prefixDir); + } + + private static void removeBrokenSymlinks(File parentDir) throws IOException { + for (File child : parentDir.listFiles()) { + if (!child.exists()) { + Log.e("termux", "Removing broken symlink: " + child.getAbsolutePath()); + child.delete(); + } else if (child.isDirectory()) { + removeBrokenSymlinks(child); + } + } + } + + public static void setupAllInstalledPackages(Context context) { + try { + removeBrokenSymlinks(new File(TermuxService.PREFIX_PATH)); + + PackageManager packageManager = context.getPackageManager(); + for (PackageInfo info : packageManager.getInstalledPackages(0)) { + if (info.sharedUserId != null && info.sharedUserId.equals("com.termux")) { + installPackage(info.applicationInfo); + } + } + } catch (Exception e) { + Log.e("termux", "Error setting up all packages", e); + } + + } +} diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 955ce8656b..f27abfd4c8 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -9,6 +9,7 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.net.Uri; import android.net.wifi.WifiManager; @@ -94,6 +95,8 @@ class LocalBinder extends Binder { /** If the user has executed the {@link #ACTION_STOP_SERVICE} intent. */ boolean mWantsToStop = false; + private final TermuxPackageInstaller packageInstaller = new TermuxPackageInstaller(); + @SuppressLint("Wakelock") @Override public int onStartCommand(Intent intent, int flags, int startId) { @@ -185,8 +188,16 @@ public IBinder onBind(Intent intent) { @Override public void onCreate() { + TermuxPackageInstaller.setupAllInstalledPackages(this); setupNotificationChannel(); startForeground(NOTIFICATION_ID, buildNotification()); + + IntentFilter addedFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + addedFilter.addDataScheme("package"); + IntentFilter removedFilter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); + removedFilter.addDataScheme("package"); + this.registerReceiver(packageInstaller, addedFilter); + this.registerReceiver(packageInstaller, removedFilter); } /** Update the shown foreground service notification after making any changes that affect it. */ @@ -254,6 +265,8 @@ private Notification buildNotification() { @Override public void onDestroy() { + unregisterReceiver(packageInstaller); + File termuxTmpDir = new File(TermuxService.PREFIX_PATH + "/tmp"); if (termuxTmpDir.exists()) { From ec8424ed7a6b5123e09b9af270d42221ab25a37b Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Thu, 20 Feb 2020 01:58:57 +0100 Subject: [PATCH 02/12] Target Android 10 --- app/build.gradle | 122 ++++++++++-------- app/src/main/cpp/Android.mk | 5 - app/src/main/cpp/termux-bootstrap-zip.S | 18 --- app/src/main/cpp/termux-bootstrap.c | 11 -- .../java/com/termux/app/TermuxInstaller.java | 87 +------------ .../termux/app/TermuxPackageInstaller.java | 8 +- gradle.properties | 4 +- 7 files changed, 79 insertions(+), 176 deletions(-) delete mode 100644 app/src/main/cpp/Android.mk delete mode 100644 app/src/main/cpp/termux-bootstrap-zip.S delete mode 100644 app/src/main/cpp/termux-bootstrap.c diff --git a/app/build.gradle b/app/build.gradle index 7ad7cb9a57..d6a1414acb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,17 +19,6 @@ android { targetSdkVersion project.properties.targetSdkVersion.toInteger() versionCode 98 versionName "0.98" - - externalNativeBuild { - ndkBuild { - cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections" - } - } - - ndk { - abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' - } - } signingConfigs { @@ -58,12 +47,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - externalNativeBuild { - ndkBuild { - path "src/main/cpp/Android.mk" - } - } - testOptions { unitTests { includeAndroidResources = true @@ -82,67 +65,100 @@ task versionName { } } -def downloadBootstrap(String arch, String expectedChecksum, int version) { +def setupBootstrap(String arch, String expectedChecksum, int version) { def digest = java.security.MessageDigest.getInstance("SHA-256") - def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip" - def file = new File(projectDir, localUrl) - if (file.exists()) { + def zipDownloadFile = new File(project.buildDir, "./gradle/bootstrap-" + arch + ".zip") + + if (zipDownloadFile.exists()) { def buffer = new byte[8192] - def input = new FileInputStream(file) + def input = new FileInputStream(zipDownloadFile) while (true) { def readBytes = input.read(buffer) if (readBytes < 0) break digest.update(buffer, 0, readBytes) } def checksum = new BigInteger(1, digest.digest()).toString(16) - if (checksum == expectedChecksum) { - return - } else { - logger.quiet("Deleting old local file with wrong hash: " + localUrl) - file.delete() + if (checksum != expectedChecksum) { + logger.quiet("Deleting old local file with wrong hash: " + zipDownloadFile.getAbsolutePath()) + zipDownloadFile.delete() } } - def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip" - logger.quiet("Downloading " + remoteUrl + " ...") + if (!zipDownloadFile.exists()) { + def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip" + logger.quiet("Downloading " + remoteUrl + " ...") - file.parentFile.mkdirs() - def out = new BufferedOutputStream(new FileOutputStream(file)) + zipDownloadFile.parentFile.mkdirs() + def out = new BufferedOutputStream(new FileOutputStream(zipDownloadFile)) - def connection = new URL(remoteUrl).openConnection() - connection.setInstanceFollowRedirects(true) - def digestStream = new java.security.DigestInputStream(connection.inputStream, digest) - out << digestStream - out.close() + def connection = new URL(remoteUrl).openConnection() + connection.setInstanceFollowRedirects(true) + def digestStream = new java.security.DigestInputStream(connection.inputStream, digest) + out << digestStream + out.close() - def checksum = new BigInteger(1, digest.digest()).toString(16) - if (checksum != expectedChecksum) { - file.delete() - throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum) + def checksum = new BigInteger(1, digest.digest()).toString(16) + if (checksum != expectedChecksum) { + zipDownloadFile.delete() + throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum) + } } -} -clean { - doLast { - def tree = fileTree(new File(projectDir, 'src/main/cpp')) - tree.include 'bootstrap-*.zip' - tree.each { it.delete() } + def doneMarkerFile = new File(zipDownloadFile.getAbsolutePath() + ".done") + + if (doneMarkerFile.exists()) return + + def archDirName + if (arch == "aarch64") archDirName = "arm64-v8a"; + if (arch == "arm") archDirName = "armeabi-v7a"; + if (arch == "i686") archDirName = "x86"; + if (arch == "x86_64") archDirName = "x86_64"; + + def outputPath = project.getRootDir().getAbsolutePath() + "/app/src/main/jniLibs/" + archDirName + "/" + def outputDir = new File(outputPath).getAbsoluteFile() + if (!outputDir.exists()) outputDir.mkdirs() + + def symlinksFile = new File(outputDir, "symlinks.so").getAbsoluteFile() + if (symlinksFile.exists()) symlinksFile.delete(); + + def mappingsFile = new File(outputDir, "files.so").getAbsoluteFile() + if (mappingsFile.exists()) mappingsFile.delete() + mappingsFile.createNewFile() + def mappingsFileWriter = new BufferedWriter(new FileWriter(mappingsFile)) + + def counter = 100 + new java.util.zip.ZipInputStream(new FileInputStream(zipDownloadFile)).withCloseable { zipInput -> + java.util.zip.ZipEntry zipEntry + while ((zipEntry = zipInput.getNextEntry()) != null) { + if (zipEntry.getName() == "SYMLINKS.txt") { + zipInput.transferTo(new FileOutputStream(symlinksFile)) + } else if (!zipEntry.isDirectory()) { + def soName = counter + ".so" + def targetFile = new File(outputDir, soName).getAbsoluteFile() + + zipInput.transferTo(new FileOutputStream(targetFile)) + mappingsFileWriter.writeLine(soName + "←" + zipEntry.getName()) + counter++ + } + } } + + doneMarkerFile.createNewFile() } -task downloadBootstraps(){ +task setupBootstraps(){ doLast { - def version = 27 - downloadBootstrap("aarch64", "517fb3aa215f7b96961f9377822d7f1b5e86c831efb4ab096ed65d0b1cdf02e9", version) - downloadBootstrap("arm", "94d17183afdd017cf8ab885b9103a370b16bec1d3cb641884511d545ee009b90", version) - downloadBootstrap("i686", "7f27723d2f0afbe7e90f203b3ca2e80871a8dfa08b136229476aa5e7ba3e988f", version) - downloadBootstrap("x86_64", "b19b2721bae5fb3a3fb0754c49611ce4721221e1e7997e7fd98940776ad88c3d", version) + def version = 20 + setupBootstrap("aarch64", "2ea6aaff12d8316223e5c1f22719d20633fae669d6461a6802b67b4adbe796de", version) + setupBootstrap("arm", "8a3a7e8adeff8eb769b03cad947f81b8c42b7c4c8edeea37c71a9d7abd9de99c", version) + setupBootstrap("i686", "b3e1f8e3ccb695d6fab7714c62b2028fbc37187ccfaff0a9f6bd64f738bc5adc", version) + setupBootstrap("x86_64", "2a9f6adbfb6f5e7c0bd03e022856a140768fa25ada850384d635c25c8e966ea3", version) } } afterEvaluate { android.applicationVariants.all { variant -> - variant.javaCompileProvider.get().dependsOn(downloadBootstraps) + variant.javaCompileProvider.get().dependsOn(setupBootstraps) } } diff --git a/app/src/main/cpp/Android.mk b/app/src/main/cpp/Android.mk deleted file mode 100644 index d013e2ed98..0000000000 --- a/app/src/main/cpp/Android.mk +++ /dev/null @@ -1,5 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := libtermux-bootstrap -LOCAL_SRC_FILES := termux-bootstrap-zip.S termux-bootstrap.c -include $(BUILD_SHARED_LIBRARY) diff --git a/app/src/main/cpp/termux-bootstrap-zip.S b/app/src/main/cpp/termux-bootstrap-zip.S deleted file mode 100644 index 1cfc958049..0000000000 --- a/app/src/main/cpp/termux-bootstrap-zip.S +++ /dev/null @@ -1,18 +0,0 @@ - .global blob - .global blob_size - .section .rodata - blob: - #if defined __i686__ - .incbin "bootstrap-i686.zip" - #elif defined __x86_64__ - .incbin "bootstrap-x86_64.zip" - #elif defined __aarch64__ - .incbin "bootstrap-aarch64.zip" - #elif defined __arm__ - .incbin "bootstrap-arm.zip" - #else - # error Unsupported arch - #endif - 1: - blob_size: - .int 1b - blob diff --git a/app/src/main/cpp/termux-bootstrap.c b/app/src/main/cpp/termux-bootstrap.c deleted file mode 100644 index 8ba745ffca..0000000000 --- a/app/src/main/cpp/termux-bootstrap.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -extern jbyte blob[]; -extern int blob_size; - -JNIEXPORT jbyteArray JNICALL Java_com_termux_app_TermuxInstaller_getZip(JNIEnv *env, __attribute__((__unused__)) jobject This) -{ - jbyteArray ret = (*env)->NewByteArray(env, blob_size); - (*env)->SetByteArrayRegion(env, ret, 0, blob_size, blob); - return ret; -} diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index 0723f31af1..61adc89cf2 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -8,41 +8,16 @@ import android.os.UserManager; import android.system.Os; import android.util.Log; -import android.util.Pair; import android.view.WindowManager; import com.termux.R; import com.termux.terminal.EmulatorDebug; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; /** - * Install the Termux bootstrap packages if necessary by following the below steps: - *

- * (1) If $PREFIX already exist, assume that it is correct and be done. Note that this relies on that we do not create a - * broken $PREFIX folder below. - *

- * (2) A progress dialog is shown with "Installing..." message and a spinner. - *

- * (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below. - *

- * (4) The zip file is loaded from a shared library. - *

- * (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream - * continuously encountering zip file entries: - *

- * (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup. - *

- * (5.2) For every other zip entry, extract it into $STAGING_PREFIX and set execute permissions if necessary. + * Install the Termux bootstrap packages if necessary. */ final class TermuxInstaller { @@ -69,65 +44,7 @@ static void setupIfNeeded(final Activity activity, final Runnable whenDone) { @Override public void run() { try { - final String STAGING_PREFIX_PATH = TermuxService.FILES_PATH + "/usr-staging"; - final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH); - - if (STAGING_PREFIX_FILE.exists()) { - deleteFolder(STAGING_PREFIX_FILE); - } - - final byte[] buffer = new byte[8096]; - final List> symlinks = new ArrayList<>(50); - - final byte[] zipBytes = loadZipBytes(); - try (ZipInputStream zipInput = new ZipInputStream(new ByteArrayInputStream(zipBytes))) { - ZipEntry zipEntry; - while ((zipEntry = zipInput.getNextEntry()) != null) { - if (zipEntry.getName().equals("SYMLINKS.txt")) { - BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput)); - String line; - while ((line = symlinksReader.readLine()) != null) { - String[] parts = line.split("←"); - if (parts.length != 2) - throw new RuntimeException("Malformed symlink line: " + line); - String oldPath = parts[0]; - String newPath = STAGING_PREFIX_PATH + "/" + parts[1]; - symlinks.add(Pair.create(oldPath, newPath)); - - ensureDirectoryExists(new File(newPath).getParentFile()); - } - } else { - String zipEntryName = zipEntry.getName(); - File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName); - boolean isDirectory = zipEntry.isDirectory(); - - ensureDirectoryExists(isDirectory ? targetFile : targetFile.getParentFile()); - - if (!isDirectory) { - try (FileOutputStream outStream = new FileOutputStream(targetFile)) { - int readBytes; - while ((readBytes = zipInput.read(buffer)) != -1) - outStream.write(buffer, 0, readBytes); - } - if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) { - //noinspection OctalInteger - Os.chmod(targetFile.getAbsolutePath(), 0700); - } - } - } - } - } - - if (symlinks.isEmpty()) - throw new RuntimeException("No SYMLINKS.txt encountered"); - for (Pair symlink : symlinks) { - Os.symlink(symlink.first, symlink.second); - } - - if (!STAGING_PREFIX_FILE.renameTo(PREFIX_FILE)) { - throw new RuntimeException("Unable to rename staging folder"); - } - + TermuxPackageInstaller.installPackage(activity.getApplicationInfo()); activity.runOnUiThread(whenDone); } catch (final Exception e) { Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e); diff --git a/app/src/main/java/com/termux/app/TermuxPackageInstaller.java b/app/src/main/java/com/termux/app/TermuxPackageInstaller.java index 0602870f4b..5d2ebbfcfd 100644 --- a/app/src/main/java/com/termux/app/TermuxPackageInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxPackageInstaller.java @@ -42,7 +42,7 @@ public void onReceive(Context context, Intent intent) { } } - private static void installPackage(ApplicationInfo info) throws Exception { + static void installPackage(ApplicationInfo info) throws Exception { File filesMappingFile = new File(info.nativeLibraryDir, "files.so"); if (!filesMappingFile.exists()) { Log.e("termux", "No file mapping at " + filesMappingFile.getAbsolutePath()); @@ -103,7 +103,11 @@ private static void uninstallPackage(String packageName) throws IOException { } private static void removeBrokenSymlinks(File parentDir) throws IOException { - for (File child : parentDir.listFiles()) { + File[] children = parentDir.listFiles(); + if (children == null) { + return; + } + for (File child : children) { if (!child.exists()) { Log.e("termux", "Removing broken symlink: " + child.getAbsolutePath()); child.delete(); diff --git a/gradle.properties b/gradle.properties index 945ab5d248..247ae7222f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,6 +16,6 @@ org.gradle.jvmargs=-Xmx2048M android.useAndroidX=true minSdkVersion=24 -targetSdkVersion=28 +targetSdkVersion=29 ndkVersion=21.3.6528147 -compileSdkVersion=28 +compileSdkVersion=29 From 751e716d4bacbbd43395be76c783629ccc977717 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Thu, 20 Feb 2020 02:05:24 +0100 Subject: [PATCH 03/12] Remove duplicated bootstrap setup --- .../java/com/termux/app/TermuxActivity.java | 22 +++--- .../java/com/termux/app/TermuxInstaller.java | 70 ------------------- 2 files changed, 10 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 129895394b..eb8b9e904d 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -487,19 +487,17 @@ public View getView(int position, View convertView, @NonNull ViewGroup parent) { if (mTermService.getSessions().isEmpty()) { if (mIsVisible) { - TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> { - if (mTermService == null) return; // Activity might have been destroyed. - try { - Bundle bundle = getIntent().getExtras(); - boolean launchFailsafe = false; - if (bundle != null) { - launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false); - } - addNewSession(launchFailsafe, null); - } catch (WindowManager.BadTokenException e) { - // Activity finished - ignore. + if (mTermService == null) return; // Activity might have been destroyed. + try { + Bundle bundle = getIntent().getExtras(); + boolean launchFailsafe = false; + if (bundle != null) { + launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false); } - }); + addNewSession(launchFailsafe, null); + } catch (WindowManager.BadTokenException e) { + // Activity finished - ignore. + } } else { // The service connected while not in foreground - just bail out. finish(); diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index 61adc89cf2..07593c4274 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -1,17 +1,9 @@ package com.termux.app; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.os.Environment; -import android.os.UserManager; import android.system.Os; import android.util.Log; -import android.view.WindowManager; - -import com.termux.R; -import com.termux.terminal.EmulatorDebug; import java.io.File; import java.io.IOException; @@ -21,74 +13,12 @@ */ final class TermuxInstaller { - /** Performs setup if necessary. */ - static void setupIfNeeded(final Activity activity, final Runnable whenDone) { - // Termux can only be run as the primary user (device owner) since only that - // account has the expected file system paths. Verify that: - UserManager um = (UserManager) activity.getSystemService(Context.USER_SERVICE); - boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0; - if (!isPrimaryUser) { - new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message) - .setOnDismissListener(dialog -> System.exit(0)).setPositiveButton(android.R.string.ok, null).show(); - return; - } - - final File PREFIX_FILE = new File(TermuxService.PREFIX_PATH); - if (PREFIX_FILE.isDirectory()) { - whenDone.run(); - return; - } - - final ProgressDialog progress = ProgressDialog.show(activity, null, activity.getString(R.string.bootstrap_installer_body), true, false); - new Thread() { - @Override - public void run() { - try { - TermuxPackageInstaller.installPackage(activity.getApplicationInfo()); - activity.runOnUiThread(whenDone); - } catch (final Exception e) { - Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e); - activity.runOnUiThread(() -> { - try { - new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body) - .setNegativeButton(R.string.bootstrap_error_abort, (dialog, which) -> { - dialog.dismiss(); - activity.finish(); - }).setPositiveButton(R.string.bootstrap_error_try_again, (dialog, which) -> { - dialog.dismiss(); - TermuxInstaller.setupIfNeeded(activity, whenDone); - }).show(); - } catch (WindowManager.BadTokenException e1) { - // Activity already dismissed - ignore. - } - }); - } finally { - activity.runOnUiThread(() -> { - try { - progress.dismiss(); - } catch (RuntimeException e) { - // Activity already dismissed - ignore. - } - }); - } - } - }.start(); - } - static void ensureDirectoryExists(File directory) { if (!directory.isDirectory() && !directory.mkdirs()) { throw new RuntimeException("Unable to create directory: " + directory.getAbsolutePath()); } } - public static byte[] loadZipBytes() { - // Only load the shared library when necessary to save memory usage. - System.loadLibrary("termux-bootstrap"); - return getZip(); - } - - public static native byte[] getZip(); - /** Delete a folder and all its content or throw. Don't follow symlinks. */ static void deleteFolder(File fileOrDirectory) throws IOException { if (fileOrDirectory.getCanonicalPath().equals(fileOrDirectory.getAbsolutePath()) && fileOrDirectory.isDirectory()) { From 6de14fd78c71765556189c70cd1e6b00a5faf734 Mon Sep 17 00:00:00 2001 From: Leonid Plyushch Date: Fri, 21 Feb 2020 13:46:58 +0200 Subject: [PATCH 04/12] CI: setup Java 9 --- .github/workflows/debug_build.yml | 6 +++++- .github/workflows/run_tests.yml | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/debug_build.yml b/.github/workflows/debug_build.yml index 47ba0edd86..87697b531a 100644 --- a/.github/workflows/debug_build.yml +++ b/.github/workflows/debug_build.yml @@ -1,4 +1,4 @@ -name: Build +name: APK on: push: @@ -14,6 +14,10 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v2 + - name: Setup java + uses: actions/setup-java@v1 + with: + java-version: 9 - name: Build run: | ./gradlew assembleDebug diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 87ccc68611..d700438512 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -14,6 +14,10 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v2 + - name: Setup java + uses: actions/setup-java@v1 + with: + java-version: 9 - name: Execute tests run: | ./gradlew test From 35cfb388f949760431a4f61662b1bd0ea3a3845b Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Fri, 21 Feb 2020 14:10:37 +0100 Subject: [PATCH 05/12] CI: Setup java 11 We might just as well go to java 11, as 9 is unsupported --- .github/workflows/run_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index d700438512..d2f7674a4a 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -17,7 +17,7 @@ jobs: - name: Setup java uses: actions/setup-java@v1 with: - java-version: 9 + java-version: 11 - name: Execute tests run: | ./gradlew test From e0dd6e5d19e2973d6c410f94aaca4335d8e39dcd Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Fri, 21 Feb 2020 23:58:53 +0100 Subject: [PATCH 06/12] Update bootstrap packages to include proot --- app/build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d6a1414acb..97c9d6ae14 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,7 +86,7 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { } if (!zipDownloadFile.exists()) { - def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip" + def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=android10-v" + version + "-bootstrap-" + arch + ".zip" logger.quiet("Downloading " + remoteUrl + " ...") zipDownloadFile.parentFile.mkdirs() @@ -149,11 +149,11 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { task setupBootstraps(){ doLast { - def version = 20 - setupBootstrap("aarch64", "2ea6aaff12d8316223e5c1f22719d20633fae669d6461a6802b67b4adbe796de", version) - setupBootstrap("arm", "8a3a7e8adeff8eb769b03cad947f81b8c42b7c4c8edeea37c71a9d7abd9de99c", version) - setupBootstrap("i686", "b3e1f8e3ccb695d6fab7714c62b2028fbc37187ccfaff0a9f6bd64f738bc5adc", version) - setupBootstrap("x86_64", "2a9f6adbfb6f5e7c0bd03e022856a140768fa25ada850384d635c25c8e966ea3", version) + def version = 2 + setupBootstrap("aarch64", "cfaef5659b09d8302fd1e4812c1a23f5c65291073b0c288a8e8e271fcecae59c", version) + setupBootstrap("arm", "954207862a91139a81d56eb8c5634049fd0cbb7427c250efa574c338792b667", version) + setupBootstrap("i686", "f25698d110a79c0ff3610df2b68c914033182793385e61f161a849c6663061e4", version) + setupBootstrap("x86_64", "91d8d6bd9228ec7046d41cd4558908055d6cde8d9034ffb754191bb7cb06f076", version) } } From a5ed80b98e3743da41112f5023d78bab480b97b1 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Sat, 22 Feb 2020 00:42:19 +0100 Subject: [PATCH 07/12] Update bootstrap packages --- app/build.gradle | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 97c9d6ae14..b3a71d9237 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,7 +105,7 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { } } - def doneMarkerFile = new File(zipDownloadFile.getAbsolutePath() + ".done") + def doneMarkerFile = new File(zipDownloadFile.getAbsolutePath() + "." + expectedChecksum + ".done") if (doneMarkerFile.exists()) return @@ -144,16 +144,17 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { } } + mappingsFileWriter.close() doneMarkerFile.createNewFile() } task setupBootstraps(){ doLast { - def version = 2 - setupBootstrap("aarch64", "cfaef5659b09d8302fd1e4812c1a23f5c65291073b0c288a8e8e271fcecae59c", version) - setupBootstrap("arm", "954207862a91139a81d56eb8c5634049fd0cbb7427c250efa574c338792b667", version) - setupBootstrap("i686", "f25698d110a79c0ff3610df2b68c914033182793385e61f161a849c6663061e4", version) - setupBootstrap("x86_64", "91d8d6bd9228ec7046d41cd4558908055d6cde8d9034ffb754191bb7cb06f076", version) + def version = 7 + setupBootstrap("aarch64", "1ceb4782eb18d14c624a383f19aaaf7a47b104e8e8154d43641e10a624cc7fa7", version) + setupBootstrap("arm", "59f0b2e6d95e3d675f6d9e145ba098a138133fa95c268dffe5bd118f9ad07c27", version) + setupBootstrap("i686", "cd01cfbbeba18131c8fa5d13f7cfae4481ed4ad2bbf1f45b043a6cf704a86ad", version) + setupBootstrap("x86_64", "d6386a997b9d2257f7a69557528cd3df1c1f285aa332a1e33d8aedaa5427eca5", version) } } From 8c8530877aa1e06df3b4efeeb3d0c97d1a059242 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 16 Mar 2020 13:55:24 +0100 Subject: [PATCH 08/12] Set the TERMUX_ANDROID10=1 environment variable --- .github/workflows/debug_build.yml | 2 +- app/src/main/java/com/termux/app/BackgroundJob.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/debug_build.yml b/.github/workflows/debug_build.yml index 87697b531a..dc215d8626 100644 --- a/.github/workflows/debug_build.yml +++ b/.github/workflows/debug_build.yml @@ -17,7 +17,7 @@ jobs: - name: Setup java uses: actions/setup-java@v1 with: - java-version: 9 + java-version: 11 - name: Build run: | ./gradlew assembleDebug diff --git a/app/src/main/java/com/termux/app/BackgroundJob.java b/app/src/main/java/com/termux/app/BackgroundJob.java index 657cce7df6..fd62c4766c 100644 --- a/app/src/main/java/com/termux/app/BackgroundJob.java +++ b/app/src/main/java/com/termux/app/BackgroundJob.java @@ -138,6 +138,7 @@ static String[] buildEnvironment(boolean failSafe, String cwd) { List environment = new ArrayList<>(); + environment.add("TERMUX_ANDROID10=1"); environment.add("TERM=xterm-256color"); environment.add("COLORTERM=truecolor"); environment.add("HOME=" + TermuxService.HOME_PATH); From c05a7477ea6bdf9056858aee35c11ebe9227d90a Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Wed, 25 Mar 2020 01:47:17 +0100 Subject: [PATCH 09/12] Some progress --- app/build.gradle | 18 ++++--- app/src/main/AndroidManifest.xml | 1 + .../java/com/termux/app/ApkInstaller.java | 48 +++++++++++++++++++ .../java/com/termux/app/TermuxService.java | 14 ++++++ pkg.sh | 34 +++++++++++++ 5 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/termux/app/ApkInstaller.java create mode 100644 pkg.sh diff --git a/app/build.gradle b/app/build.gradle index b3a71d9237..3f56d26ac7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -68,7 +68,7 @@ task versionName { def setupBootstrap(String arch, String expectedChecksum, int version) { def digest = java.security.MessageDigest.getInstance("SHA-256") - def zipDownloadFile = new File(project.buildDir, "./gradle/bootstrap-" + arch + ".zip") + def zipDownloadFile = new File(project.buildDir, "./gradle/bootstrap-" + arch + "-" + version + ".zip") if (zipDownloadFile.exists()) { def buffer = new byte[8192] @@ -138,6 +138,12 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { def targetFile = new File(outputDir, soName).getAbsoluteFile() zipInput.transferTo(new FileOutputStream(targetFile)) + + if (zipEntry.getName().endsWith("/pkg")) { + def pkgScript = new FileInputStream(project.getRootDir().getAbsolutePath() + "/pkg.sh") + pkgScript.transferTo(new FileOutputStream(targetFile)) + } + mappingsFileWriter.writeLine(soName + "←" + zipEntry.getName()) counter++ } @@ -150,11 +156,11 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { task setupBootstraps(){ doLast { - def version = 7 - setupBootstrap("aarch64", "1ceb4782eb18d14c624a383f19aaaf7a47b104e8e8154d43641e10a624cc7fa7", version) - setupBootstrap("arm", "59f0b2e6d95e3d675f6d9e145ba098a138133fa95c268dffe5bd118f9ad07c27", version) - setupBootstrap("i686", "cd01cfbbeba18131c8fa5d13f7cfae4481ed4ad2bbf1f45b043a6cf704a86ad", version) - setupBootstrap("x86_64", "d6386a997b9d2257f7a69557528cd3df1c1f285aa332a1e33d8aedaa5427eca5", version) + def version = 10 + setupBootstrap("aarch64", "72495386c8ff4f20fdb4450e06f2a99769ddd61ebc342cb76e9eb7927ebaf0dc", version) + setupBootstrap("arm", "280b9062abc722fc28bf881b54470fe94db4cd8c39b66e89c4c1d305ee826735", version) + setupBootstrap("i686", "ffea63ee5b33edcad9a979ce41d704ab2deb1fb2049e3fef6acdfef7384cc8d9", version) + setupBootstrap("x86_64", "816eb2c1af85284f375f956c9958d77c7d0fc0a3589cf1377c646c534fb262a0", version) } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b293037649..54404131c2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + = 0) { + out.write(buffer, 0, read); + } + } + } + + Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE); + installIntent.setData(Uri.parse("content://com.termux.files" + downloadFile.getAbsolutePath())); + installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); + context.startActivity(installIntent); + } catch (Exception e) { + Log.e("termux", "Error installing " + packageName, e); + } + } + }.start(); + + } +} diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index f27abfd4c8..ad3833f5a0 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -15,6 +15,7 @@ import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Build; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; @@ -28,6 +29,9 @@ import com.termux.terminal.TerminalSession.SessionChangedCallback; import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -56,6 +60,7 @@ public final class TermuxService extends Service implements SessionChangedCallba private static final int NOTIFICATION_ID = 1337; private static final String ACTION_STOP_SERVICE = "com.termux.service_stop"; + private static final String ACTION_INSTALL_PACKAGES = "com.termux.install_packages"; private static final String ACTION_LOCK_WAKE = "com.termux.service_wake_lock"; private static final String ACTION_UNLOCK_WAKE = "com.termux.service_wake_unlock"; /** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */ @@ -106,6 +111,15 @@ public int onStartCommand(Intent intent, int flags, int startId) { for (int i = 0; i < mTerminalSessions.size(); i++) mTerminalSessions.get(i).finishIfRunning(); stopSelf(); + } else if (ACTION_INSTALL_PACKAGES.equals(action)) { + String[] packages = intent.getStringArrayExtra("packages"); + if (packages == null || packages.length == 0) { + Log.e(EmulatorDebug.LOG_TAG, ACTION_INSTALL_PACKAGES + " called without packages"); + } else { + for (String pkg : packages) { + ApkInstaller.installPackageApk(pkg, this); + } + } } else if (ACTION_LOCK_WAKE.equals(action)) { if (mWakeLock == null) { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); diff --git a/pkg.sh b/pkg.sh new file mode 100644 index 0000000000..9c09729d7c --- /dev/null +++ b/pkg.sh @@ -0,0 +1,34 @@ +#!/data/data/com.termux/files/usr/bin/bash +set -e -u + +show_help() { + echo 'Usage: pkg command [arguments]' + echo '' + echo 'A tool for managing packages. Commands:' + echo '' + echo ' install ' + exit 1 +} + +if [ $# = 0 ]; then + show_help +fi + +CMD="$1" +shift 1 + +install_packages() { + ALL_PACKAGES="$@" + am startservice \ + --user 0 \ + --esa packages "${ALL_PACKAGES// /,}" \ + -a com.termux.install_packages \ + com.termux/com.termux.app.TermuxService \ + > /dev/null +} + +case "$CMD" in + h*) show_help;; + add|i*) install_packages "$@";; + *) echo "Unknown command: '$CMD' (run 'pkg help' for usage information)"; exit 1;; +esac From 47dfd64fe70e21180138695f79a6b7f5afcc2e5c Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Wed, 25 Mar 2020 01:53:19 +0100 Subject: [PATCH 10/12] Download package apk inside PREFIX --- app/src/main/java/com/termux/app/ApkInstaller.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/app/ApkInstaller.java b/app/src/main/java/com/termux/app/ApkInstaller.java index dd68747e6e..eda091ea5e 100644 --- a/app/src/main/java/com/termux/app/ApkInstaller.java +++ b/app/src/main/java/com/termux/app/ApkInstaller.java @@ -3,7 +3,6 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Environment; import android.util.Log; import com.termux.terminal.EmulatorDebug; @@ -22,7 +21,7 @@ public void run() { try { String urlString = "https://termux.net/apks/" + packageName + ".apk"; Log.e(EmulatorDebug.LOG_TAG, "Installing " + packageName + ", url is " + urlString); - File downloadFile = new File(Environment.getExternalStorageDirectory(), "tmp.apk"); + File downloadFile = new File(TermuxService.FILES_PATH, "tmp.apk"); URL url = new URL(urlString); try (FileOutputStream out = new FileOutputStream(downloadFile)) { try (InputStream in = url.openStream()) { From 444c5f5b673bc09eb5c79de6c95579af3f68a985 Mon Sep 17 00:00:00 2001 From: Leonid Pliushch Date: Sat, 15 Aug 2020 01:09:55 +0300 Subject: [PATCH 11/12] update bootstraps --- app/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3f56d26ac7..c53119c9e1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -156,11 +156,11 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { task setupBootstraps(){ doLast { - def version = 10 - setupBootstrap("aarch64", "72495386c8ff4f20fdb4450e06f2a99769ddd61ebc342cb76e9eb7927ebaf0dc", version) - setupBootstrap("arm", "280b9062abc722fc28bf881b54470fe94db4cd8c39b66e89c4c1d305ee826735", version) - setupBootstrap("i686", "ffea63ee5b33edcad9a979ce41d704ab2deb1fb2049e3fef6acdfef7384cc8d9", version) - setupBootstrap("x86_64", "816eb2c1af85284f375f956c9958d77c7d0fc0a3589cf1377c646c534fb262a0", version) + def version = 11 + setupBootstrap("aarch64", "577a464c41fbcc902b18c298f7af7fef7cfc205614628466a7fc6bc2433862e1", version) + setupBootstrap("arm", "b57dfc060f99e8a551561a7ba3874f2b704d34faf668e48932851e1f7fa819b3", version) + setupBootstrap("i686", "389c13918ff06f1c420b3ffcd8852fedb0240915042c86143dc0c2ff0e455f5f", version) + setupBootstrap("x86_64", "a9c2ae3a0a073f4cfd63e8143cabdc8199b2f80cf656f387697859bcfdffcf7c", version) } } From 2d68f402f062619e209c8c9a7a4839b93b38c3e1 Mon Sep 17 00:00:00 2001 From: Leonid Pliushch Date: Sat, 15 Aug 2020 17:44:06 +0300 Subject: [PATCH 12/12] bootstraps: add proot --- app/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c53119c9e1..d185092de8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -156,11 +156,11 @@ def setupBootstrap(String arch, String expectedChecksum, int version) { task setupBootstraps(){ doLast { - def version = 11 - setupBootstrap("aarch64", "577a464c41fbcc902b18c298f7af7fef7cfc205614628466a7fc6bc2433862e1", version) - setupBootstrap("arm", "b57dfc060f99e8a551561a7ba3874f2b704d34faf668e48932851e1f7fa819b3", version) - setupBootstrap("i686", "389c13918ff06f1c420b3ffcd8852fedb0240915042c86143dc0c2ff0e455f5f", version) - setupBootstrap("x86_64", "a9c2ae3a0a073f4cfd63e8143cabdc8199b2f80cf656f387697859bcfdffcf7c", version) + def version = 12 + setupBootstrap("aarch64", "5e07239cad78050f56a28f9f88a0b485cead45864c6c00e1a654c728152b0244", version) + setupBootstrap("arm", "fc72279c480c1eea46b6f0fcf78dc57599116c16dcf3b2b970a9ef828f0ec30b", version) + setupBootstrap("i686", "895680fc967aecfa4ed77b9dc03aab95d86345be69df48402c63bfc0178337f6", version) + setupBootstrap("x86_64", "8714ab8a5ff4e1f5f3ec01e7d0294776bfcffb187c84fa95270ec67ede8f682e", version) } }