adb-d8.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #!/usr/bin/env python
  2. # Copyright 2017 the V8 project authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. # Runs an android build of d8 over adb, with any given arguments. Files
  6. # requested by d8 are transferred on-demand from the caller, by reverse port
  7. # forwarding a simple TCP file server from the computer to the android device.
  8. #
  9. # Usage:
  10. # adb-d8.py <build_dir> [<d8_args>...]
  11. #
  12. # Options:
  13. # <build_dir> The directory containing the android build of d8.
  14. # <d8_args>... The arguments passed through to d8.
  15. #
  16. # Run adb-d8.py --help for complete usage information.
  17. from __future__ import print_function
  18. import os
  19. import sys
  20. import struct
  21. import threading
  22. import subprocess
  23. import SocketServer # TODO(leszeks): python 3 compatibility
  24. def CreateFileHandlerClass(root_dirs, verbose):
  25. class FileHandler(SocketServer.BaseRequestHandler):
  26. def handle(self):
  27. data = self.request.recv(1024);
  28. while data[-1] != "\0":
  29. data += self.request.recv(1024);
  30. filename = data[0:-1]
  31. try:
  32. filename = os.path.abspath(filename)
  33. if not any(filename.startswith(root) for root in root_dirs):
  34. raise Exception("{} not in roots {}".format(filename, root_dirs))
  35. if not os.path.isfile(filename):
  36. raise Exception("{} is not a file".format(filename))
  37. if verbose:
  38. sys.stdout.write("Serving {}\r\n".format(os.path.relpath(filename)))
  39. with open(filename) as f:
  40. contents = f.read();
  41. self.request.sendall(struct.pack("!i", len(contents)))
  42. self.request.sendall(contents)
  43. except Exception as e:
  44. if verbose:
  45. sys.stderr.write(
  46. "Request failed ({})\n".format(e).replace('\n','\r\n'))
  47. self.request.sendall(struct.pack("!i", -1))
  48. return FileHandler
  49. def TransferD8ToDevice(adb, build_dir, device_d8_dir, verbose):
  50. files_to_copy = ["d8", "snapshot_blob.bin"]
  51. # Pipe the output of md5sum from the local computer to the device, checking
  52. # the md5 hashes on the device.
  53. local_md5_sum_proc = subprocess.Popen(
  54. ["md5sum"] + files_to_copy,
  55. cwd=build_dir,
  56. stdout=subprocess.PIPE
  57. )
  58. device_md5_check_proc = subprocess.Popen(
  59. [
  60. adb, "shell",
  61. "mkdir -p '{0}' ; cd '{0}' ; md5sum -c -".format(device_d8_dir)
  62. ],
  63. stdin=local_md5_sum_proc.stdout,
  64. stdout=subprocess.PIPE,
  65. stderr=subprocess.PIPE
  66. )
  67. # Push any files which failed the md5 check.
  68. (stdoutdata, stderrdata) = device_md5_check_proc.communicate()
  69. for line in stdoutdata.split('\n'):
  70. if line.endswith(": FAILED"):
  71. filename = line[:-len(": FAILED")]
  72. if verbose:
  73. print("Updating {}...".format(filename))
  74. subprocess.check_call([
  75. adb, "push",
  76. os.path.join(build_dir, filename),
  77. device_d8_dir
  78. ], stdout=sys.stdout if verbose else open(os.devnull, 'wb'))
  79. def AdbForwardDeviceToLocal(adb, device_port, server_port, verbose):
  80. if verbose:
  81. print("Forwarding device:{} to localhost:{}...".format(
  82. device_port, server_port))
  83. subprocess.check_call([
  84. adb, "reverse",
  85. "tcp:{}".format(device_port),
  86. "tcp:{}".format(server_port)
  87. ])
  88. def AdbRunD8(adb, device_d8_dir, device_port, d8_args, verbose):
  89. # Single-quote the arguments to d8, and concatenate them into a string.
  90. d8_arg_str = " ".join("'{}'".format(a) for a in d8_args)
  91. d8_arg_str = "--read-from-tcp-port='{}' ".format(device_port) + d8_arg_str
  92. # Don't use os.path.join for d8 because we care about the device's os, not
  93. # the host os.
  94. d8_str = "{}/d8 {}".format(device_d8_dir, d8_arg_str)
  95. if sys.stdout.isatty():
  96. # Run adb shell with -t to have a tty if we run d8 without a script.
  97. cmd = [adb, "shell", "-t", d8_str]
  98. else:
  99. cmd = [adb, "shell", d8_str]
  100. if verbose:
  101. print("Running {}".format(" ".join(cmd)))
  102. return subprocess.call(cmd)
  103. def PrintUsage(file=sys.stdout):
  104. print("Usage: adb-d8.py [-v|--verbose] [--] <build_dir> [<d8 args>...]",
  105. file=file)
  106. def PrintHelp(file=sys.stdout):
  107. print("""Usage:
  108. adb-d8.py [options] [--] <build_dir> [<d8_args>...]
  109. adb-d8.py -h|--help
  110. Options:
  111. -h|--help Show this help message and exit.
  112. -v|--verbose Print verbose output.
  113. --device-dir=DIR Specify which directory on the device should be used
  114. for the d8 binary. [default: /data/local/tmp/v8]
  115. --extra-root-dir=DIR In addition to the current directory, allow d8 to
  116. access files inside DIR. Multiple additional roots
  117. can be specified.
  118. <build_dir> The directory containing the android build of d8.
  119. <d8_args>... The arguments passed through to d8.""", file=file)
  120. def Main():
  121. if len(sys.argv) < 2:
  122. PrintUsage(sys.stderr)
  123. return 1
  124. script_dir = os.path.dirname(sys.argv[0])
  125. # Use the platform-tools version of adb so that we know it has the reverse
  126. # command.
  127. adb = os.path.join(
  128. script_dir,
  129. "../third_party/android_sdk/public/platform-tools/adb"
  130. )
  131. # Read off any command line flags before build_dir (or --). Do this
  132. # manually, rather than using something like argparse, to be able to split
  133. # the adb-d8 options from the passthrough d8 options.
  134. verbose = False
  135. device_d8_dir = '/data/local/tmp/v8'
  136. root_dirs = []
  137. arg_index = 1
  138. while arg_index < len(sys.argv):
  139. arg = sys.argv[arg_index]
  140. if not arg.startswith("-"):
  141. break
  142. elif arg == "--":
  143. arg_index += 1
  144. break
  145. elif arg == "-h" or arg == "--help":
  146. PrintHelp(sys.stdout)
  147. return 0
  148. elif arg == "-v" or arg == "--verbose":
  149. verbose = True
  150. elif arg == "--device-dir":
  151. arg_index += 1
  152. device_d8_dir = sys.argv[arg_index]
  153. elif arg.startswith("--device-dir="):
  154. device_d8_dir = arg[len("--device-dir="):]
  155. elif arg == "--extra-root-dir":
  156. arg_index += 1
  157. root_dirs.append(sys.argv[arg_index])
  158. elif arg.startswith("--extra-root-dir="):
  159. root_dirs.append(arg[len("--extra-root-dir="):])
  160. else:
  161. print("ERROR: Unrecognised option: {}".format(arg))
  162. PrintUsage(sys.stderr)
  163. return 1
  164. arg_index += 1
  165. # Transfer d8 (and dependencies) to the device.
  166. build_dir = os.path.abspath(sys.argv[arg_index])
  167. TransferD8ToDevice(adb, build_dir, device_d8_dir, verbose)
  168. # Start a file server for the files d8 might need.
  169. script_root_dir = os.path.abspath(os.curdir)
  170. root_dirs.append(script_root_dir)
  171. server = SocketServer.TCPServer(
  172. ("localhost", 0), # 0 means an arbitrary unused port.
  173. CreateFileHandlerClass(root_dirs, verbose)
  174. )
  175. try:
  176. # Start the file server in its own thread.
  177. server_thread = threading.Thread(target=server.serve_forever)
  178. server_thread.daemon = True
  179. server_thread.start()
  180. # Port-forward the given device port to the file server.
  181. # TODO(leszeks): Pick an unused device port.
  182. # TODO(leszeks): Remove the port forwarding on exit.
  183. server_ip, server_port = server.server_address
  184. device_port = 4444
  185. AdbForwardDeviceToLocal(adb, device_port, server_port, verbose)
  186. # Run d8 over adb with the remaining arguments, using the given device
  187. # port to forward file reads.
  188. return AdbRunD8(
  189. adb, device_d8_dir, device_port, sys.argv[arg_index+1:], verbose)
  190. finally:
  191. if verbose:
  192. print("Shutting down file server...")
  193. server.shutdown()
  194. server.server_close()
  195. if __name__ == '__main__':
  196. sys.exit(Main())