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