Skip to content

Commit

Permalink
[Android] Add an action to check/record attached devices
Browse files Browse the repository at this point in the history
When doing a gyp_managed_install, we install APKs to the attached
device. Currently this can fail in many ways (no device attached,
multiple devices attached, device offline, device doesn't have root,
etc.). In addition, we need to detect changes to the attached device
(particularly when the device is switched, when an APK is
 uninstalled/updated).

The current approach is to check all this information in the action
interacting with the device. This means that when there is some
problem we print the same warning messages for every APK that is built,
and, in some cases, multiple times for each APK. Also, we have to run
every install/push action every build because we detect changes to the
attached device in that action.

This change creates a new build action, "get device configurations".
This action inspects the attached devices, filters out offline devices,
filters out devices without root, and then writes a configuration
file with the id+metadata for the first non-filtered device. This
configuration is then used by each of the build steps that interacts
with the device. This consolidates all the device checking to a single
place, and the build actions don't need to do any checking. In
addition, to detect changes in the attached device, we only need to run
this single action every build and the install/push actions will only
change when the device/metadata changes.

Also, with this change we can now gracefully handle the case where
multiple devices are attached (currently just write the configuration
for the first valid device and install to that one).

Review URL: https://chromiumcodereview.appspot.com/16831013

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@209582 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
cjhopman@chromium.org committed Jul 2, 2013
1 parent 172865b commit 014d1ff
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 71 deletions.
45 changes: 17 additions & 28 deletions build/android/gyp/apk_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,85 +14,75 @@
import subprocess
import sys

from util import build_device
from util import build_utils
from util import md5_check

BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..')
sys.path.append(BUILD_ANDROID_DIR)

from pylib import android_commands
from pylib.utils import apk_helper


def GetMetadata(apk_package):
def GetNewMetadata(device, apk_package):
"""Gets the metadata on the device for the apk_package apk."""
adb = android_commands.AndroidCommands()
output = adb.RunShellCommand('ls -l /data/app/')
output = device.RunShellCommand('ls -l /data/app/')
# Matches lines like:
# -rw-r--r-- system system 7376582 2013-04-19 16:34 org.chromium.chrome.testshell.apk
# -rw-r--r-- system system 7376582 2013-04-19 16:34 org.chromium.chrome.testshell-1.apk
apk_matcher = lambda s: re.match('.*%s(-[0-9]*)?.apk$' % apk_package, s)
matches = filter(apk_matcher, output)
return matches[0] if matches else None


def HasInstallMetadataChanged(apk_package, metadata_path):
def HasInstallMetadataChanged(device, apk_package, metadata_path):
"""Checks if the metadata on the device for apk_package has changed."""
if not os.path.exists(metadata_path):
return True

with open(metadata_path, 'r') as expected_file:
return expected_file.read() != GetMetadata(apk_package)
return expected_file.read() != device.GetInstallMetadata(apk_package)


def RecordInstallMetadata(apk_package, metadata_path):
def RecordInstallMetadata(device, apk_package, metadata_path):
"""Records the metadata from the device for apk_package."""
metadata = GetMetadata(apk_package)
metadata = GetNewMetadata(device, apk_package)
if not metadata:
if not android_commands.AndroidCommands().IsRootEnabled():
raise Exception('APK install failed unexpectedly -- root not enabled on '
'the device (run adb root).')
raise Exception('APK install failed unexpectedly.')

with open(metadata_path, 'w') as outfile:
outfile.write(metadata)


def main(argv):
if not build_utils.IsDeviceReady():
build_utils.PrintBigWarning(
'Zero (or multiple) devices attached. Skipping APK install.')
return

parser = optparse.OptionParser()
parser.add_option('--android-sdk-tools',
help='Path to Android SDK tools.')
parser.add_option('--apk-path',
help='Path to .apk to install.')
parser.add_option('--install-record',
help='Path to install record (touched only when APK is installed).')
parser.add_option('--build-device-configuration',
help='Path to build device configuration.')
parser.add_option('--stamp',
help='Path to touch on success.')
options, _ = parser.parse_args()

# TODO(cjhopman): Should this install to all devices/be configurable?
install_cmd = [
os.path.join(options.android_sdk_tools, 'adb'),
'install', '-r',
options.apk_path]
device = build_device.GetBuildDeviceFromPath(
options.build_device_configuration)
if not device:
return

serial_number = android_commands.AndroidCommands().Adb().GetSerialNumber()
serial_number = device.GetSerialNumber()
apk_package = apk_helper.GetPackageName(options.apk_path)

metadata_path = '%s.%s.device.time.stamp' % (options.apk_path, serial_number)

# If the APK on the device does not match the one that was last installed by
# the build, then the APK has to be installed (regardless of the md5 record).
force_install = HasInstallMetadataChanged(apk_package, metadata_path)
force_install = HasInstallMetadataChanged(device, apk_package, metadata_path)

def Install():
build_utils.CheckCallDie(install_cmd)
RecordInstallMetadata(apk_package, metadata_path)
device.Install(options.apk_path, reinstall=True)
RecordInstallMetadata(device, apk_package, metadata_path)
build_utils.Touch(options.install_record)


Expand All @@ -101,7 +91,6 @@ def Install():
Install,
record_path=record_path,
input_paths=[options.apk_path],
input_strings=install_cmd,
force=force_install)

if options.stamp:
Expand Down
25 changes: 13 additions & 12 deletions build/android/gyp/create_device_library_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
import os
import sys

from util import build_device
from util import build_utils
from util import md5_check

BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..')
sys.path.append(BUILD_ANDROID_DIR)

from pylib import android_commands
from pylib.utils import apk_helper

def RunShellCommand(adb, cmd):
output = adb.RunShellCommand(cmd)
def RunShellCommand(device, cmd):
output = device.RunShellCommand(cmd)

if output:
raise Exception(
Expand All @@ -53,15 +53,19 @@ def CreateSymlinkScript(options):


def TriggerSymlinkScript(options):
device = build_device.GetBuildDeviceFromPath(
options.build_device_configuration)
if not device:
return

apk_package = apk_helper.GetPackageName(options.apk)
apk_libraries_dir = '/data/data/%s/lib' % apk_package

adb = android_commands.AndroidCommands()
device_dir = os.path.dirname(options.script_device_path)
mkdir_cmd = ('if [ ! -e %(dir)s ]; then mkdir -p %(dir)s; fi ' %
{ 'dir': device_dir })
RunShellCommand(adb, mkdir_cmd)
adb.PushIfNeeded(options.script_host_path, options.script_device_path)
RunShellCommand(device, mkdir_cmd)
device.PushIfNeeded(options.script_host_path, options.script_device_path)

trigger_cmd = (
'APK_LIBRARIES_DIR=%(apk_libraries_dir)s; '
Expand All @@ -72,15 +76,10 @@ def TriggerSymlinkScript(options):
'target_dir': options.target_dir,
'script_device_path': options.script_device_path
}
RunShellCommand(adb, trigger_cmd)
RunShellCommand(device, trigger_cmd)


def main(argv):
if not build_utils.IsDeviceReady():
build_utils.PrintBigWarning(
'Zero (or multiple) devices attached. Skipping creating symlinks.')
return

parser = optparse.OptionParser()
parser.add_option('--apk', help='Path to the apk.')
parser.add_option('--script-host-path',
Expand All @@ -92,6 +91,8 @@ def main(argv):
parser.add_option('--target-dir',
help='Device directory that contains the target libraries for symlinks.')
parser.add_option('--stamp', help='Path to touch on success.')
parser.add_option('--build-device-configuration',
help='Path to build device configuration.')
options, _ = parser.parse_args()

required_options = ['apk', 'libraries_json', 'script_host_path',
Expand Down
75 changes: 75 additions & 0 deletions build/android/gyp/get_device_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python
#
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Gets and writes the configurations of the attached devices.
This configuration is used by later build steps to determine which devices to
install to and what needs to be installed to those devices.
"""

import logging
import optparse
import os
import subprocess
import sys

from util import build_utils
from util import build_device

BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..')
sys.path.append(BUILD_ANDROID_DIR)

from pylib.utils import apk_helper


def main(argv):
parser = optparse.OptionParser()
parser.add_option('--stamp', action='store')
parser.add_option('--output', action='store')
options, _ = parser.parse_args(argv)

devices = build_device.GetAttachedDevices()

device_configurations = []
for d in devices:
configuration, is_online, has_root = (
build_device.GetConfigurationForDevice(d))

if not is_online:
build_utils.PrintBigWarning(
'%s is not online. Skipping managed install for this device. '
'Try rebooting the device to fix this warning.' % d)
continue

if not has_root:
build_utils.PrintBigWarning(
'"adb root" failed on device: %s\n'
'Skipping managed install for this device.'
% configuration['description'])
continue

device_configurations.append(configuration)

if len(device_configurations) == 0:
build_utils.PrintBigWarning(
'No valid devices attached. Skipping managed install steps.')
elif len(devices) > 1:
# Note that this checks len(devices) and not len(device_configurations).
# This way, any time there are multiple devices attached it is
# explicitly stated which device we will install things to even if all but
# one device were rejected for other reasons (e.g. two devices attached,
# one w/o root).
build_utils.PrintBigWarning(
'Multiple devices attached. '
'Installing to the preferred device: '
'%(id)s (%(description)s)' % (device_configurations[0]))


build_device.WriteConfigurations(device_configurations, options.output)


if __name__ == '__main__':
sys.exit(main(sys.argv))
26 changes: 11 additions & 15 deletions build/android/gyp/push_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,19 @@
import os
import sys

from util import build_device
from util import build_utils
from util import md5_check

BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..')
sys.path.append(BUILD_ANDROID_DIR)

from pylib import android_commands


def DoPush(options):
libraries = build_utils.ReadJson(options.libraries_json)

adb = android_commands.AndroidCommands()
serial_number = adb.Adb().GetSerialNumber()
device = build_device.GetBuildDeviceFromPath(
options.build_device_configuration)
if not device:
return

serial_number = device.GetSerialNumber()
# A list so that it is modifiable in Push below.
needs_directory = [True]
for lib in libraries:
Expand All @@ -35,9 +34,9 @@ def DoPush(options):

def Push():
if needs_directory:
adb.RunShellCommand('mkdir -p ' + options.device_dir)
device.RunShellCommand('mkdir -p ' + options.device_dir)
needs_directory[:] = [] # = False
adb.PushIfNeeded(host_path, device_path)
device.PushIfNeeded(host_path, device_path)

record_path = '%s.%s.push.md5.stamp' % (host_path, serial_number)
md5_check.CallAndRecordIfStale(
Expand All @@ -48,11 +47,6 @@ def Push():


def main(argv):
if not build_utils.IsDeviceReady():
build_utils.PrintBigWarning(
'Zero (or multiple) devices attached. Skipping native library push.')
return

parser = optparse.OptionParser()
parser.add_option('--libraries-dir',
help='Directory that contains stripped libraries.')
Expand All @@ -61,6 +55,8 @@ def main(argv):
parser.add_option('--libraries-json',
help='Path to the json list of native libraries.')
parser.add_option('--stamp', help='Path to touch on success.')
parser.add_option('--build-device-configuration',
help='Path to build device configuration.')
options, _ = parser.parse_args()

required_options = ['libraries_dir', 'device_dir', 'libraries_json']
Expand Down
Loading

0 comments on commit 014d1ff

Please sign in to comment.