| #!/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()) |