diff --git a/build/android/gyp/apk_install.py b/build/android/gyp/apk_install.py index ef10c6a3bd6954..f0ed973a3085f1 100755 --- a/build/android/gyp/apk_install.py +++ b/build/android/gyp/apk_install.py @@ -14,20 +14,18 @@ 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 @@ -35,23 +33,19 @@ def GetMetadata(apk_package): 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: @@ -59,11 +53,6 @@ def RecordInstallMetadata(apk_package, metadata_path): 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.') @@ -71,28 +60,29 @@ def main(argv): 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) @@ -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: diff --git a/build/android/gyp/create_device_library_links.py b/build/android/gyp/create_device_library_links.py index 5dd5f39988c220..1df177d6dd1366 100755 --- a/build/android/gyp/create_device_library_links.py +++ b/build/android/gyp/create_device_library_links.py @@ -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( @@ -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; ' @@ -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', @@ -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', diff --git a/build/android/gyp/get_device_configuration.py b/build/android/gyp/get_device_configuration.py new file mode 100644 index 00000000000000..f27c12be4b360a --- /dev/null +++ b/build/android/gyp/get_device_configuration.py @@ -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)) diff --git a/build/android/gyp/push_libraries.py b/build/android/gyp/push_libraries.py index b94d6b5ebf800e..349e0cbafc621c 100755 --- a/build/android/gyp/push_libraries.py +++ b/build/android/gyp/push_libraries.py @@ -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: @@ -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( @@ -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.') @@ -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'] diff --git a/build/android/gyp/util/build_device.py b/build/android/gyp/util/build_device.py new file mode 100644 index 00000000000000..11f6277453ba67 --- /dev/null +++ b/build/android/gyp/util/build_device.py @@ -0,0 +1,98 @@ +# 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. + +""" A simple device interface for build steps. + +""" + +import logging +import os +import re +import sys + +import build_utils + +BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..', '..') +sys.path.append(BUILD_ANDROID_DIR) + +from pylib import android_commands + +from pylib.android_commands import GetAttachedDevices + + +class BuildDevice(object): + def __init__(self, configuration): + self.id = configuration['id'] + self.description = configuration['description'] + self.install_metadata = configuration['install_metadata'] + self.adb = android_commands.AndroidCommands(self.id) + + def RunShellCommand(self, *args, **kwargs): + return self.adb.RunShellCommand(*args, **kwargs) + + def PushIfNeeded(self, *args, **kwargs): + return self.adb.PushIfNeeded(*args, **kwargs) + + def GetSerialNumber(self): + return self.id + + def Install(self, *args, **kwargs): + return self.adb.Install(*args, **kwargs) + + def GetInstallMetadata(self, apk_package): + """Gets the metadata on the device for the apk_package apk.""" + # 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, self.install_metadata) + return matches[0] if matches else None + + +def GetConfigurationForDevice(id): + adb = android_commands.AndroidCommands(id) + configuration = None + has_root = False + is_online = adb.IsOnline() + if is_online: + cmd = 'ls -l /data/app; getprop ro.build.description' + cmd_output = adb.RunShellCommand(cmd) + has_root = not 'Permission denied' in cmd_output[0] + if not has_root: + # Disable warning log messages from EnableAdbRoot() + logging.getLogger().disabled = True + has_root = adb.EnableAdbRoot() + logging.getLogger().disabled = False + cmd_output = adb.RunShellCommand(cmd) + + configuration = { + 'id': id, + 'description': cmd_output[-1], + 'install_metadata': cmd_output[:-1], + } + return configuration, is_online, has_root + + +def WriteConfigurations(configurations, path): + # Currently we only support installing to the first device. + build_utils.WriteJson(configurations[:1], path, only_if_changed=True) + + +def ReadConfigurations(path): + return build_utils.ReadJson(path) + + +def GetBuildDevice(configurations): + assert len(configurations) == 1 + return BuildDevice(configurations[0]) + + +def GetBuildDeviceFromPath(path): + configurations = ReadConfigurations(path) + if len(configurations) > 0: + return GetBuildDevice(ReadConfigurations(path)) + return None + diff --git a/build/android/push_libraries.gypi b/build/android/push_libraries.gypi index 17f479debbef31..1f17660c44832d 100644 --- a/build/android/push_libraries.gypi +++ b/build/android/push_libraries.gypi @@ -29,17 +29,14 @@ '<(DEPTH)/build/android/gyp/util/md5_check.py', '<(DEPTH)/build/android/gyp/push_libraries.py', '<(strip_stamp)', + '<(build_device_config_path)', ], 'outputs': [ '<(push_stamp)', - # If a user switches the connected device, new libraries may - # need to be pushed even if there have been no changes. To - # ensure that the libraries on the device are always - # up-to-date, this step should always be triggered. - '<(push_stamp).fake', ], 'action': [ 'python', '<(DEPTH)/build/android/gyp/push_libraries.py', + '--build-device-configuration=<(build_device_config_path)', '--libraries-dir=<(libraries_source_dir)', '--device-dir=<(device_library_dir)', '--libraries-json=<(ordered_libraries_file)', diff --git a/build/android/setup.gyp b/build/android/setup.gyp index 9abfbc6cf341db..7dce19de7266a0 100644 --- a/build/android/setup.gyp +++ b/build/android/setup.gyp @@ -25,6 +25,27 @@ }], ], 'targets': [ + { + 'target_name': 'get_build_device_configurations', + 'type': 'none', + 'actions': [ + { + 'action_name': 'get configurations', + 'inputs': [ + 'gyp/util/build_device.py', + 'gyp/get_device_configuration.py', + ], + 'outputs': [ + '<(build_device_config_path)', + '<(build_device_config_path).fake', + ], + 'action': [ + 'python', 'gyp/get_device_configuration.py', + '--output=<(build_device_config_path)', + ], + } + ], + }, { # Target for creating common output build directories. Creating output # dirs beforehand ensures that build scripts can assume these folders to @@ -37,11 +58,11 @@ { 'action_name': 'create_java_output_dirs', 'variables' : { - 'output_dirs' : [ - '<(PRODUCT_DIR)/apks', - '<(PRODUCT_DIR)/lib.java', - '<(PRODUCT_DIR)/test.lib.java', - ] + 'output_dirs' : [ + '<(PRODUCT_DIR)/apks', + '<(PRODUCT_DIR)/lib.java', + '<(PRODUCT_DIR)/test.lib.java', + ] }, 'inputs' : [], # By not specifying any outputs, we ensure that this command isn't diff --git a/build/common.gypi b/build/common.gypi index 75cb12e9d28d49..12ada0e96c045a 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -1087,6 +1087,10 @@ 'android_app_version_name%': 'Developer Build', 'android_app_version_code%': 0, + + # Contains data about the attached devices for gyp_managed_install. + 'build_device_config_path': '<(PRODUCT_DIR)/build_devices.cfg', + 'sas_dll_exists': '