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