Laszlo Csomor | fe14baf | 2017-08-07 15:47:18 +0200 | [diff] [blame] | 1 | # pylint: disable=g-direct-third-party-import |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 2 | # pylint: disable=g-bad-file-header |
Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 3 | # Copyright 2015 The Bazel Authors. All rights reserved. |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | """Installs an Android application, possibly in an incremental way.""" |
| 18 | |
| 19 | import collections |
| 20 | import hashlib |
| 21 | import logging |
| 22 | import os |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 23 | import posixpath |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 24 | import re |
| 25 | import shutil |
| 26 | import subprocess |
| 27 | import sys |
| 28 | import tempfile |
| 29 | import time |
| 30 | import zipfile |
| 31 | |
| 32 | from third_party.py import gflags |
| 33 | from third_party.py.concurrent import futures |
| 34 | |
| 35 | |
| 36 | gflags.DEFINE_string("split_main_apk", None, "The main APK for split install") |
| 37 | gflags.DEFINE_multistring("split_apk", [], "Split APKs to install") |
| 38 | gflags.DEFINE_string("dexmanifest", None, "The .dex manifest") |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 39 | gflags.DEFINE_multistring("native_lib", None, "Native libraries to install") |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 40 | gflags.DEFINE_string("resource_apk", None, "The resource .apk") |
| 41 | gflags.DEFINE_string("apk", None, "The app .apk. If not specified, " |
| 42 | "do incremental deployment") |
| 43 | gflags.DEFINE_string("adb", None, "ADB to use") |
| 44 | gflags.DEFINE_string("stub_datafile", None, "The stub data file") |
| 45 | gflags.DEFINE_string("output_marker", None, "The output marker file") |
| 46 | gflags.DEFINE_multistring("extra_adb_arg", [], "Extra arguments to adb") |
| 47 | gflags.DEFINE_string("execroot", ".", "The exec root") |
| 48 | gflags.DEFINE_integer("adb_jobs", 2, |
| 49 | "The number of instances of adb to use in parallel to " |
| 50 | "update files on the device", |
| 51 | lower_bound=1) |
Googler | 2b88f62 | 2017-03-16 21:52:14 +0000 | [diff] [blame] | 52 | gflags.DEFINE_enum("start", "no", ["no", "cold", "warm", "debug"], |
| 53 | "Whether/how to start the app after installing it. 'cold' " |
| 54 | "and 'warm' will both cause the app to be started, 'warm' " |
| 55 | "will start it with previously saved application state, " |
| 56 | "'debug' will wait for the debugger before a clean start.") |
Googler | 73d4fc9 | 2015-07-30 20:39:46 +0000 | [diff] [blame] | 57 | gflags.DEFINE_boolean("start_app", False, "Deprecated, use 'start'.") |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 58 | gflags.DEFINE_string("user_home_dir", None, "Path to the user's home directory") |
| 59 | gflags.DEFINE_string("flagfile", None, |
| 60 | "Path to a file to read additional flags from") |
| 61 | gflags.DEFINE_string("verbosity", None, "Logging verbosity") |
| 62 | |
| 63 | FLAGS = gflags.FLAGS |
| 64 | |
| 65 | DEVICE_DIRECTORY = "/data/local/tmp/incrementaldeployment" |
| 66 | |
Lukacs Berki | a3a33d7 | 2015-08-19 08:34:55 +0000 | [diff] [blame] | 67 | # Some devices support ABIs other than those reported by getprop. In this case, |
| 68 | # if the most specific ABI is not available in the .apk, we push the more |
| 69 | # general ones. |
| 70 | COMPATIBLE_ABIS = { |
| 71 | "armeabi-v7a": ["armeabi"], |
| 72 | "arm64-v8a": ["armeabi-v7a", "armeabi"] |
| 73 | } |
| 74 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 75 | |
| 76 | class AdbError(Exception): |
| 77 | """An exception class signaling an error in an adb invocation.""" |
| 78 | |
| 79 | def __init__(self, args, returncode, stdout, stderr): |
| 80 | self.args = args |
| 81 | self.returncode = returncode |
| 82 | self.stdout = stdout |
| 83 | self.stderr = stderr |
| 84 | details = "\n".join([ |
| 85 | "adb command: %s" % args, |
| 86 | "return code: %s" % returncode, |
| 87 | "stdout: %s" % stdout, |
| 88 | "stderr: %s" % stderr, |
| 89 | ]) |
| 90 | super(AdbError, self).__init__(details) |
| 91 | |
| 92 | |
| 93 | class DeviceNotFoundError(Exception): |
| 94 | """Raised when the device could not be found.""" |
| 95 | |
| 96 | |
| 97 | class MultipleDevicesError(Exception): |
| 98 | """Raised when > 1 device is attached and no device serial was given.""" |
| 99 | |
| 100 | @staticmethod |
| 101 | def CheckError(s): |
| 102 | return re.search("more than one (device and emulator|device|emulator)", s) |
| 103 | |
| 104 | |
| 105 | class DeviceUnauthorizedError(Exception): |
| 106 | """Raised when the local machine is not authorized to the device.""" |
| 107 | |
| 108 | |
| 109 | class TimestampException(Exception): |
| 110 | """Raised when there is a problem with timestamp reading/writing.""" |
| 111 | |
| 112 | |
Alex Humesky | f0a5ac6 | 2015-09-22 00:41:11 +0000 | [diff] [blame] | 113 | class OldSdkException(Exception): |
| 114 | """Raised when the SDK on the target device is older than the app allows.""" |
| 115 | |
| 116 | |
Laszlo Csomor | fe14baf | 2017-08-07 15:47:18 +0200 | [diff] [blame] | 117 | class EnvvarError(Exception): |
| 118 | """Raised when a required environment variable is not set.""" |
| 119 | |
| 120 | |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 121 | hostpath = os.path |
| 122 | targetpath = posixpath |
| 123 | |
| 124 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 125 | class Adb(object): |
| 126 | """A class to handle interaction with adb.""" |
| 127 | |
Akira Baruah | e8a83af | 2017-12-13 08:33:11 -0800 | [diff] [blame] | 128 | def __init__(self, adb_path, temp_dir, adb_jobs, user_home_dir, |
| 129 | extra_adb_args): |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 130 | self._adb_path = adb_path |
| 131 | self._temp_dir = temp_dir |
| 132 | self._user_home_dir = user_home_dir |
| 133 | self._file_counter = 1 |
| 134 | self._executor = futures.ThreadPoolExecutor(max_workers=adb_jobs) |
Akira Baruah | e8a83af | 2017-12-13 08:33:11 -0800 | [diff] [blame] | 135 | self._extra_adb_args = extra_adb_args or [] |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 136 | |
| 137 | def _Exec(self, adb_args): |
| 138 | """Executes the given adb command + args.""" |
Akira Baruah | e8a83af | 2017-12-13 08:33:11 -0800 | [diff] [blame] | 139 | args = [self._adb_path] + self._extra_adb_args + adb_args |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 140 | # TODO(ahumesky): Because multiple instances of adb are executed in |
| 141 | # parallel, these debug logging lines will get interleaved. |
| 142 | logging.debug("Executing: %s", " ".join(args)) |
| 143 | |
| 144 | # adb sometimes requires the user's home directory to access things in |
| 145 | # $HOME/.android (e.g. keys to authorize with the device). To avoid any |
| 146 | # potential problems with python picking up things in the user's home |
| 147 | # directory, HOME is not set in the environment around python and is instead |
| 148 | # passed explicitly as a flag. |
| 149 | env = {} |
| 150 | if self._user_home_dir: |
| 151 | env["HOME"] = self._user_home_dir |
| 152 | |
Laszlo Csomor | fe14baf | 2017-08-07 15:47:18 +0200 | [diff] [blame] | 153 | # On Windows, adb requires the SystemRoot environment variable. |
| 154 | if Adb._IsHostOsWindows(): |
| 155 | value = os.getenv("SYSTEMROOT") |
| 156 | if not value: |
| 157 | raise EnvvarError(("The %SYSTEMROOT% environment variable must " |
| 158 | "be set or Adb won't work")) |
| 159 | env["SYSTEMROOT"] = value |
| 160 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 161 | adb = subprocess.Popen( |
| 162 | args, |
| 163 | stdin=subprocess.PIPE, |
| 164 | stdout=subprocess.PIPE, |
| 165 | stderr=subprocess.PIPE, |
| 166 | env=env) |
| 167 | stdout, stderr = adb.communicate() |
| 168 | stdout = stdout.strip() |
| 169 | stderr = stderr.strip() |
| 170 | logging.debug("adb ret: %s", adb.returncode) |
| 171 | logging.debug("adb out: %s", stdout) |
| 172 | logging.debug("adb err: %s", stderr) |
| 173 | |
| 174 | # Check these first so that the more specific error gets raised instead of |
| 175 | # the more generic AdbError. |
Laszlo Csomor | 00ac93a | 2019-03-03 23:30:52 -0800 | [diff] [blame] | 176 | stdout = stdout.decode() |
| 177 | stderr = stderr.decode() |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 178 | if "device not found" in stderr: |
| 179 | raise DeviceNotFoundError() |
| 180 | elif "device unauthorized" in stderr: |
| 181 | raise DeviceUnauthorizedError() |
| 182 | elif MultipleDevicesError.CheckError(stderr): |
| 183 | # The error messages are from adb's transport.c, but something adds |
| 184 | # "error: " to the beginning, so take it off so that we don't end up |
| 185 | # printing "Error: error: ..." |
| 186 | raise MultipleDevicesError(re.sub("^error: ", "", stderr)) |
Alex Humesky | f0a5ac6 | 2015-09-22 00:41:11 +0000 | [diff] [blame] | 187 | elif "INSTALL_FAILED_OLDER_SDK" in stdout: |
| 188 | raise OldSdkException() |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 189 | |
| 190 | if adb.returncode != 0: |
| 191 | raise AdbError(args, adb.returncode, stdout, stderr) |
| 192 | |
| 193 | return adb.returncode, stdout, stderr, args |
| 194 | |
| 195 | def _ExecParallel(self, adb_args): |
| 196 | return self._executor.submit(self._Exec, adb_args) |
| 197 | |
| 198 | def _CreateLocalFile(self): |
| 199 | """Returns a path to a temporary local file in the temp directory.""" |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 200 | local = hostpath.join(self._temp_dir, "adbfile_%d" % self._file_counter) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 201 | self._file_counter += 1 |
| 202 | return local |
| 203 | |
| 204 | def GetInstallTime(self, package): |
| 205 | """Get the installation time of a package.""" |
| 206 | _, stdout, _, _ = self._Shell("dumpsys package %s" % package) |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 207 | match = re.search("firstInstallTime=(.*)$", stdout, re.MULTILINE) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 208 | if match: |
| 209 | return match.group(1) |
| 210 | else: |
Lukacs Berki | 0dffeac | 2015-09-15 07:18:47 +0000 | [diff] [blame] | 211 | return None |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 212 | |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 213 | def GetAbi(self): |
| 214 | """Returns the ABI the device supports.""" |
| 215 | _, stdout, _, _ = self._Shell("getprop ro.product.cpu.abi") |
| 216 | return stdout |
| 217 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 218 | def Push(self, local, remote): |
| 219 | """Invoke 'adb push' in parallel.""" |
| 220 | return self._ExecParallel(["push", local, remote]) |
| 221 | |
| 222 | def PushString(self, contents, remote): |
| 223 | """Push a given string to a given path on the device in parallel.""" |
| 224 | local = self._CreateLocalFile() |
Laszlo Csomor | d456fca | 2017-08-10 10:21:53 +0200 | [diff] [blame] | 225 | with open(local, "wb") as f: |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 226 | f.write(contents.encode("utf-8")) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 227 | return self.Push(local, remote) |
| 228 | |
| 229 | def Pull(self, remote): |
| 230 | """Invoke 'adb pull'. |
| 231 | |
| 232 | Args: |
| 233 | remote: The path to the remote file to pull. |
| 234 | |
| 235 | Returns: |
| 236 | The contents of a file or None if the file didn't exist. |
| 237 | """ |
| 238 | local = self._CreateLocalFile() |
| 239 | try: |
| 240 | self._Exec(["pull", remote, local]) |
Laszlo Csomor | d456fca | 2017-08-10 10:21:53 +0200 | [diff] [blame] | 241 | with open(local, "rb") as f: |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 242 | return f.read().decode("utf-8") |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 243 | except (AdbError, IOError): |
| 244 | return None |
| 245 | |
| 246 | def InstallMultiple(self, apk, pkg=None): |
| 247 | """Invoke 'adb install-multiple'.""" |
| 248 | |
| 249 | pkg_args = ["-p", pkg] if pkg else [] |
| 250 | ret, stdout, stderr, args = self._Exec( |
| 251 | ["install-multiple", "-r"] + pkg_args + [apk]) |
Lukacs Berki | 60f6cf6 | 2015-12-16 08:57:12 +0000 | [diff] [blame] | 252 | if "FAILED" in stdout or "FAILED" in stderr: |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 253 | raise AdbError(args, ret, stdout, stderr) |
| 254 | |
| 255 | def Install(self, apk): |
| 256 | """Invoke 'adb install'.""" |
| 257 | ret, stdout, stderr, args = self._Exec(["install", "-r", apk]) |
| 258 | |
| 259 | # adb install could fail with a message on stdout like this: |
| 260 | # |
| 261 | # pkg: /data/local/tmp/Gmail_dev_sharded_incremental.apk |
| 262 | # Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES] |
| 263 | # |
| 264 | # and yet it will still have a return code of 0. At least for the install |
| 265 | # command, it will print "Success" if it succeeded, so check for that in |
| 266 | # standard out instead of relying on the return code. |
Lukacs Berki | 60f6cf6 | 2015-12-16 08:57:12 +0000 | [diff] [blame] | 267 | if "FAILED" in stdout or "FAILED" in stderr: |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 268 | raise AdbError(args, ret, stdout, stderr) |
| 269 | |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 270 | def Uninstall(self, pkg): |
| 271 | """Invoke 'adb uninstall'.""" |
| 272 | self._Exec(["uninstall", pkg]) |
| 273 | # No error checking. If this fails, we assume that the app was not installed |
| 274 | # in the first place. |
| 275 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 276 | def Delete(self, remote): |
| 277 | """Delete the given file (or directory) on the device.""" |
| 278 | self.DeleteMultiple([remote]) |
| 279 | |
| 280 | def DeleteMultiple(self, remote_files): |
| 281 | """Delete the given files (or directories) on the device.""" |
| 282 | files_str = " ".join(remote_files) |
| 283 | if files_str: |
| 284 | self._Shell("rm -fr %s" % files_str) |
| 285 | |
| 286 | def Mkdir(self, d): |
| 287 | """Invokes mkdir with the specified directory on the device.""" |
| 288 | self._Shell("mkdir -p %s" % d) |
| 289 | |
| 290 | def StopApp(self, package): |
| 291 | """Force stops the app with the given package.""" |
| 292 | self._Shell("am force-stop %s" % package) |
| 293 | |
Googler | 73d4fc9 | 2015-07-30 20:39:46 +0000 | [diff] [blame] | 294 | def StopAppAndSaveState(self, package): |
| 295 | """Stops the app with the given package, saving state for the next run.""" |
| 296 | # 'am kill' will only kill processes in the background, so we must make sure |
| 297 | # our process is in the background first. We accomplish this by bringing up |
| 298 | # the app switcher. |
| 299 | self._Shell("input keyevent KEYCODE_APP_SWITCH") |
| 300 | self._Shell("am kill %s" % package) |
| 301 | |
Googler | 2b88f62 | 2017-03-16 21:52:14 +0000 | [diff] [blame] | 302 | def StartApp(self, package, start_type): |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 303 | """Starts the app with the given package.""" |
Googler | 2b88f62 | 2017-03-16 21:52:14 +0000 | [diff] [blame] | 304 | if start_type == "debug": |
| 305 | self._Shell("am set-debug-app -w --persistent %s" % package) |
| 306 | else: |
| 307 | self._Shell("am clear-debug-app %s" % package) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 308 | self._Shell("monkey -p %s -c android.intent.category.LAUNCHER 1" % package) |
| 309 | |
| 310 | def _Shell(self, cmd): |
| 311 | """Invoke 'adb shell'.""" |
| 312 | return self._Exec(["shell", cmd]) |
| 313 | |
Laszlo Csomor | fe14baf | 2017-08-07 15:47:18 +0200 | [diff] [blame] | 314 | @staticmethod |
| 315 | def _IsHostOsWindows(): |
| 316 | return os.name == "nt" |
| 317 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 318 | |
| 319 | ManifestEntry = collections.namedtuple( |
| 320 | "ManifestEntry", ["input_file", "zippath", "installpath", "sha256"]) |
| 321 | |
| 322 | |
| 323 | def ParseManifest(contents): |
| 324 | """Parses a dexmanifest file. |
| 325 | |
| 326 | Args: |
| 327 | contents: the contents of the manifest file to be parsed. |
| 328 | |
| 329 | Returns: |
| 330 | A dict of install path -> ManifestEntry. |
| 331 | """ |
| 332 | result = {} |
| 333 | |
| 334 | for l in contents.split("\n"): |
| 335 | entry = ManifestEntry(*(l.strip().split(" "))) |
| 336 | result[entry.installpath] = entry |
| 337 | |
| 338 | return result |
| 339 | |
| 340 | |
| 341 | def GetAppPackage(stub_datafile): |
| 342 | """Returns the app package specified in a stub data file.""" |
Laszlo Csomor | d456fca | 2017-08-10 10:21:53 +0200 | [diff] [blame] | 343 | with open(stub_datafile, "rb") as f: |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 344 | return f.readlines()[1].decode("utf-8").strip() |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 345 | |
| 346 | |
| 347 | def UploadDexes(adb, execroot, app_dir, temp_dir, dexmanifest, full_install): |
| 348 | """Uploads dexes to the device so that the state. |
| 349 | |
| 350 | Does the minimum amount of work necessary to make the state of the device |
| 351 | consistent with what was built. |
| 352 | |
| 353 | Args: |
| 354 | adb: the Adb instance representing the device to install to |
| 355 | execroot: the execroot |
| 356 | app_dir: the directory things should be installed under on the device |
| 357 | temp_dir: a local temporary directory |
| 358 | dexmanifest: contents of the dex manifest |
| 359 | full_install: whether to do a full install |
| 360 | |
| 361 | Returns: |
| 362 | None. |
| 363 | """ |
| 364 | |
| 365 | # Fetch the manifest on the device |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 366 | dex_dir = targetpath.join(app_dir, "dex") |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 367 | adb.Mkdir(dex_dir) |
| 368 | |
| 369 | old_manifest = None |
| 370 | |
| 371 | if not full_install: |
| 372 | logging.info("Fetching dex manifest from device...") |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 373 | old_manifest_contents = adb.Pull(targetpath.join(dex_dir, "manifest")) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 374 | if old_manifest_contents: |
| 375 | old_manifest = ParseManifest(old_manifest_contents) |
| 376 | else: |
| 377 | logging.info("Dex manifest not found on device") |
| 378 | |
| 379 | if old_manifest is None: |
| 380 | # If the manifest is not found, maybe a previous installation attempt |
| 381 | # was interrupted. Wipe the slate clean. Do this also in case we do a full |
| 382 | # installation. |
| 383 | old_manifest = {} |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 384 | adb.Delete(targetpath.join(dex_dir, "*")) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 385 | |
| 386 | new_manifest = ParseManifest(dexmanifest) |
| 387 | dexes_to_delete = set(old_manifest) - set(new_manifest) |
| 388 | |
| 389 | # Figure out which dexes to upload: those that are present in the new manifest |
| 390 | # but not in the old one and those whose checksum was changed |
| 391 | common_dexes = set(new_manifest).intersection(old_manifest) |
| 392 | dexes_to_upload = set(d for d in common_dexes |
| 393 | if new_manifest[d].sha256 != old_manifest[d].sha256) |
| 394 | dexes_to_upload.update(set(new_manifest) - set(old_manifest)) |
| 395 | |
| 396 | if not dexes_to_delete and not dexes_to_upload: |
| 397 | # If we have nothing to do, don't bother removing and rewriting the manifest |
| 398 | logging.info("Application dexes up-to-date") |
| 399 | return |
| 400 | |
| 401 | # Delete the manifest so that we know how to get back to a consistent state |
| 402 | # if we are interrupted. |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 403 | adb.Delete(targetpath.join(dex_dir, "manifest")) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 404 | |
| 405 | # Tuple of (local, remote) files to push to the device. |
| 406 | files_to_push = [] |
| 407 | |
| 408 | # Sort dexes to be uploaded by the zip file they are in so that we only need |
| 409 | # to open each zip only once. |
| 410 | dexzips_in_upload = set(new_manifest[d].input_file for d in dexes_to_upload |
| 411 | if new_manifest[d].zippath != "-") |
| 412 | for i, dexzip_name in enumerate(dexzips_in_upload): |
| 413 | zip_dexes = [ |
| 414 | d for d in dexes_to_upload if new_manifest[d].input_file == dexzip_name] |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 415 | dexzip_tempdir = hostpath.join(temp_dir, "dex", str(i)) |
| 416 | with zipfile.ZipFile(hostpath.join(execroot, dexzip_name)) as dexzip: |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 417 | for dex in zip_dexes: |
| 418 | zippath = new_manifest[dex].zippath |
| 419 | dexzip.extract(zippath, dexzip_tempdir) |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 420 | files_to_push.append((hostpath.join(dexzip_tempdir, zippath), |
| 421 | targetpath.join(dex_dir, dex))) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 422 | |
| 423 | # Now gather all the dexes that are not within a .zip file. |
| 424 | dexes_to_upload = set( |
| 425 | d for d in dexes_to_upload if new_manifest[d].zippath == "-") |
| 426 | for dex in dexes_to_upload: |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 427 | files_to_push.append((new_manifest[dex].input_file, targetpath.join( |
| 428 | dex_dir, dex))) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 429 | |
| 430 | num_files = len(dexes_to_delete) + len(files_to_push) |
| 431 | logging.info("Updating %d dex%s...", num_files, "es" if num_files > 1 else "") |
| 432 | |
| 433 | # Delete the dexes that are not in the new manifest |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 434 | adb.DeleteMultiple(targetpath.join(dex_dir, dex) for dex in dexes_to_delete) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 435 | |
| 436 | # Upload all the files. |
| 437 | upload_walltime_start = time.time() |
| 438 | fs = [adb.Push(local, remote) for local, remote in files_to_push] |
| 439 | done, not_done = futures.wait(fs, return_when=futures.FIRST_EXCEPTION) |
| 440 | upload_walltime = time.time() - upload_walltime_start |
| 441 | logging.debug("Dex upload walltime: %s seconds", upload_walltime) |
| 442 | |
| 443 | # If there is anything in not_done, then some adb call failed and we |
| 444 | # can cancel the rest. |
| 445 | if not_done: |
| 446 | for f in not_done: |
| 447 | f.cancel() |
| 448 | |
| 449 | # If any adb call resulted in an exception, re-raise it. |
| 450 | for f in done: |
| 451 | f.result() |
| 452 | |
| 453 | # If no dex upload failed, upload the manifest. If any upload failed, the |
| 454 | # exception should have been re-raised above. |
| 455 | # Call result() to raise the exception if there was one. |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 456 | adb.PushString(dexmanifest, targetpath.join(dex_dir, "manifest")).result() |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 457 | |
| 458 | |
| 459 | def Checksum(filename): |
| 460 | """Compute the SHA-256 checksum of a file.""" |
| 461 | h = hashlib.sha256() |
Laszlo Csomor | d456fca | 2017-08-10 10:21:53 +0200 | [diff] [blame] | 462 | with open(filename, "rb") as f: |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 463 | while True: |
| 464 | data = f.read(65536) |
| 465 | if not data: |
| 466 | break |
| 467 | |
| 468 | h.update(data) |
| 469 | |
| 470 | return h.hexdigest() |
| 471 | |
| 472 | |
| 473 | def UploadResources(adb, resource_apk, app_dir): |
| 474 | """Uploads resources to the device. |
| 475 | |
| 476 | Args: |
| 477 | adb: The Adb instance representing the device to install to. |
| 478 | resource_apk: Path to the resource apk. |
| 479 | app_dir: The directory things should be installed under on the device. |
| 480 | |
| 481 | Returns: |
| 482 | None. |
| 483 | """ |
| 484 | |
| 485 | # Compute the checksum of the new resources file |
| 486 | new_checksum = Checksum(resource_apk) |
| 487 | |
| 488 | # Fetch the checksum of the resources file on the device, if it exists |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 489 | device_checksum_file = targetpath.join(app_dir, "resources_checksum") |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 490 | old_checksum = adb.Pull(device_checksum_file) |
| 491 | if old_checksum == new_checksum: |
| 492 | logging.info("Application resources up-to-date") |
| 493 | return |
| 494 | logging.info("Updating application resources...") |
| 495 | |
| 496 | # Remove the checksum file on the device so that if the transfer is |
| 497 | # interrupted, we know how to get the device back to a consistent state. |
| 498 | adb.Delete(device_checksum_file) |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 499 | adb.Push(resource_apk, targetpath.join(app_dir, "resources.ap_")).result() |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 500 | |
| 501 | # Write the new checksum to the device. |
| 502 | adb.PushString(new_checksum, device_checksum_file).result() |
| 503 | |
| 504 | |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 505 | def ConvertNativeLibs(args): |
| 506 | """Converts the --native_libs command line argument to an arch -> libs map.""" |
| 507 | native_libs = {} |
| 508 | if args is not None: |
| 509 | for native_lib in args: |
| 510 | abi, path = native_lib.split(":") |
| 511 | if abi not in native_libs: |
| 512 | native_libs[abi] = set() |
| 513 | |
| 514 | native_libs[abi].add(path) |
| 515 | |
| 516 | return native_libs |
| 517 | |
| 518 | |
Lukacs Berki | a3a33d7 | 2015-08-19 08:34:55 +0000 | [diff] [blame] | 519 | def FindAbi(device_abi, app_abis): |
| 520 | """Selects which ABI native libs should be installed for.""" |
| 521 | if device_abi in app_abis: |
| 522 | return device_abi |
| 523 | |
| 524 | if device_abi in COMPATIBLE_ABIS: |
| 525 | for abi in COMPATIBLE_ABIS[device_abi]: |
| 526 | if abi in app_abis: |
| 527 | logging.warn("App does not have native libs for ABI '%s'. Using ABI " |
| 528 | "'%s'.", device_abi, abi) |
| 529 | return abi |
| 530 | |
| 531 | logging.warn("No native libs for device ABI '%s'. App has native libs for " |
| 532 | "ABIs: %s", device_abi, ", ".join(app_abis)) |
| 533 | return None |
| 534 | |
| 535 | |
Lukacs Berki | b4b19bc | 2015-07-30 11:45:35 +0000 | [diff] [blame] | 536 | def UploadNativeLibs(adb, native_lib_args, app_dir, full_install): |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 537 | """Uploads native libraries to the device.""" |
| 538 | |
| 539 | native_libs = ConvertNativeLibs(native_lib_args) |
| 540 | libs = set() |
| 541 | if native_libs: |
Lukacs Berki | a3a33d7 | 2015-08-19 08:34:55 +0000 | [diff] [blame] | 542 | abi = FindAbi(adb.GetAbi(), native_libs.keys()) |
| 543 | if abi: |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 544 | libs = native_libs[abi] |
| 545 | |
| 546 | basename_to_path = {} |
| 547 | install_checksums = {} |
| 548 | for lib in sorted(libs): |
| 549 | install_checksums[os.path.basename(lib)] = Checksum(lib) |
| 550 | basename_to_path[os.path.basename(lib)] = lib |
| 551 | |
Lukacs Berki | b4b19bc | 2015-07-30 11:45:35 +0000 | [diff] [blame] | 552 | device_manifest = None |
| 553 | if not full_install: |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 554 | device_manifest = adb.Pull( |
| 555 | targetpath.join(app_dir, "native", "native_manifest")) |
Lukacs Berki | b4b19bc | 2015-07-30 11:45:35 +0000 | [diff] [blame] | 556 | |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 557 | device_checksums = {} |
Lukacs Berki | b4b19bc | 2015-07-30 11:45:35 +0000 | [diff] [blame] | 558 | if device_manifest is None: |
| 559 | # If we couldn't fetch the device manifest or if this is a non-incremental |
| 560 | # install, wipe the slate clean |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 561 | adb.Delete(targetpath.join(app_dir, "native")) |
jingwen | b3f28d3 | 2018-12-10 19:30:57 -0800 | [diff] [blame] | 562 | |
| 563 | # From Android 28 onwards, `adb push` creates directories with insufficient |
| 564 | # permissions, resulting in errors when pushing files. `adb shell mkdir` |
| 565 | # works correctly however, so we create the directory here. |
| 566 | # See https://github.com/bazelbuild/examples/issues/77 for more information. |
| 567 | adb.Mkdir(targetpath.join(app_dir, "native")) |
Lukacs Berki | b4b19bc | 2015-07-30 11:45:35 +0000 | [diff] [blame] | 568 | else: |
| 569 | # Otherwise, parse the manifest. Note that this branch is also taken if the |
| 570 | # manifest is empty. |
Lukacs Berki | ff6ef9b | 2015-08-14 08:17:41 +0000 | [diff] [blame] | 571 | for manifest_line in device_manifest.split("\n"): |
| 572 | if manifest_line: |
| 573 | name, checksum = manifest_line.split(" ") |
| 574 | device_checksums[name] = checksum |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 575 | |
| 576 | libs_to_delete = set(device_checksums) - set(install_checksums) |
| 577 | libs_to_upload = set(install_checksums) - set(device_checksums) |
| 578 | common_libs = set(install_checksums).intersection(set(device_checksums)) |
| 579 | libs_to_upload.update([l for l in common_libs |
| 580 | if install_checksums[l] != device_checksums[l]]) |
| 581 | |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 582 | libs_to_push = [(basename_to_path[lib], targetpath.join( |
| 583 | app_dir, "native", lib)) for lib in libs_to_upload] |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 584 | |
| 585 | if not libs_to_delete and not libs_to_push and device_manifest is not None: |
| 586 | logging.info("Native libs up-to-date") |
| 587 | return |
| 588 | |
| 589 | num_files = len(libs_to_delete) + len(libs_to_push) |
| 590 | logging.info("Updating %d native lib%s...", |
| 591 | num_files, "s" if num_files != 1 else "") |
| 592 | |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 593 | adb.Delete(targetpath.join(app_dir, "native", "native_manifest")) |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 594 | |
| 595 | if libs_to_delete: |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 596 | adb.DeleteMultiple( |
| 597 | [targetpath.join(app_dir, "native", lib) for lib in libs_to_delete]) |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 598 | |
| 599 | upload_walltime_start = time.time() |
| 600 | fs = [adb.Push(local, remote) for local, remote in libs_to_push] |
| 601 | done, not_done = futures.wait(fs, return_when=futures.FIRST_EXCEPTION) |
| 602 | upload_walltime = time.time() - upload_walltime_start |
| 603 | logging.debug("Native library upload walltime: %s seconds", upload_walltime) |
| 604 | |
| 605 | # If there is anything in not_done, then some adb call failed and we |
| 606 | # can cancel the rest. |
| 607 | if not_done: |
| 608 | for f in not_done: |
| 609 | f.cancel() |
| 610 | |
| 611 | # If any adb call resulted in an exception, re-raise it. |
| 612 | for f in done: |
| 613 | f.result() |
| 614 | |
| 615 | install_manifest = [ |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 616 | name + " " + checksum for name, checksum in install_checksums.items()] |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 617 | adb.PushString("\n".join(install_manifest), |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 618 | targetpath.join(app_dir, "native", |
| 619 | "native_manifest")).result() |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 620 | |
| 621 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 622 | def VerifyInstallTimestamp(adb, app_package): |
| 623 | """Verifies that the app is unchanged since the last mobile-install.""" |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 624 | expected_timestamp = adb.Pull( |
| 625 | targetpath.join(DEVICE_DIRECTORY, app_package, "install_timestamp")) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 626 | if not expected_timestamp: |
| 627 | raise TimestampException( |
| 628 | "Cannot verify last mobile install. At least one non-incremental " |
| 629 | "'mobile-install' must precede incremental installs") |
| 630 | |
| 631 | actual_timestamp = adb.GetInstallTime(app_package) |
Lukacs Berki | 0dffeac | 2015-09-15 07:18:47 +0000 | [diff] [blame] | 632 | if actual_timestamp is None: |
| 633 | raise TimestampException( |
| 634 | "Package '%s' is not installed on the device. At least one " |
| 635 | "non-incremental 'mobile-install' must precede incremental " |
| 636 | "installs." % app_package) |
| 637 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 638 | if actual_timestamp != expected_timestamp: |
| 639 | raise TimestampException("Installed app '%s' has an unexpected timestamp. " |
| 640 | "Did you last install the app in a way other than " |
| 641 | "'mobile-install'?" % app_package) |
| 642 | |
| 643 | |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 644 | def SplitIncrementalInstall(adb, app_package, execroot, split_main_apk, |
| 645 | split_apks): |
| 646 | """Does incremental installation using split packages.""" |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 647 | app_dir = targetpath.join(DEVICE_DIRECTORY, app_package) |
| 648 | device_manifest_path = targetpath.join(app_dir, "split_manifest") |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 649 | device_manifest = adb.Pull(device_manifest_path) |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 650 | expected_timestamp = adb.Pull(targetpath.join(app_dir, "install_timestamp")) |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 651 | actual_timestamp = adb.GetInstallTime(app_package) |
| 652 | device_checksums = {} |
| 653 | if device_manifest is not None: |
| 654 | for manifest_line in device_manifest.split("\n"): |
| 655 | if manifest_line: |
| 656 | name, checksum = manifest_line.split(" ") |
| 657 | device_checksums[name] = checksum |
| 658 | |
| 659 | install_checksums = {} |
| 660 | install_checksums["__MAIN__"] = Checksum( |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 661 | hostpath.join(execroot, split_main_apk)) |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 662 | for apk in split_apks: |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 663 | install_checksums[apk] = Checksum(hostpath.join(execroot, apk)) |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 664 | |
| 665 | reinstall_main = False |
| 666 | if (device_manifest is None or actual_timestamp is None or |
| 667 | actual_timestamp != expected_timestamp or |
| 668 | install_checksums["__MAIN__"] != device_checksums["__MAIN__"] or |
| 669 | set(device_checksums.keys()) != set(install_checksums.keys())): |
| 670 | # The main app is not up to date or not present or something happened |
| 671 | # with the on-device manifest. Start from scratch. Notably, we cannot |
| 672 | # uninstall a split package, so if the set of packages changes, we also |
| 673 | # need to do a full reinstall. |
| 674 | reinstall_main = True |
| 675 | device_checksums = {} |
| 676 | |
| 677 | apks_to_update = [ |
| 678 | apk for apk in split_apks if |
| 679 | apk not in device_checksums or |
| 680 | device_checksums[apk] != install_checksums[apk]] |
| 681 | |
| 682 | if not apks_to_update and not reinstall_main: |
| 683 | # Nothing to do |
| 684 | return |
| 685 | |
| 686 | # Delete the device manifest so that if something goes wrong, we do a full |
| 687 | # reinstall next time |
| 688 | adb.Delete(device_manifest_path) |
| 689 | |
| 690 | if reinstall_main: |
| 691 | logging.info("Installing main APK...") |
| 692 | adb.Uninstall(app_package) |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 693 | adb.InstallMultiple(targetpath.join(execroot, split_main_apk)) |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 694 | adb.PushString( |
| 695 | adb.GetInstallTime(app_package), |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 696 | targetpath.join(app_dir, "install_timestamp")).result() |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 697 | |
| 698 | logging.info("Reinstalling %s APKs...", len(apks_to_update)) |
| 699 | |
| 700 | for apk in apks_to_update: |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 701 | adb.InstallMultiple(targetpath.join(execroot, apk), app_package) |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 702 | |
| 703 | install_manifest = [ |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 704 | name + " " + checksum for name, checksum in install_checksums.items()] |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 705 | adb.PushString("\n".join(install_manifest), |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 706 | targetpath.join(app_dir, "split_manifest")).result() |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 707 | |
| 708 | |
Akira Baruah | e8a83af | 2017-12-13 08:33:11 -0800 | [diff] [blame] | 709 | def IncrementalInstall(adb_path, |
| 710 | execroot, |
| 711 | stub_datafile, |
| 712 | output_marker, |
| 713 | adb_jobs, |
| 714 | start_type, |
| 715 | dexmanifest=None, |
| 716 | apk=None, |
| 717 | native_libs=None, |
| 718 | resource_apk=None, |
| 719 | split_main_apk=None, |
| 720 | split_apks=None, |
| 721 | user_home_dir=None, |
| 722 | extra_adb_args=None): |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 723 | """Performs an incremental install. |
| 724 | |
| 725 | Args: |
| 726 | adb_path: Path to the adb executable. |
| 727 | execroot: Exec root. |
| 728 | stub_datafile: The stub datafile containing the app's package name. |
| 729 | output_marker: Path to the output marker file. |
| 730 | adb_jobs: The number of instances of adb to use in parallel. |
Googler | 73d4fc9 | 2015-07-30 20:39:46 +0000 | [diff] [blame] | 731 | start_type: A string describing whether/how to start the app after |
| 732 | installing it. Can be 'no', 'cold', or 'warm'. |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 733 | dexmanifest: Path to the .dex manifest file. |
| 734 | apk: Path to the .apk file. May be None to perform an incremental install. |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 735 | native_libs: Native libraries to install. |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 736 | resource_apk: Path to the apk containing the app's resources. |
| 737 | split_main_apk: the split main .apk if split installation is desired. |
| 738 | split_apks: the list of split .apks to be installed. |
| 739 | user_home_dir: Path to the user's home directory. |
Akira Baruah | e8a83af | 2017-12-13 08:33:11 -0800 | [diff] [blame] | 740 | extra_adb_args: Extra arguments that will always be passed to adb. |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 741 | """ |
| 742 | temp_dir = tempfile.mkdtemp() |
| 743 | try: |
Akira Baruah | e8a83af | 2017-12-13 08:33:11 -0800 | [diff] [blame] | 744 | adb = Adb(adb_path, temp_dir, adb_jobs, user_home_dir, extra_adb_args) |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 745 | app_package = GetAppPackage(hostpath.join(execroot, stub_datafile)) |
| 746 | app_dir = targetpath.join(DEVICE_DIRECTORY, app_package) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 747 | if split_main_apk: |
Lukacs Berki | faff718 | 2015-08-20 14:27:42 +0000 | [diff] [blame] | 748 | SplitIncrementalInstall(adb, app_package, execroot, split_main_apk, |
| 749 | split_apks) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 750 | else: |
| 751 | if not apk: |
| 752 | VerifyInstallTimestamp(adb, app_package) |
| 753 | |
Laszlo Csomor | d456fca | 2017-08-10 10:21:53 +0200 | [diff] [blame] | 754 | with open(hostpath.join(execroot, dexmanifest), "rb") as f: |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 755 | dexmanifest = f.read().decode("utf-8") |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 756 | UploadDexes(adb, execroot, app_dir, temp_dir, dexmanifest, bool(apk)) |
| 757 | # TODO(ahumesky): UploadDexes waits for all the dexes to be uploaded, and |
| 758 | # then UploadResources is called. We could instead enqueue everything |
| 759 | # onto the threadpool so that uploading resources happens sooner. |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 760 | UploadResources(adb, hostpath.join(execroot, resource_apk), app_dir) |
Lukacs Berki | b4b19bc | 2015-07-30 11:45:35 +0000 | [diff] [blame] | 761 | UploadNativeLibs(adb, native_libs, app_dir, bool(apk)) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 762 | if apk: |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 763 | apk_path = targetpath.join(execroot, apk) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 764 | adb.Install(apk_path) |
| 765 | future = adb.PushString( |
| 766 | adb.GetInstallTime(app_package), |
Laszlo Csomor | d4e673e | 2017-06-29 18:02:52 +0200 | [diff] [blame] | 767 | targetpath.join(DEVICE_DIRECTORY, app_package, "install_timestamp")) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 768 | future.result() |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 769 | else: |
Googler | 73d4fc9 | 2015-07-30 20:39:46 +0000 | [diff] [blame] | 770 | if start_type == "warm": |
| 771 | adb.StopAppAndSaveState(app_package) |
| 772 | else: |
| 773 | adb.StopApp(app_package) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 774 | |
Googler | 2b88f62 | 2017-03-16 21:52:14 +0000 | [diff] [blame] | 775 | if start_type in ["cold", "warm", "debug"]: |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 776 | logging.info("Starting application %s", app_package) |
Googler | 2b88f62 | 2017-03-16 21:52:14 +0000 | [diff] [blame] | 777 | adb.StartApp(app_package, start_type) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 778 | |
Laszlo Csomor | d456fca | 2017-08-10 10:21:53 +0200 | [diff] [blame] | 779 | with open(output_marker, "wb") as _: |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 780 | pass |
| 781 | except DeviceNotFoundError: |
| 782 | sys.exit("Error: Device not found") |
| 783 | except DeviceUnauthorizedError: |
| 784 | sys.exit("Error: Device unauthorized. Please check the confirmation " |
| 785 | "dialog on your device.") |
| 786 | except MultipleDevicesError as e: |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 787 | sys.exit("Error: " + str(e) + "\nTry specifying a device serial with " |
Dan Fabulich | 1d35ca0 | 2018-07-05 16:08:06 -0700 | [diff] [blame] | 788 | "\"bazel mobile-install --adb_arg=-s --adb_arg=$ANDROID_SERIAL\"") |
Alex Humesky | f0a5ac6 | 2015-09-22 00:41:11 +0000 | [diff] [blame] | 789 | except OldSdkException as e: |
| 790 | sys.exit("Error: The device does not support the API level specified in " |
| 791 | "the application's manifest. Check minSdkVersion in " |
| 792 | "AndroidManifest.xml") |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 793 | except TimestampException as e: |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 794 | sys.exit("Error:\n%s" % str(e)) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 795 | except AdbError as e: |
Googler | 246ee56 | 2017-12-19 11:04:21 -0800 | [diff] [blame] | 796 | sys.exit("Error:\n%s" % str(e)) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 797 | finally: |
| 798 | shutil.rmtree(temp_dir, True) |
| 799 | |
| 800 | |
| 801 | def main(): |
| 802 | if FLAGS.verbosity == "1": |
| 803 | level = logging.DEBUG |
| 804 | fmt = "%(levelname)-5s %(asctime)s %(module)s:%(lineno)3d] %(message)s" |
| 805 | else: |
| 806 | level = logging.INFO |
| 807 | fmt = "%(message)s" |
| 808 | logging.basicConfig(stream=sys.stdout, level=level, format=fmt) |
| 809 | |
Googler | 73d4fc9 | 2015-07-30 20:39:46 +0000 | [diff] [blame] | 810 | start_type = FLAGS.start |
| 811 | if FLAGS.start_app and start_type == "no": |
| 812 | start_type = "cold" |
| 813 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 814 | IncrementalInstall( |
| 815 | adb_path=FLAGS.adb, |
| 816 | adb_jobs=FLAGS.adb_jobs, |
| 817 | execroot=FLAGS.execroot, |
| 818 | stub_datafile=FLAGS.stub_datafile, |
| 819 | output_marker=FLAGS.output_marker, |
Googler | 73d4fc9 | 2015-07-30 20:39:46 +0000 | [diff] [blame] | 820 | start_type=start_type, |
Lukacs Berki | ecbf242 | 2015-07-28 07:57:45 +0000 | [diff] [blame] | 821 | native_libs=FLAGS.native_lib, |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 822 | split_main_apk=FLAGS.split_main_apk, |
| 823 | split_apks=FLAGS.split_apk, |
| 824 | dexmanifest=FLAGS.dexmanifest, |
| 825 | apk=FLAGS.apk, |
| 826 | resource_apk=FLAGS.resource_apk, |
Akira Baruah | e8a83af | 2017-12-13 08:33:11 -0800 | [diff] [blame] | 827 | user_home_dir=FLAGS.user_home_dir, |
| 828 | extra_adb_args=FLAGS.extra_adb_arg) |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 829 | |
| 830 | |
| 831 | if __name__ == "__main__": |
| 832 | FLAGS(sys.argv) |
| 833 | # process any additional flags in --flagfile |
| 834 | if FLAGS.flagfile: |
Laszlo Csomor | d456fca | 2017-08-10 10:21:53 +0200 | [diff] [blame] | 835 | with open(FLAGS.flagfile, "rb") as flagsfile: |
Lukacs Berki | 682ad92 | 2015-06-11 08:01:07 +0000 | [diff] [blame] | 836 | FLAGS.Reset() |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 837 | FLAGS(sys.argv + [line.strip() for line in flagsfile.readlines()]) |
Lukacs Berki | 682ad92 | 2015-06-11 08:01:07 +0000 | [diff] [blame] | 838 | |
Alex Humesky | a4ecde6 | 2015-05-21 17:08:42 +0000 | [diff] [blame] | 839 | main() |