123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- #!/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", "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_sdk/public/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())
|