From e373a702399d7fc2d6f585e4b8c010f7a010d987 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Ruel Date: Thu, 18 Feb 2016 19:19:14 -0500 Subject: [PATCH] Switch use of google-flags (gflags) to more standard argparse. It is more appropriate for open source projects to use argparse. Keep the 'clever' approach and uses the docstring on methods on AdbCommands/FastbootCommands to generate the arguments on the parser. Override the documentation only when it doesn't make sense, like when an argument can be "object-like". Update default fastboot packet size from 4kb to 1Mb. Fix tests (adb_test.py was broken). Make them executable. --- README.rst | 6 +- adb/adb_commands.py | 55 +++++----- adb/adb_debug.py | 169 ++++++++++++++++++++++++------ adb/common_cli.py | 236 +++++++++++++++++++++--------------------- adb/fastboot.py | 55 +++++----- adb/fastboot_debug.py | 95 +++++++++++++---- adb_test.py | 1 + fastboot_test.py | 3 +- setup.py | 2 +- 9 files changed, 400 insertions(+), 222 deletions(-) mode change 100644 => 100755 adb_test.py mode change 100644 => 100755 fastboot_test.py diff --git a/README.rst b/README.rst index 064a10c..9de0804 100644 --- a/README.rst +++ b/README.rst @@ -27,8 +27,8 @@ Cons: Dependencies: * libusb1 (1.0.16+) - * python-gflags (2.0+) * python-libusb1 (1.2.0+) * python-progressbar (for fastboot_debug, 2.3+) - * python-m2crypto (0.21.1+) - + * One of: + * python-m2crypto (0.21.1+) + * python-rsa (3.2+) diff --git a/adb/adb_commands.py b/adb/adb_commands.py index 0996552..0a7b79c 100644 --- a/adb/adb_commands.py +++ b/adb/adb_commands.py @@ -109,13 +109,13 @@ def Devices(cls): def GetState(self): return self._device_state - def Install(self, apk_path, destination_dir=None, timeout_ms=None): + def Install(self, apk_path, destination_dir='', timeout_ms=None): """Install an apk to the device. Doesn't support verifier file, instead allows destination directory to be overridden. - Arguments: + Args: apk_path: Local path to apk to install. destination_dir: Optional destination directory. Use /system/app/ for persistent applications. @@ -135,44 +135,42 @@ def Install(self, apk_path, destination_dir=None, timeout_ms=None): def Push(self, source_file, device_filename, mtime='0', timeout_ms=None): """Push a file or directory to the device. - Arguments: + Args: source_file: Either a filename, a directory or file-like object to push to the device. - device_filename: The filename on the device to write to. + device_filename: Destination on the device to write to. mtime: Optional, modification time to set on the file. timeout_ms: Expected timeout for any part of the push. """ - - if os.path.isdir(source_file): - self.Shell("mkdir " + device_filename) - for dir_file in os.listdir(source_file): - self.Push(os.path.join(source_file, dir_file), device_filename + "/" + dir_file) - return - - connection = self.protocol_handler.Open( - self.handle, destination='sync:', - timeout_ms=timeout_ms) if isinstance(source_file, basestring): + if os.path.isdir(source_file): + self.Shell("mkdir " + device_filename) + for f in os.listdir(source_file): + self.Push(os.path.join(source_file, f), device_filename + '/' + f) + return source_file = open(source_file) + + connection = self.protocol_handler.Open( + self.handle, destination='sync:', timeout_ms=timeout_ms) self.filesync_handler.Push(connection, source_file, device_filename, mtime=int(mtime)) connection.Close() - def Pull(self, device_filename, dest_file=None, timeout_ms=None): + def Pull(self, device_filename, dest_file='', timeout_ms=None): """Pull a file from the device. - Arguments: - device_filename: The filename on the device to pull. + Args: + device_filename: Filename on the device to pull. dest_file: If set, a filename or writable file-like object. timeout_ms: Expected timeout for any part of the pull. Returns: The file data if dest_file is not set. """ - if isinstance(dest_file, basestring): - dest_file = open(dest_file, 'w') - elif not dest_file: + if not dest_file: dest_file = cStringIO.StringIO() + elif isinstance(dest_file, basestring): + dest_file = open(dest_file, 'w') connection = self.protocol_handler.Open( self.handle, destination='sync:', timeout_ms=timeout_ms) @@ -192,7 +190,11 @@ def Stat(self, device_filename): return mode, size, mtime def List(self, device_path): - """Return a directory listing of the given path.""" + """Return a directory listing of the given path. + + Args: + device_path: Directory to list. + """ connection = self.protocol_handler.Open(self.handle, destination='sync:') listing = self.filesync_handler.List(connection, device_path) connection.Close() @@ -201,7 +203,8 @@ def List(self, device_path): def Reboot(self, destination=''): """Reboot the device. - Specify 'bootloader' for fastboot. + Args: + destination: Specify 'bootloader' for fastboot. """ self.protocol_handler.Open(self.handle, 'reboot:%s' % destination) @@ -227,7 +230,7 @@ def StreamingShell(self, command, timeout_ms=None): """Run command on the device, yielding each line of output. Args: - command: the command to run on the target. + command: Command to run on the target. timeout_ms: Maximum time to allow the command to run. Yields: @@ -238,7 +241,11 @@ def StreamingShell(self, command, timeout_ms=None): timeout_ms=timeout_ms) def Logcat(self, options, timeout_ms=None): - """Run 'shell logcat' and stream the output to stdout.""" + """Run 'shell logcat' and stream the output to stdout. + + Args: + options: Arguments to pass to 'logcat'. + """ return self.protocol_handler.StreamingCommand( self.handle, service='shell', command='logcat %s' % options, timeout_ms=timeout_ms) diff --git a/adb/adb_debug.py b/adb/adb_debug.py index 839ce72..179a0fc 100755 --- a/adb/adb_debug.py +++ b/adb/adb_debug.py @@ -12,15 +12,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""ADB debugging binary. -Call it similar to how you call android's adb. Takes either --serial or ---port_path to connect to a device. -""" +"""Daemon-less ADB client in python.""" + +import argparse +import functools +import logging import os +import stat import sys - -import gflags +import time import adb_commands import common_cli @@ -36,34 +37,144 @@ rsa_signer = None -gflags.ADOPT_module_key_flags(common_cli) +def Devices(args): + """Lists the available devices. + + Mimics 'adb devices' output: + List of devices attached + 015DB7591102001A device 1,2 + """ + for d in adb_commands.AdbCommands.Devices(): + if args.output_port_path: + print('%s\tdevice\t%s' % ( + d.serial_number, ','.join(str(p) for p in d.port_path))) + else: + print('%s\tdevice' % d.serial_number) + return 0 + + +def List(self, device_path): + """Prints a directory listing. + + Args: + device_path: Directory to list. + """ + files = adb_commands.AdbCommands.List(self, device_path) + files.sort(key=lambda x: x.filename) + maxname = max(len(f.filename) for f in files) + maxsize = max(len(str(f.size)) for f in files) + for f in files: + mode = ( + ('d' if stat.S_ISDIR(f.mode) else '-') + + ('r' if f.mode & stat.S_IRUSR else '-') + + ('w' if f.mode & stat.S_IWUSR else '-') + + ('x' if f.mode & stat.S_IXUSR else '-') + + ('r' if f.mode & stat.S_IRGRP else '-') + + ('w' if f.mode & stat.S_IWGRP else '-') + + ('x' if f.mode & stat.S_IXGRP else '-') + + ('r' if f.mode & stat.S_IROTH else '-') + + ('w' if f.mode & stat.S_IWOTH else '-') + + ('x' if f.mode & stat.S_IXOTH else '-')) + t = time.gmtime(f.mtime) + yield '%s %*d %04d-%02d-%02d %02d:%02d:%02d %-*s\n' % ( + mode, maxsize, f.size, + t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, + maxname, f.filename) + + +@functools.wraps(adb_commands.AdbCommands.Logcat) +def Logcat(self, *options): + return adb_commands.AdbCommands.StreamingShell( + self, 'logcat ' + ' '.join(options)) + + +def Shell(self, *command): + """Runs a command on the device and prints the stdout. + + Args: + command: Command to run on the target. + """ + return adb_commands.AdbCommands.StreamingShell(self, ' '.join(command)) + + +def main(): + common = common_cli.GetCommonArguments() + common.add_argument( + '--rsa_key_path', action='append', default=[], + metavar='~/.android/adbkey', + help='RSA key(s) to use, use multiple times to load mulitple keys') + common.add_argument( + '--auth_timeout_s', default=60., metavar='60', type=int, + help='Seconds to wait for the dialog to be accepted when using ' + 'authenticated ADB.') + device = common_cli.GetDeviceArguments() + parents = [common, device] + + parser = argparse.ArgumentParser( + description=sys.modules[__name__].__doc__, parents=[common]) + subparsers = parser.add_subparsers(title='Commands', dest='command_name') -gflags.DEFINE_multistring('rsa_key_path', '~/.android/adbkey', - 'RSA key(s) to use') -gflags.DEFINE_integer('auth_timeout_s', 60, - 'Seconds to wait for the dialog to be accepted when using ' - 'authenticated ADB.') -FLAGS = gflags.FLAGS + subparser = subparsers.add_parser( + name='help', help='Prints the commands available') + subparser = subparsers.add_parser( + name='devices', help='Lists the available devices', parents=[common]) + subparser.add_argument( + '--output_port_path', action='store_true', + help='Outputs the port_path alongside the serial') + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Install) + subparser = common_cli.MakeSubparser(subparsers, parents, List) + subparser = common_cli.MakeSubparser(subparsers, parents, Logcat) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Push, + {'source_file': 'Filename or directory to push to the device.'}) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Pull, + { + 'dest_file': 'Filename to write to on the host, if not specified, ' + 'prints the content to stdout.', + }) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Reboot) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.RebootBootloader) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Remount) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Root) + subparser = common_cli.MakeSubparser(subparsers, parents, Shell) -def GetRSAKwargs(): - if FLAGS.rsa_key_path: - if rsa_signer is None: - print >> sys.stderr, 'Please install either M2Crypto or python-rsa' - sys.exit(1) - return { - 'rsa_keys': [rsa_signer(os.path.expanduser(path)) - for path in FLAGS.rsa_key_path], - 'auth_timeout_ms': int(FLAGS.auth_timeout_s * 1000.0), - } - return {} + if len(sys.argv) == 1: + parser.print_help() + return 2 + args = parser.parse_args() + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + if not args.rsa_key_path: + default = os.path.expanduser('~/.android/adbkey') + if os.path.isfile(default): + args.rsa_key_path = [default] + if args.rsa_key_path and not rsa_signer: + parser.error('Please install either M2Crypto or python-rsa') + # Hacks so that the generated doc is nicer. + if args.command_name == 'devices': + return Devices(args) + if args.command_name == 'help': + parser.print_help() + return 0 + if args.command_name == 'logcat': + args.positional = args.options + elif args.command_name == 'shell': + args.positional = args.command -def main(argv): - common_cli.StartCli( - argv, adb_commands.AdbCommands.ConnectDevice, - list_callback=adb_commands.AdbCommands.Devices, **GetRSAKwargs()) + return common_cli.StartCli( + args, + adb_commands.AdbCommands.ConnectDevice, + auth_timeout_ms=args.auth_timeout_s * 1000, + rsa_keys=[rsa_signer(path) for path in args.rsa_key_path]) if __name__ == '__main__': - main(FLAGS(sys.argv)) + sys.exit(main()) diff --git a/adb/common_cli.py b/adb/common_cli.py index 435e99f..164e4e6 100644 --- a/adb/common_cli.py +++ b/adb/common_cli.py @@ -19,144 +19,142 @@ outputting the results. """ +import argparse import cStringIO import inspect +import logging import re import sys +import textwrap import types -import gflags - import usb_exceptions -gflags.DEFINE_integer('timeout_ms', 10000, 'Timeout in milliseconds.') -gflags.DEFINE_list('port_path', [], 'USB port path integers (eg 1,2 or 2,1,1)') -gflags.DEFINE_string('serial', None, 'Device serial to look for (host:port or USB serial)', short_name='s') - -gflags.DEFINE_bool('output_port_path', False, - 'Affects the devices command only, outputs the port_path ' - 'alongside the serial if true.') - -FLAGS = gflags.FLAGS - -_BLACKLIST = { - 'Connect', - 'Close', - 'ConnectDevice', - 'DeviceIsAvailable', -} - - -def Uncamelcase(name): - parts = re.split(r'([A-Z][a-z]+)', name)[1:-1:2] - return ('-'.join(parts)).lower() - - -def Camelcase(name): - return name.replace('-', ' ').title().replace(' ', '') - -def Usage(adb_dev): - methods = inspect.getmembers(adb_dev, inspect.ismethod) - print 'Methods:' - for name, method in methods: - if name.startswith('_'): - continue - if not method.__doc__: - continue - if name in _BLACKLIST: - continue - - argspec = inspect.getargspec(method) - args = argspec.args[1:] or '' - # Surround default'd arguments with [] - defaults = argspec.defaults or [] - if args: - args = (args[:-len(defaults)] + - ['[%s]' % arg for arg in args[-len(defaults):]]) - - args = ' ' + ' '.join(args) - - print ' %s%s:' % (Uncamelcase(name), args) - print ' %s' % method.__doc__ - - -def StartCli(argv, device_callback, kwarg_callback=None, list_callback=None, - **device_kwargs): - """Starts a common CLI interface for this usb path and protocol.""" - argv = argv[1:] - - if len(argv) == 1 and argv[0] == 'devices' and list_callback is not None: - # To mimic 'adb devices' output like: - # ------------------------------ - # List of devices attached - # 015DB7591102001A device - # Or with --output_port_path: - # 015DB7591102001A device 1,2 - # ------------------------------ - for device in list_callback(): - if FLAGS.output_port_path: - print '%s\tdevice\t%s' % ( - device.serial_number, - ','.join(str(port) for port in device.port_path)) +class _PortPathAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr( + namespace, self.dest, + [int(i) for i in values.replace('/', ',').split(',')]) + + +class PositionalArg(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + namespace.positional.append(values) + + +def GetDeviceArguments(): + group = argparse.ArgumentParser('Device', add_help=False) + group.add_argument( + '--timeout_ms', default=10000, type=int, metavar='10000', + help='Timeout in milliseconds.') + group.add_argument( + '--port_path', action=_PortPathAction, + help='USB port path integers (eg 1,2 or 2,1,1)') + group.add_argument( + '-s', '--serial', + help='Device serial to look for (host:port or USB serial)') + return group + + +def GetCommonArguments(): + group = argparse.ArgumentParser('Common', add_help=False) + group.add_argument('--verbose', action='store_true', help='Enable logging') + return group + + +def _DocToArgs(doc): + """Converts a docstring documenting arguments into a dict.""" + offset = None + in_arg = False + out = {} + for l in doc.splitlines(): + if l.strip() == 'Args:': + in_arg = True + elif in_arg: + if not l.strip(): + break + if offset is None: + offset = len(l) - len(l.lstrip()) + l = l[offset:] + if l[0] == ' ': + out[m.group(1)] += ' ' + l.lstrip() else: - print '%s\tdevice' % device.serial_number - return + m = re.match(r'^([a-z_]+): (.+)$', l.strip()) + out[m.group(1)] = m.group(2) + return out + + +def MakeSubparser(subparsers, parents, method, arguments=None): + """Returns an argparse subparser to create a 'subcommand' to adb.""" + name = ('-'.join(re.split(r'([A-Z][a-z]+)', method.__name__)[1:-1:2])).lower() + help = method.__doc__.splitlines()[0] + subparser = subparsers.add_parser( + name=name, description=help, help=help.rstrip('.'), parents=parents) + subparser.set_defaults(method=method, positional=[]) + argspec = inspect.getargspec(method) + offset = len(argspec.args) - len(argspec.defaults or []) - 1 + positional = [] + for i in xrange(1, len(argspec.args)): + if i > offset and argspec.defaults[i-offset-1] is None: + break + positional.append(argspec.args[i]) + args_help = _DocToArgs(method.__doc__) + defaults = [None] * offset + list(argspec.defaults or []) + if positional or argspec.varargs: + for name, default in zip(positional, defaults): + if not isinstance(default, (None.__class__, str)): + continue + subparser.add_argument( + name, help=(arguments or {}).get(name, args_help.get(name)), + default=default, nargs='?' if default is not None else None, + action=PositionalArg) + if argspec.varargs: + subparser.add_argument( + argspec.varargs, nargs=argparse.REMAINDER, + help=(arguments or {}).get(argspec.varargs, args_help.get(argspec.varargs))) + return subparser + + +def _RunMethod(dev, args, extra): + """Runs a method registered via MethodToSubparser.""" + logging.info('%s(%s)', args.method.__name__, ', '.join(args.positional)) + result = args.method(dev, *args.positional, **extra) + if result is not None: + if isinstance(result, cStringIO.OutputType): + sys.stdout.write(result.getvalue()) + elif isinstance(result, (list, types.GeneratorType)): + r = '' + for r in result: + r = str(r) + sys.stdout.write(r) + if not r.endswith('\n'): + sys.stdout.write('\n') + else: + result = str(result) + sys.stdout.write(result) + if not result.endswith('\n'): + sys.stdout.write('\n') + return 0 - port_path = [int(part) for part in FLAGS.port_path] - serial = FLAGS.serial - device_kwargs.setdefault('default_timeout_ms', FLAGS.timeout_ms) +def StartCli(args, device_factory, extra=None, **device_kwargs): + """Starts a common CLI interface for this usb path and protocol.""" try: - dev = device_callback( - port_path=port_path, serial=serial, **device_kwargs) + dev = device_factory( + port_path=args.port_path, serial=args.serial, + default_timeout_ms=args.timeout_ms, **device_kwargs) except usb_exceptions.DeviceNotFoundError as e: print >> sys.stderr, 'No device found: %s' % e - return + return 1 except usb_exceptions.CommonUsbError as e: print >> sys.stderr, 'Could not connect to device: %s' % e - raise - - if not argv: - Usage(dev) - return - - kwargs = {} - - # CamelCase method names, eg reboot-bootloader -> RebootBootloader - method_name = Camelcase(argv[0]) - method = getattr(dev, method_name) - argspec = inspect.getargspec(method) - num_args = len(argspec.args) - 1 # self is the first one. - # Handle putting the remaining command line args into the last normal arg. - argv.pop(0) - - # Flags -> Keyword args - if kwarg_callback: - kwarg_callback(kwargs, argspec) - + return 1 try: - if num_args == 1: - # Only one argument, so join them all with spaces - result = method(' '.join(argv), **kwargs) - else: - result = method(*argv, **kwargs) - - if result is not None: - if isinstance(result, cStringIO.OutputType): - sys.stdout.write(result.getvalue()) - elif isinstance(result, (list, types.GeneratorType)): - for r in result: - r = str(r) - sys.stdout.write(r) - if not r.endswith('\n'): - sys.stdout.write('\n') - else: - sys.stdout.write(result) - sys.stdout.write('\n') + return _RunMethod(dev, args, extra or {}) except Exception as e: # pylint: disable=broad-except + raise sys.stdout.write(str(e)) - return + return 1 finally: dev.Close() - diff --git a/adb/fastboot.py b/adb/fastboot.py index 684d3ee..0cdf040 100644 --- a/adb/fastboot.py +++ b/adb/fastboot.py @@ -13,6 +13,7 @@ # limitations under the License. """A libusb1-based fastboot implementation.""" +import argparse import binascii import collections import cStringIO @@ -20,16 +21,9 @@ import os import struct -import gflags - import common import usb_exceptions -FLAGS = gflags.FLAGS -gflags.DEFINE_integer('fastboot_write_chunk_size_kb', 4, - 'The size of packets to write to usb, this is set to 4 ' - "for legacy reasons. We've had success with 1MB " - 'DRASTICALLY decreasing flashing times.') _LOG = logging.getLogger('fastboot') @@ -70,13 +64,15 @@ class FastbootProtocol(object): """Encapsulates the fastboot protocol.""" FINAL_HEADERS = {'OKAY', 'DATA'} - def __init__(self, usb): + def __init__(self, usb, chunk_kb=1024): """Constructs a FastbootProtocol instance. - Arguments: + Args: usb: UsbHandle instance. + chunk_kb: Packet size. For older devices, 4 may be required. """ self.usb = usb + self.chunk_kb = chunk_kb @property def usb_handle(self): @@ -111,7 +107,7 @@ def HandleDataSending(self, source_file, source_len, progress_callback=None, timeout_ms=None): """Handles the protocol for sending data to the device. - Arguments: + Args: source_file: File-object to read from for the device. source_len: Amount of data, in bytes, to send to the device. info_cb: Optional callback for text sent from the bootloader. @@ -143,7 +139,7 @@ def HandleDataSending(self, source_file, source_len, def _AcceptResponses(self, expected_header, info_cb, timeout_ms=None): """Accepts responses until the expected header or a FAIL. - Arguments: + Args: expected_header: OKAY or DATA info_cb: Optional callback for text sent from the bootloader. timeout_ms: Timeout in milliseconds to wait for each response. @@ -195,7 +191,7 @@ def _Write(self, data, length, progress_callback=None): progress = self._HandleProgress(length, progress_callback) progress.next() while length: - tmp = data.read(FLAGS.fastboot_write_chunk_size_kb * 1024) + tmp = data.read(self.chunk_kb * 1024) length -= len(tmp) self.usb.BulkWrite(tmp) @@ -205,16 +201,15 @@ def _Write(self, data, length, progress_callback=None): class FastbootCommands(object): """Encapsulates the fastboot commands.""" - protocol_handler = FastbootProtocol - def __init__(self, usb): + def __init__(self, usb, chunk_kb=1024): """Constructs a FastbootCommands instance. - Arguments: + Args: usb: UsbHandle instance. """ self._usb = usb - self._protocol = self.protocol_handler(usb) + self._protocol = FastbootProtocol(usb, chunk_kb) @property def usb_handle(self): @@ -225,12 +220,12 @@ def Close(self): @classmethod def ConnectDevice( - cls, port_path=None, serial=None, default_timeout_ms=None): + cls, port_path=None, serial=None, default_timeout_ms=None, chunk_kb=1024): """Convenience function to get an adb device from usb path or serial.""" usb = common.UsbHandle.FindAndOpen( DeviceIsAvailable, port_path=port_path, serial=serial, timeout_ms=default_timeout_ms) - return cls(usb) + return cls(usb, chunk_kb=chunk_kb) @classmethod def Devices(cls): @@ -300,7 +295,7 @@ def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK): """Flashes the last downloaded file to the given partition. Args: - partition: Partition to flash. + partition: Partition to overwrite with the new image. timeout_ms: Optional timeout in milliseconds to wait for it to finish. info_cb: See Download. Usually no messages. @@ -311,15 +306,20 @@ def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK): timeout_ms=timeout_ms) def Erase(self, partition, timeout_ms=None): - """Erases the given partition.""" + """Erases the given partition. + + Args: + partition: Partition to clear. + """ self._SimpleCommand('erase', arg=partition, timeout_ms=timeout_ms) def Getvar(self, var, info_cb=DEFAULT_MESSAGE_CALLBACK): """Returns the given variable's definition. Args: - var: A variable the bootloader tracks, such as version. + var: A variable the bootloader tracks. Use 'all' to get them all. info_cb: See Download. Usually no messages. + Returns: Value of var according to the current bootloader. """ @@ -329,9 +329,10 @@ def Oem(self, command, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): """Executes an OEM command on the device. Args: - command: The command to execute, such as 'poweroff' or 'bootconfig read'. + command: Command to execute, such as 'poweroff' or 'bootconfig read'. timeout_ms: Optional timeout in milliseconds to wait for a response. info_cb: See Download. Messages vary based on command. + Returns: The final response from the device. """ @@ -342,17 +343,19 @@ def Continue(self): """Continues execution past fastboot into the system.""" return self._SimpleCommand('continue') - def Reboot(self, target_mode=None, timeout_ms=None): + def Reboot(self, target_mode='', timeout_ms=None): """Reboots the device. Args: - target_mode: Normal reboot when unspecified (or None). Can specify - other target modes, such as 'recovery' or 'bootloader'. + target_mode: Normal reboot when unspecified. Can specify other target + modes such as 'recovery' or 'bootloader'. timeout_ms: Optional timeout in milliseconds to wait for a response. + Returns: Usually the empty string. Depends on the bootloader and the target_mode. """ - return self._SimpleCommand('reboot', arg=target_mode, timeout_ms=timeout_ms) + return self._SimpleCommand( + 'reboot', arg=target_mode or None, timeout_ms=timeout_ms) def RebootBootloader(self, timeout_ms=None): """Reboots into the bootloader, usually equiv to Reboot('bootloader').""" diff --git a/adb/fastboot_debug.py b/adb/fastboot_debug.py index a9c056f..82fc24f 100755 --- a/adb/fastboot_debug.py +++ b/adb/fastboot_debug.py @@ -12,34 +12,94 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Fastboot debugging binary. + +"""Fastboot in python. Call it similar to how you call android's fastboot. Call it similar to how you call android's fastboot, but this only accepts usb paths and no serials. """ + +import argparse +import inspect +import logging import sys -import gflags import progressbar import common_cli import fastboot -gflags.ADOPT_module_key_flags(common_cli) -FLAGS = gflags.FLAGS +def Devices(args): + """Lists the available devices. + + List of devices attached + 015DB7591102001A device + """ + for device in fastboot.FastbootCommands.Devices(): + print('%s\tdevice' % device.serial_number) + return 0 + + +def _InfoCb(message): + # Use an unbuffered version of stdout. + if not message.message: + return + sys.stdout.write('%s: %s\n' % (message.header, message.message)) + sys.stdout.flush() + +def main(): + common = common_cli.GetCommonArguments() + device = common_cli.GetDeviceArguments() + device.add_argument( + '--chunk', type=int, default=1024, metavar='1024', + help='Size of packets to write in Kb. For older devices, it may be ' + 'required to use 4.') + parents = [common, device] -def KwargHandler(kwargs, argspec): + parser = argparse.ArgumentParser( + description=sys.modules[__name__].__doc__, parents=[common]) + subparsers = parser.add_subparsers(title='Commands', dest='command_name') + subparser = subparsers.add_parser( + name='help', help='Prints the commands available') + subparser = subparsers.add_parser( + name='devices', help='Lists the available devices', parents=[common]) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Continue) + + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Download, + {'source_file': 'Filename on the host to push'}) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Erase) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Flash) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Getvar) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Oem) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Reboot) + + if len(sys.argv) == 1: + parser.print_help() + return 2 + + args = parser.parse_args() + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + if args.command_name == 'devices': + return Devices(args) + if args.command_name == 'help': + parser.print_help() + return 0 + + kwargs = {} + argspec = inspect.getargspec(args.method) if 'info_cb' in argspec.args: - # Use an unbuffered version of stdout. - def InfoCb(message): - if not message.message: - return - sys.stdout.write('%s: %s\n' % (message.header, message.message)) - sys.stdout.flush() - kwargs['info_cb'] = InfoCb + kwargs['info_cb'] = _InfoCb if 'progress_callback' in argspec.args: bar = progressbar.ProgessBar( widgets=[progressbar.Bar(), progressbar.Percentage()]) @@ -50,13 +110,10 @@ def SetProgress(current, total): bar.finish() kwargs['progress_callback'] = SetProgress - -def main(argv): - common_cli.StartCli( - argv, fastboot.FastbootCommands.ConnectDevice, - list_callback=fastboot.FastbootCommands.Devices, - kwarg_callback=KwargHandler) + return common_cli.StartCli( + args, fastboot.FastbootCommands.ConnectDevice, chunk_kb=args.chunk, + extra=kwargs) if __name__ == '__main__': - main(FLAGS(sys.argv)) + sys.exit(main()) diff --git a/adb_test.py b/adb_test.py old mode 100644 new mode 100755 index 96eb037..6b89782 --- a/adb_test.py +++ b/adb_test.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Copyright 2014 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/fastboot_test.py b/fastboot_test.py old mode 100644 new mode 100755 index a8ce840..f857edd --- a/fastboot_test.py +++ b/fastboot_test.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # Copyright 2014 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -115,7 +116,7 @@ def testFlashFromFile(self): progresses = [] pieces = [] - chunk_size = FLAGS.fastboot_write_chunk_size_kb * 1024 + chunk_size = fastboot.FastbootProtocol(None).chunk_kb * 1024 while raw: pieces.append(raw[:chunk_size]) raw = raw[chunk_size:] diff --git a/setup.py b/setup.py index 8545664..37d00c0 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ keywords = ['android', 'adb', 'fastboot'], - install_requires = ['python-gflags>=2.0', 'libusb1>=1.0.16', 'M2Crypto>=0.21.1'], + install_requires = ['libusb1>=1.0.16', 'M2Crypto>=0.21.1'], extra_requires = { 'fastboot': 'progressbar>=2.3'