common_cli.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # Copyright 2014 Google Inc. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Common code for ADB and Fastboot CLI.
  15. Usage introspects the given class for methods, args, and docs to show the user.
  16. StartCli handles connecting to a device, calling the expected method, and
  17. outputting the results.
  18. """
  19. from __future__ import print_function
  20. import argparse
  21. import io
  22. import inspect
  23. import logging
  24. import re
  25. import sys
  26. import types
  27. from adb import usb_exceptions
  28. class _PortPathAction(argparse.Action):
  29. def __call__(self, parser, namespace, values, option_string=None):
  30. setattr(
  31. namespace, self.dest,
  32. [int(i) for i in values.replace('/', ',').split(',')])
  33. class PositionalArg(argparse.Action):
  34. def __call__(self, parser, namespace, values, option_string=None):
  35. namespace.positional.append(values)
  36. def GetDeviceArguments():
  37. group = argparse.ArgumentParser('Device', add_help=False)
  38. group.add_argument(
  39. '--timeout_ms', default=10000, type=int, metavar='10000',
  40. help='Timeout in milliseconds.')
  41. group.add_argument(
  42. '--port_path', action=_PortPathAction,
  43. help='USB port path integers (eg 1,2 or 2,1,1)')
  44. group.add_argument(
  45. '-s', '--serial',
  46. help='Device serial to look for (host:port or USB serial)')
  47. return group
  48. def GetCommonArguments():
  49. group = argparse.ArgumentParser('Common', add_help=False)
  50. group.add_argument('--verbose', action='store_true', help='Enable logging')
  51. return group
  52. def _DocToArgs(doc):
  53. """Converts a docstring documenting arguments into a dict."""
  54. m = None
  55. offset = None
  56. in_arg = False
  57. out = {}
  58. for l in doc.splitlines():
  59. if l.strip() == 'Args:':
  60. in_arg = True
  61. elif in_arg:
  62. if not l.strip():
  63. break
  64. if offset is None:
  65. offset = len(l) - len(l.lstrip())
  66. l = l[offset:]
  67. if l[0] == ' ' and m:
  68. out[m.group(1)] += ' ' + l.lstrip()
  69. else:
  70. m = re.match(r'^([a-z_]+): (.+)$', l.strip())
  71. out[m.group(1)] = m.group(2)
  72. return out
  73. def MakeSubparser(subparsers, parents, method, arguments=None):
  74. """Returns an argparse subparser to create a 'subcommand' to adb."""
  75. name = ('-'.join(re.split(r'([A-Z][a-z]+)', method.__name__)[1:-1:2])).lower()
  76. help = method.__doc__.splitlines()[0]
  77. subparser = subparsers.add_parser(
  78. name=name, description=help, help=help.rstrip('.'), parents=parents)
  79. subparser.set_defaults(method=method, positional=[])
  80. argspec = inspect.getargspec(method)
  81. # Figure out positionals and default argument, if any. Explicitly includes
  82. # arguments that default to '' but excludes arguments that default to None.
  83. offset = len(argspec.args) - len(argspec.defaults or []) - 1
  84. positional = []
  85. for i in range(1, len(argspec.args)):
  86. if i > offset and argspec.defaults[i - offset - 1] is None:
  87. break
  88. positional.append(argspec.args[i])
  89. defaults = [None] * offset + list(argspec.defaults or [])
  90. # Add all arguments so they append to args.positional.
  91. args_help = _DocToArgs(method.__doc__)
  92. for name, default in zip(positional, defaults):
  93. if not isinstance(default, (None.__class__, str)):
  94. continue
  95. subparser.add_argument(
  96. name, help=(arguments or {}).get(name, args_help.get(name)),
  97. default=default, nargs='?' if default is not None else None,
  98. action=PositionalArg)
  99. if argspec.varargs:
  100. subparser.add_argument(
  101. argspec.varargs, nargs=argparse.REMAINDER,
  102. help=(arguments or {}).get(argspec.varargs, args_help.get(argspec.varargs)))
  103. return subparser
  104. def _RunMethod(dev, args, extra):
  105. """Runs a method registered via MakeSubparser."""
  106. logging.info('%s(%s)', args.method.__name__, ', '.join(args.positional))
  107. result = args.method(dev, *args.positional, **extra)
  108. if result is not None:
  109. if isinstance(result, io.StringIO):
  110. sys.stdout.write(result.getvalue())
  111. elif isinstance(result, (list, types.GeneratorType)):
  112. r = ''
  113. for r in result:
  114. r = str(r)
  115. sys.stdout.write(r)
  116. if not r.endswith('\n'):
  117. sys.stdout.write('\n')
  118. else:
  119. result = str(result)
  120. sys.stdout.write(result)
  121. if not result.endswith('\n'):
  122. sys.stdout.write('\n')
  123. return 0
  124. def StartCli(args, adb_commands, extra=None, **device_kwargs):
  125. """Starts a common CLI interface for this usb path and protocol."""
  126. try:
  127. dev = adb_commands()
  128. dev.ConnectDevice(port_path=args.port_path, serial=args.serial, default_timeout_ms=args.timeout_ms,
  129. **device_kwargs)
  130. except usb_exceptions.DeviceNotFoundError as e:
  131. print('No device found: {}'.format(e), file=sys.stderr)
  132. return 1
  133. except usb_exceptions.CommonUsbError as e:
  134. print('Could not connect to device: {}'.format(e), file=sys.stderr)
  135. return 1
  136. try:
  137. return _RunMethod(dev, args, extra or {})
  138. except Exception as e: # pylint: disable=broad-except
  139. sys.stdout.write(str(e))
  140. return 1
  141. finally:
  142. dev.Close()