File: adb-d8.py

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (241 lines) | stat: -rwxr-xr-x 7,576 bytes parent folder | download | duplicates (14)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#!/usr/bin/env python3

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