| #!/usr/bin/env python | |
| # Copyright 2017 the V8 project authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| # Runs an android build of d8 over adb, with any given arguments. Files | |
| # requested by d8 are transferred on-demand from the caller, by reverse port | |
| # forwarding a simple TCP file server from the computer to the android device. | |
| # | |
| # Usage: | |
| # adb-d8.py <build_dir> [<d8_args>...] | |
| # | |
| # Options: | |
| # <build_dir> The directory containing the android build of d8. | |
| # <d8_args>... The arguments passed through to d8. | |
| # | |
| # Run adb-d8.py --help for complete usage information. | |
| from __future__ import print_function | |
| import os | |
| import sys | |
| import struct | |
| import threading | |
| import subprocess | |
| import SocketServer # TODO(leszeks): python 3 compatibility | |
| def CreateFileHandlerClass(root_dirs, verbose): | |
| class FileHandler(SocketServer.BaseRequestHandler): | |
| def handle(self): | |
| data = self.request.recv(1024); | |
| while data[-1] != "\0": | |
| data += self.request.recv(1024); | |
| filename = data[0:-1] | |
| try: | |
| filename = os.path.abspath(filename) | |
| if not any(filename.startswith(root) for root in root_dirs): | |
| raise Exception("{} not in roots {}".format(filename, root_dirs)) | |
| if not os.path.isfile(filename): | |
| raise Exception("{} is not a file".format(filename)) | |
| if verbose: | |
| sys.stdout.write("Serving {}\r\n".format(os.path.relpath(filename))) | |
| with open(filename) as f: | |
| contents = f.read(); | |
| self.request.sendall(struct.pack("!i", len(contents))) | |
| self.request.sendall(contents) | |
| except Exception as e: | |
| if verbose: | |
| sys.stderr.write( | |
| "Request failed ({})\n".format(e).replace('\n','\r\n')) | |
| self.request.sendall(struct.pack("!i", -1)) | |
| return FileHandler | |
| def TransferD8ToDevice(adb, build_dir, device_d8_dir, verbose): | |
| files_to_copy = ["d8", "natives_blob.bin", "snapshot_blob.bin"] | |
| # Pipe the output of md5sum from the local computer to the device, checking | |
| # the md5 hashes on the device. | |
| local_md5_sum_proc = subprocess.Popen( | |
| ["md5sum"] + files_to_copy, | |
| cwd=build_dir, | |
| stdout=subprocess.PIPE | |
| ) | |
| device_md5_check_proc = subprocess.Popen( | |
| [ | |
| adb, "shell", | |
| "mkdir -p '{0}' ; cd '{0}' ; md5sum -c -".format(device_d8_dir) | |
| ], | |
| stdin=local_md5_sum_proc.stdout, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE | |
| ) | |
| # Push any files which failed the md5 check. | |
| (stdoutdata, stderrdata) = device_md5_check_proc.communicate() | |
| for line in stdoutdata.split('\n'): | |
| if line.endswith(": FAILED"): | |
| filename = line[:-len(": FAILED")] | |
| if verbose: | |
| print("Updating {}...".format(filename)) | |
| subprocess.check_call([ | |
| adb, "push", | |
| os.path.join(build_dir, filename), | |
| device_d8_dir | |
| ], stdout=sys.stdout if verbose else open(os.devnull, 'wb')) | |
| def AdbForwardDeviceToLocal(adb, device_port, server_port, verbose): | |
| if verbose: | |
| print("Forwarding device:{} to localhost:{}...".format( | |
| device_port, server_port)) | |
| subprocess.check_call([ | |
| adb, "reverse", | |
| "tcp:{}".format(device_port), | |
| "tcp:{}".format(server_port) | |
| ]) | |
| def AdbRunD8(adb, device_d8_dir, device_port, d8_args, verbose): | |
| # Single-quote the arguments to d8, and concatenate them into a string. | |
| d8_arg_str = " ".join("'{}'".format(a) for a in d8_args) | |
| d8_arg_str = "--read-from-tcp-port='{}' ".format(device_port) + d8_arg_str | |
| # Don't use os.path.join for d8 because we care about the device's os, not | |
| # the host os. | |
| d8_str = "{}/d8 {}".format(device_d8_dir, d8_arg_str) | |
| if sys.stdout.isatty(): | |
| # Run adb shell with -t to have a tty if we run d8 without a script. | |
| cmd = [adb, "shell", "-t", d8_str] | |
| else: | |
| cmd = [adb, "shell", d8_str] | |
| if verbose: | |
| print("Running {}".format(" ".join(cmd))) | |
| return subprocess.call(cmd) | |
| def PrintUsage(file=sys.stdout): | |
| print("Usage: adb-d8.py [-v|--verbose] [--] <build_dir> [<d8 args>...]", | |
| file=file) | |
| def PrintHelp(file=sys.stdout): | |
| print("""Usage: | |
| adb-d8.py [options] [--] <build_dir> [<d8_args>...] | |
| adb-d8.py -h|--help | |
| Options: | |
| -h|--help Show this help message and exit. | |
| -v|--verbose Print verbose output. | |
| --device-dir=DIR Specify which directory on the device should be used | |
| for the d8 binary. [default: /data/local/tmp/v8] | |
| --extra-root-dir=DIR In addition to the current directory, allow d8 to | |
| access files inside DIR. Multiple additional roots | |
| can be specified. | |
| <build_dir> The directory containing the android build of d8. | |
| <d8_args>... The arguments passed through to d8.""", file=file) | |
| def Main(): | |
| if len(sys.argv) < 2: | |
| PrintUsage(sys.stderr) | |
| return 1 | |
| script_dir = os.path.dirname(sys.argv[0]) | |
| # Use the platform-tools version of adb so that we know it has the reverse | |
| # command. | |
| adb = os.path.join( | |
| script_dir, | |
| "../third_party/android_tools/sdk/platform-tools/adb" | |
| ) | |
| # Read off any command line flags before build_dir (or --). Do this | |
| # manually, rather than using something like argparse, to be able to split | |
| # the adb-d8 options from the passthrough d8 options. | |
| verbose = False | |
| device_d8_dir = '/data/local/tmp/v8' | |
| root_dirs = [] | |
| arg_index = 1 | |
| while arg_index < len(sys.argv): | |
| arg = sys.argv[arg_index] | |
| if not arg.startswith("-"): | |
| break | |
| elif arg == "--": | |
| arg_index += 1 | |
| break | |
| elif arg == "-h" or arg == "--help": | |
| PrintHelp(sys.stdout) | |
| return 0 | |
| elif arg == "-v" or arg == "--verbose": | |
| verbose = True | |
| elif arg == "--device-dir": | |
| arg_index += 1 | |
| device_d8_dir = sys.argv[arg_index] | |
| elif arg.startswith("--device-dir="): | |
| device_d8_dir = arg[len("--device-dir="):] | |
| elif arg == "--extra-root-dir": | |
| arg_index += 1 | |
| root_dirs.append(sys.argv[arg_index]) | |
| elif arg.startswith("--extra-root-dir="): | |
| root_dirs.append(arg[len("--extra-root-dir="):]) | |
| else: | |
| print("ERROR: Unrecognised option: {}".format(arg)) | |
| PrintUsage(sys.stderr) | |
| return 1 | |
| arg_index += 1 | |
| # Transfer d8 (and dependencies) to the device. | |
| build_dir = os.path.abspath(sys.argv[arg_index]) | |
| TransferD8ToDevice(adb, build_dir, device_d8_dir, verbose) | |
| # Start a file server for the files d8 might need. | |
| script_root_dir = os.path.abspath(os.curdir) | |
| root_dirs.append(script_root_dir) | |
| server = SocketServer.TCPServer( | |
| ("localhost", 0), # 0 means an arbitrary unused port. | |
| CreateFileHandlerClass(root_dirs, verbose) | |
| ) | |
| try: | |
| # Start the file server in its own thread. | |
| server_thread = threading.Thread(target=server.serve_forever) | |
| server_thread.daemon = True | |
| server_thread.start() | |
| # Port-forward the given device port to the file server. | |
| # TODO(leszeks): Pick an unused device port. | |
| # TODO(leszeks): Remove the port forwarding on exit. | |
| server_ip, server_port = server.server_address | |
| device_port = 4444 | |
| AdbForwardDeviceToLocal(adb, device_port, server_port, verbose) | |
| # Run d8 over adb with the remaining arguments, using the given device | |
| # port to forward file reads. | |
| return AdbRunD8( | |
| adb, device_d8_dir, device_port, sys.argv[arg_index+1:], verbose) | |
| finally: | |
| if verbose: | |
| print("Shutting down file server...") | |
| server.shutdown() | |
| server.server_close() | |
| if __name__ == '__main__': | |
| sys.exit(Main()) |