Make the Android incremental installer script support incrementally installing native libraries.
--
MOS_MIGRATED_REVID=99257598
diff --git a/tools/android/incremental_install.py b/tools/android/incremental_install.py
index ad2f904..9862f7a 100644
--- a/tools/android/incremental_install.py
+++ b/tools/android/incremental_install.py
@@ -33,6 +33,7 @@
gflags.DEFINE_string("split_main_apk", None, "The main APK for split install")
gflags.DEFINE_multistring("split_apk", [], "Split APKs to install")
gflags.DEFINE_string("dexmanifest", None, "The .dex manifest")
+gflags.DEFINE_multistring("native_lib", None, "Native libraries to install")
gflags.DEFINE_string("resource_apk", None, "The resource .apk")
gflags.DEFINE_string("apk", None, "The app .apk. If not specified, "
"do incremental deployment")
@@ -171,6 +172,11 @@
"non-incremental 'mobile-install' must precede incremental "
"installs." % package)
+ def GetAbi(self):
+ """Returns the ABI the device supports."""
+ _, stdout, _, _ = self._Shell("getprop ro.product.cpu.abi")
+ return stdout
+
def Push(self, local, remote):
"""Invoke 'adb push' in parallel."""
return self._ExecParallel(["push", local, remote])
@@ -436,6 +442,90 @@
adb.PushString(new_checksum, device_checksum_file).result()
+def ConvertNativeLibs(args):
+ """Converts the --native_libs command line argument to an arch -> libs map."""
+ native_libs = {}
+ if args is not None:
+ for native_lib in args:
+ abi, path = native_lib.split(":")
+ if abi not in native_libs:
+ native_libs[abi] = set()
+
+ native_libs[abi].add(path)
+
+ return native_libs
+
+
+def UploadNativeLibs(adb, native_lib_args, app_dir):
+ """Uploads native libraries to the device."""
+
+ native_libs = ConvertNativeLibs(native_lib_args)
+ libs = set()
+ if native_libs:
+ abi = adb.GetAbi()
+ if abi not in native_libs:
+ logging.warn("No native libs for device ABI '%s'. Available ABIs: %s",
+ abi, ", ".join(native_libs))
+ else:
+ libs = native_libs[abi]
+
+ basename_to_path = {}
+ install_checksums = {}
+ for lib in sorted(libs):
+ install_checksums[os.path.basename(lib)] = Checksum(lib)
+ basename_to_path[os.path.basename(lib)] = lib
+
+ device_manifest = adb.Pull("%s/native/native_manifest" % app_dir)
+ device_checksums = {}
+ if device_manifest:
+ for name, checksum in [l.split(" ") for l in device_manifest.split("\n")]:
+ device_checksums[name] = checksum
+
+ libs_to_delete = set(device_checksums) - set(install_checksums)
+ libs_to_upload = set(install_checksums) - set(device_checksums)
+ common_libs = set(install_checksums).intersection(set(device_checksums))
+ libs_to_upload.update([l for l in common_libs
+ if install_checksums[l] != device_checksums[l]])
+
+ libs_to_push = [(basename_to_path[lib], "%s/native/%s" % (app_dir, lib))
+ for lib in libs_to_upload]
+
+ if not libs_to_delete and not libs_to_push and device_manifest is not None:
+ logging.info("Native libs up-to-date")
+ return
+
+ num_files = len(libs_to_delete) + len(libs_to_push)
+ logging.info("Updating %d native lib%s...",
+ num_files, "s" if num_files != 1 else "")
+
+ adb.Delete("%s/native/native_manifest" % app_dir)
+
+ if libs_to_delete:
+ adb.DeleteMultiple([
+ "%s/native/%s" % (app_dir, lib) for lib in libs_to_delete])
+
+ upload_walltime_start = time.time()
+ fs = [adb.Push(local, remote) for local, remote in libs_to_push]
+ done, not_done = futures.wait(fs, return_when=futures.FIRST_EXCEPTION)
+ upload_walltime = time.time() - upload_walltime_start
+ logging.debug("Native library upload walltime: %s seconds", upload_walltime)
+
+ # If there is anything in not_done, then some adb call failed and we
+ # can cancel the rest.
+ if not_done:
+ for f in not_done:
+ f.cancel()
+
+ # If any adb call resulted in an exception, re-raise it.
+ for f in done:
+ f.result()
+
+ install_manifest = [
+ name + " " + checksum for name, checksum in install_checksums.iteritems()]
+ adb.PushString("\n".join(install_manifest),
+ "%s/native/native_manifest" % app_dir).result()
+
+
def VerifyInstallTimestamp(adb, app_package):
"""Verifies that the app is unchanged since the last mobile-install."""
expected_timestamp = adb.Pull("%s/%s/install_timestamp" % (
@@ -454,7 +544,8 @@
def IncrementalInstall(adb_path, execroot, stub_datafile, output_marker,
adb_jobs, start_app, dexmanifest=None, apk=None,
- resource_apk=None, split_main_apk=None, split_apks=None,
+ native_libs=None, resource_apk=None,
+ split_main_apk=None, split_apks=None,
user_home_dir=None):
"""Performs an incremental install.
@@ -467,6 +558,7 @@
start_app: If True, starts the app after updating.
dexmanifest: Path to the .dex manifest file.
apk: Path to the .apk file. May be None to perform an incremental install.
+ native_libs: Native libraries to install.
resource_apk: Path to the apk containing the app's resources.
split_main_apk: the split main .apk if split installation is desired.
split_apks: the list of split .apks to be installed.
@@ -480,6 +572,8 @@
if split_main_apk:
adb.InstallMultiple(os.path.join(execroot, split_main_apk))
for split_apk in split_apks:
+ # TODO(build-team): This always reinstalls everything, which defeats the
+ # purpose of this whole system.
adb.InstallMultiple(os.path.join(execroot, split_apk), app_package)
else:
if not apk:
@@ -492,6 +586,7 @@
# then UploadResources is called. We could instead enqueue everything
# onto the threadpool so that uploading resources happens sooner.
UploadResources(adb, os.path.join(execroot, resource_apk), app_dir)
+ UploadNativeLibs(adb, native_libs, app_dir)
if apk:
apk_path = os.path.join(execroot, apk)
adb.Install(apk_path)
@@ -542,6 +637,7 @@
stub_datafile=FLAGS.stub_datafile,
output_marker=FLAGS.output_marker,
start_app=FLAGS.start_app,
+ native_libs=FLAGS.native_lib,
split_main_apk=FLAGS.split_main_apk,
split_apks=FLAGS.split_apk,
dexmanifest=FLAGS.dexmanifest,
diff --git a/tools/android/incremental_install_test.py b/tools/android/incremental_install_test.py
index b8ea64f..a040f74 100644
--- a/tools/android/incremental_install_test.py
+++ b/tools/android/incremental_install_test.py
@@ -78,6 +78,8 @@
elif shell_cmdln.startswith("rm"):
file_path = shell_cmdln.split()[2]
self.files.pop(file_path, None)
+ elif shell_cmdln.startswith("getprop ro.product.cpu.abi"):
+ return self._CreatePopenMock(0, "armeabi-v7a", "")
else:
raise Exception("Unknown shell command line: %s" % shell_cmdln)
# Return a mock subprocess.Popen object
@@ -151,11 +153,13 @@
def _PutDeviceFile(self, f, content):
self._mock_adb.files[self._GetDeviceAppPath(f)] = content
- def _CallIncrementalInstall(self, incremental, start_app=False):
+ def _CallIncrementalInstall(self, incremental, native_libs=None,
+ start_app=False):
if incremental:
apk = None
else:
apk = self._APK
+
incremental_install.IncrementalInstall(
adb_path=self._ADB_PATH,
execroot=self._EXEC_ROOT,
@@ -163,13 +167,13 @@
dexmanifest=self._DEXMANIFEST,
apk=apk,
resource_apk=self._RESOURCE_APK,
+ native_libs=native_libs,
output_marker=self._OUTPUT_MARKER,
adb_jobs=1,
start_app=start_app,
user_home_dir="/home/root")
def testUploadToPristineDevice(self):
-
self._CreateZip()
with open("dex1", "w") as f:
@@ -190,8 +194,35 @@
self.assertEquals("content3", self._GetDeviceFile("dex/ip3"))
self.assertEquals("resource apk", self._GetDeviceFile("resources.ap_"))
- def testUploadWithOneChangedFile(self):
+ def testUploadNativeLibs(self):
+ self._CreateZip()
+ with open("liba.so", "w") as f:
+ f.write("liba_1")
+ with open("libb.so", "w") as f:
+ f.write("libb_1")
+ native_libs = ["armeabi-v7a:liba.so", "armeabi-v7a:libb.so"]
+ self._CallIncrementalInstall(incremental=False, native_libs=native_libs)
+ self.assertEquals("liba_1", self._GetDeviceFile("native/liba.so"))
+ self.assertEquals("libb_1", self._GetDeviceFile("native/libb.so"))
+
+ # Change a library
+ with open("libb.so", "w") as f:
+ f.write("libb_2")
+ self._CallIncrementalInstall(incremental=True, native_libs=native_libs)
+ self.assertEquals("libb_2", self._GetDeviceFile("native/libb.so"))
+
+ # Delete a library
+ self._CallIncrementalInstall(
+ incremental=True, native_libs=["armeabi-v7a:liba.so"])
+ self.assertFalse(
+ self._GetDeviceAppPath("native/libb.so") in self._mock_adb.files)
+
+ # Add the deleted library back
+ self._CallIncrementalInstall(incremental=True, native_libs=native_libs)
+ self.assertEquals("libb_2", self._GetDeviceFile("native/libb.so"))
+
+ def testUploadWithOneChangedFile(self):
# Existing manifest from a previous install.
self._CreateRemoteManifest(
"zip1 zp1 ip1 0",