1*60517a1eSAndroid Build Coastguard Worker# Copyright 2024 The Bazel Authors. All rights reserved. 2*60517a1eSAndroid Build Coastguard Worker# 3*60517a1eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*60517a1eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*60517a1eSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*60517a1eSAndroid Build Coastguard Worker# 7*60517a1eSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*60517a1eSAndroid Build Coastguard Worker# 9*60517a1eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*60517a1eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*60517a1eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*60517a1eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*60517a1eSAndroid Build Coastguard Worker# limitations under the License. 14*60517a1eSAndroid Build Coastguard Worker 15*60517a1eSAndroid Build Coastguard Worker"""A simple precompiler to generate deterministic pyc files for Bazel.""" 16*60517a1eSAndroid Build Coastguard Worker 17*60517a1eSAndroid Build Coastguard Worker# NOTE: Imports specific to the persistent worker should only be imported 18*60517a1eSAndroid Build Coastguard Worker# when a persistent worker is used. Avoiding the unnecessary imports 19*60517a1eSAndroid Build Coastguard Worker# saves significant startup time for non-worker invocations. 20*60517a1eSAndroid Build Coastguard Workerimport argparse 21*60517a1eSAndroid Build Coastguard Workerimport py_compile 22*60517a1eSAndroid Build Coastguard Workerimport sys 23*60517a1eSAndroid Build Coastguard Worker 24*60517a1eSAndroid Build Coastguard Worker 25*60517a1eSAndroid Build Coastguard Workerdef _create_parser() -> "argparse.Namespace": 26*60517a1eSAndroid Build Coastguard Worker parser = argparse.ArgumentParser(fromfile_prefix_chars="@") 27*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--invalidation_mode", default="CHECKED_HASH") 28*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--optimize", type=int, default=-1) 29*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--python_version") 30*60517a1eSAndroid Build Coastguard Worker 31*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--src", action="append", dest="srcs") 32*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--src_name", action="append", dest="src_names") 33*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--pyc", action="append", dest="pycs") 34*60517a1eSAndroid Build Coastguard Worker 35*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--persistent_worker", action="store_true") 36*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--log_level", default="ERROR") 37*60517a1eSAndroid Build Coastguard Worker parser.add_argument("--worker_impl", default="async") 38*60517a1eSAndroid Build Coastguard Worker return parser 39*60517a1eSAndroid Build Coastguard Worker 40*60517a1eSAndroid Build Coastguard Worker 41*60517a1eSAndroid Build Coastguard Workerdef _compile(options: "argparse.Namespace") -> None: 42*60517a1eSAndroid Build Coastguard Worker try: 43*60517a1eSAndroid Build Coastguard Worker invalidation_mode = py_compile.PycInvalidationMode[ 44*60517a1eSAndroid Build Coastguard Worker options.invalidation_mode.upper() 45*60517a1eSAndroid Build Coastguard Worker ] 46*60517a1eSAndroid Build Coastguard Worker except KeyError as e: 47*60517a1eSAndroid Build Coastguard Worker raise ValueError( 48*60517a1eSAndroid Build Coastguard Worker f"Unknown PycInvalidationMode: {options.invalidation_mode}" 49*60517a1eSAndroid Build Coastguard Worker ) from e 50*60517a1eSAndroid Build Coastguard Worker 51*60517a1eSAndroid Build Coastguard Worker if not (len(options.srcs) == len(options.src_names) == len(options.pycs)): 52*60517a1eSAndroid Build Coastguard Worker raise AssertionError( 53*60517a1eSAndroid Build Coastguard Worker "Mismatched number of --src, --src_name, and/or --pyc args" 54*60517a1eSAndroid Build Coastguard Worker ) 55*60517a1eSAndroid Build Coastguard Worker 56*60517a1eSAndroid Build Coastguard Worker for src, src_name, pyc in zip(options.srcs, options.src_names, options.pycs): 57*60517a1eSAndroid Build Coastguard Worker py_compile.compile( 58*60517a1eSAndroid Build Coastguard Worker src, 59*60517a1eSAndroid Build Coastguard Worker pyc, 60*60517a1eSAndroid Build Coastguard Worker doraise=True, 61*60517a1eSAndroid Build Coastguard Worker dfile=src_name, 62*60517a1eSAndroid Build Coastguard Worker optimize=options.optimize, 63*60517a1eSAndroid Build Coastguard Worker invalidation_mode=invalidation_mode, 64*60517a1eSAndroid Build Coastguard Worker ) 65*60517a1eSAndroid Build Coastguard Worker return 0 66*60517a1eSAndroid Build Coastguard Worker 67*60517a1eSAndroid Build Coastguard Worker 68*60517a1eSAndroid Build Coastguard Worker# A stub type alias for readability. 69*60517a1eSAndroid Build Coastguard Worker# See the Bazel WorkRequest object definition: 70*60517a1eSAndroid Build Coastguard Worker# https://github.com/bazelbuild/bazel/blob/master/src/main/protobuf/worker_protocol.proto 71*60517a1eSAndroid Build Coastguard WorkerJsonWorkerRequest = object 72*60517a1eSAndroid Build Coastguard Worker 73*60517a1eSAndroid Build Coastguard Worker# A stub type alias for readability. 74*60517a1eSAndroid Build Coastguard Worker# See the Bazel WorkResponse object definition: 75*60517a1eSAndroid Build Coastguard Worker# https://github.com/bazelbuild/bazel/blob/master/src/main/protobuf/worker_protocol.proto 76*60517a1eSAndroid Build Coastguard WorkerJsonWorkerResponse = object 77*60517a1eSAndroid Build Coastguard Worker 78*60517a1eSAndroid Build Coastguard Worker 79*60517a1eSAndroid Build Coastguard Workerclass _SerialPersistentWorker: 80*60517a1eSAndroid Build Coastguard Worker """Simple, synchronous, serial persistent worker.""" 81*60517a1eSAndroid Build Coastguard Worker 82*60517a1eSAndroid Build Coastguard Worker def __init__(self, instream: "typing.TextIO", outstream: "typing.TextIO"): 83*60517a1eSAndroid Build Coastguard Worker self._instream = instream 84*60517a1eSAndroid Build Coastguard Worker self._outstream = outstream 85*60517a1eSAndroid Build Coastguard Worker self._parser = _create_parser() 86*60517a1eSAndroid Build Coastguard Worker 87*60517a1eSAndroid Build Coastguard Worker def run(self) -> None: 88*60517a1eSAndroid Build Coastguard Worker try: 89*60517a1eSAndroid Build Coastguard Worker while True: 90*60517a1eSAndroid Build Coastguard Worker request = None 91*60517a1eSAndroid Build Coastguard Worker try: 92*60517a1eSAndroid Build Coastguard Worker request = self._get_next_request() 93*60517a1eSAndroid Build Coastguard Worker if request is None: 94*60517a1eSAndroid Build Coastguard Worker _logger.info("Empty request: exiting") 95*60517a1eSAndroid Build Coastguard Worker break 96*60517a1eSAndroid Build Coastguard Worker response = self._process_request(request) 97*60517a1eSAndroid Build Coastguard Worker if response: # May be none for cancel request 98*60517a1eSAndroid Build Coastguard Worker self._send_response(response) 99*60517a1eSAndroid Build Coastguard Worker except Exception: 100*60517a1eSAndroid Build Coastguard Worker _logger.exception("Unhandled error: request=%s", request) 101*60517a1eSAndroid Build Coastguard Worker output = ( 102*60517a1eSAndroid Build Coastguard Worker f"Unhandled error:\nRequest: {request}\n" 103*60517a1eSAndroid Build Coastguard Worker + traceback.format_exc() 104*60517a1eSAndroid Build Coastguard Worker ) 105*60517a1eSAndroid Build Coastguard Worker request_id = 0 if not request else request.get("requestId", 0) 106*60517a1eSAndroid Build Coastguard Worker self._send_response( 107*60517a1eSAndroid Build Coastguard Worker { 108*60517a1eSAndroid Build Coastguard Worker "exitCode": 3, 109*60517a1eSAndroid Build Coastguard Worker "output": output, 110*60517a1eSAndroid Build Coastguard Worker "requestId": request_id, 111*60517a1eSAndroid Build Coastguard Worker } 112*60517a1eSAndroid Build Coastguard Worker ) 113*60517a1eSAndroid Build Coastguard Worker finally: 114*60517a1eSAndroid Build Coastguard Worker _logger.info("Worker shutting down") 115*60517a1eSAndroid Build Coastguard Worker 116*60517a1eSAndroid Build Coastguard Worker def _get_next_request(self) -> "object | None": 117*60517a1eSAndroid Build Coastguard Worker line = self._instream.readline() 118*60517a1eSAndroid Build Coastguard Worker if not line: 119*60517a1eSAndroid Build Coastguard Worker return None 120*60517a1eSAndroid Build Coastguard Worker return json.loads(line) 121*60517a1eSAndroid Build Coastguard Worker 122*60517a1eSAndroid Build Coastguard Worker def _process_request(self, request: "JsonWorkRequest") -> "JsonWorkResponse | None": 123*60517a1eSAndroid Build Coastguard Worker if request.get("cancel"): 124*60517a1eSAndroid Build Coastguard Worker return None 125*60517a1eSAndroid Build Coastguard Worker options = self._options_from_request(request) 126*60517a1eSAndroid Build Coastguard Worker _compile(options) 127*60517a1eSAndroid Build Coastguard Worker response = { 128*60517a1eSAndroid Build Coastguard Worker "requestId": request.get("requestId", 0), 129*60517a1eSAndroid Build Coastguard Worker "exitCode": 0, 130*60517a1eSAndroid Build Coastguard Worker } 131*60517a1eSAndroid Build Coastguard Worker return response 132*60517a1eSAndroid Build Coastguard Worker 133*60517a1eSAndroid Build Coastguard Worker def _options_from_request( 134*60517a1eSAndroid Build Coastguard Worker self, request: "JsonWorkResponse" 135*60517a1eSAndroid Build Coastguard Worker ) -> "argparse.Namespace": 136*60517a1eSAndroid Build Coastguard Worker options = self._parser.parse_args(request["arguments"]) 137*60517a1eSAndroid Build Coastguard Worker if request.get("sandboxDir"): 138*60517a1eSAndroid Build Coastguard Worker prefix = request["sandboxDir"] 139*60517a1eSAndroid Build Coastguard Worker options.srcs = [os.path.join(prefix, v) for v in options.srcs] 140*60517a1eSAndroid Build Coastguard Worker options.pycs = [os.path.join(prefix, v) for v in options.pycs] 141*60517a1eSAndroid Build Coastguard Worker return options 142*60517a1eSAndroid Build Coastguard Worker 143*60517a1eSAndroid Build Coastguard Worker def _send_response(self, response: "JsonWorkResponse") -> None: 144*60517a1eSAndroid Build Coastguard Worker self._outstream.write(json.dumps(response) + "\n") 145*60517a1eSAndroid Build Coastguard Worker self._outstream.flush() 146*60517a1eSAndroid Build Coastguard Worker 147*60517a1eSAndroid Build Coastguard Worker 148*60517a1eSAndroid Build Coastguard Workerclass _AsyncPersistentWorker: 149*60517a1eSAndroid Build Coastguard Worker """Asynchronous, concurrent, persistent worker.""" 150*60517a1eSAndroid Build Coastguard Worker 151*60517a1eSAndroid Build Coastguard Worker def __init__(self, reader: "typing.TextIO", writer: "typing.TextIO"): 152*60517a1eSAndroid Build Coastguard Worker self._reader = reader 153*60517a1eSAndroid Build Coastguard Worker self._writer = writer 154*60517a1eSAndroid Build Coastguard Worker self._parser = _create_parser() 155*60517a1eSAndroid Build Coastguard Worker self._request_id_to_task = {} 156*60517a1eSAndroid Build Coastguard Worker self._task_to_request_id = {} 157*60517a1eSAndroid Build Coastguard Worker 158*60517a1eSAndroid Build Coastguard Worker @classmethod 159*60517a1eSAndroid Build Coastguard Worker async def main(cls, instream: "typing.TextIO", outstream: "typing.TextIO") -> None: 160*60517a1eSAndroid Build Coastguard Worker reader, writer = await cls._connect_streams(instream, outstream) 161*60517a1eSAndroid Build Coastguard Worker await cls(reader, writer).run() 162*60517a1eSAndroid Build Coastguard Worker 163*60517a1eSAndroid Build Coastguard Worker @classmethod 164*60517a1eSAndroid Build Coastguard Worker async def _connect_streams( 165*60517a1eSAndroid Build Coastguard Worker cls, instream: "typing.TextIO", outstream: "typing.TextIO" 166*60517a1eSAndroid Build Coastguard Worker ) -> "tuple[asyncio.StreamReader, asyncio.StreamWriter]": 167*60517a1eSAndroid Build Coastguard Worker loop = asyncio.get_event_loop() 168*60517a1eSAndroid Build Coastguard Worker reader = asyncio.StreamReader() 169*60517a1eSAndroid Build Coastguard Worker protocol = asyncio.StreamReaderProtocol(reader) 170*60517a1eSAndroid Build Coastguard Worker await loop.connect_read_pipe(lambda: protocol, instream) 171*60517a1eSAndroid Build Coastguard Worker 172*60517a1eSAndroid Build Coastguard Worker w_transport, w_protocol = await loop.connect_write_pipe( 173*60517a1eSAndroid Build Coastguard Worker asyncio.streams.FlowControlMixin, outstream 174*60517a1eSAndroid Build Coastguard Worker ) 175*60517a1eSAndroid Build Coastguard Worker writer = asyncio.StreamWriter(w_transport, w_protocol, reader, loop) 176*60517a1eSAndroid Build Coastguard Worker return reader, writer 177*60517a1eSAndroid Build Coastguard Worker 178*60517a1eSAndroid Build Coastguard Worker async def run(self) -> None: 179*60517a1eSAndroid Build Coastguard Worker while True: 180*60517a1eSAndroid Build Coastguard Worker _logger.info("pending requests: %s", len(self._request_id_to_task)) 181*60517a1eSAndroid Build Coastguard Worker request = await self._get_next_request() 182*60517a1eSAndroid Build Coastguard Worker request_id = request.get("requestId", 0) 183*60517a1eSAndroid Build Coastguard Worker task = asyncio.create_task( 184*60517a1eSAndroid Build Coastguard Worker self._process_request(request), name=f"request_{request_id}" 185*60517a1eSAndroid Build Coastguard Worker ) 186*60517a1eSAndroid Build Coastguard Worker self._request_id_to_task[request_id] = task 187*60517a1eSAndroid Build Coastguard Worker self._task_to_request_id[task] = request_id 188*60517a1eSAndroid Build Coastguard Worker task.add_done_callback(self._handle_task_done) 189*60517a1eSAndroid Build Coastguard Worker 190*60517a1eSAndroid Build Coastguard Worker async def _get_next_request(self) -> "JsonWorkRequest": 191*60517a1eSAndroid Build Coastguard Worker _logger.debug("awaiting line") 192*60517a1eSAndroid Build Coastguard Worker line = await self._reader.readline() 193*60517a1eSAndroid Build Coastguard Worker _logger.debug("recv line: %s", line) 194*60517a1eSAndroid Build Coastguard Worker return json.loads(line) 195*60517a1eSAndroid Build Coastguard Worker 196*60517a1eSAndroid Build Coastguard Worker def _handle_task_done(self, task: "asyncio.Task") -> None: 197*60517a1eSAndroid Build Coastguard Worker request_id = self._task_to_request_id[task] 198*60517a1eSAndroid Build Coastguard Worker _logger.info("task done: %s %s", request_id, task) 199*60517a1eSAndroid Build Coastguard Worker del self._task_to_request_id[task] 200*60517a1eSAndroid Build Coastguard Worker del self._request_id_to_task[request_id] 201*60517a1eSAndroid Build Coastguard Worker 202*60517a1eSAndroid Build Coastguard Worker async def _process_request(self, request: "JsonWorkRequest") -> None: 203*60517a1eSAndroid Build Coastguard Worker _logger.info("request %s: start: %s", request.get("requestId"), request) 204*60517a1eSAndroid Build Coastguard Worker try: 205*60517a1eSAndroid Build Coastguard Worker if request.get("cancel", False): 206*60517a1eSAndroid Build Coastguard Worker await self._process_cancel_request(request) 207*60517a1eSAndroid Build Coastguard Worker else: 208*60517a1eSAndroid Build Coastguard Worker await self._process_compile_request(request) 209*60517a1eSAndroid Build Coastguard Worker except asyncio.CancelledError: 210*60517a1eSAndroid Build Coastguard Worker _logger.info( 211*60517a1eSAndroid Build Coastguard Worker "request %s: cancel received, stopping processing", 212*60517a1eSAndroid Build Coastguard Worker request.get("requestId"), 213*60517a1eSAndroid Build Coastguard Worker ) 214*60517a1eSAndroid Build Coastguard Worker # We don't send a response because we assume the request that 215*60517a1eSAndroid Build Coastguard Worker # triggered cancelling sent the response 216*60517a1eSAndroid Build Coastguard Worker raise 217*60517a1eSAndroid Build Coastguard Worker except: 218*60517a1eSAndroid Build Coastguard Worker _logger.exception("Unhandled error: request=%s", request) 219*60517a1eSAndroid Build Coastguard Worker self._send_response( 220*60517a1eSAndroid Build Coastguard Worker { 221*60517a1eSAndroid Build Coastguard Worker "exitCode": 3, 222*60517a1eSAndroid Build Coastguard Worker "output": f"Unhandled error:\nRequest: {request}\n" 223*60517a1eSAndroid Build Coastguard Worker + traceback.format_exc(), 224*60517a1eSAndroid Build Coastguard Worker "requestId": 0 if not request else request.get("requestId", 0), 225*60517a1eSAndroid Build Coastguard Worker } 226*60517a1eSAndroid Build Coastguard Worker ) 227*60517a1eSAndroid Build Coastguard Worker 228*60517a1eSAndroid Build Coastguard Worker async def _process_cancel_request(self, request: "JsonWorkRequest") -> None: 229*60517a1eSAndroid Build Coastguard Worker request_id = request.get("requestId", 0) 230*60517a1eSAndroid Build Coastguard Worker task = self._request_id_to_task.get(request_id) 231*60517a1eSAndroid Build Coastguard Worker if not task: 232*60517a1eSAndroid Build Coastguard Worker # It must be already completed, so ignore the request, per spec 233*60517a1eSAndroid Build Coastguard Worker return 234*60517a1eSAndroid Build Coastguard Worker 235*60517a1eSAndroid Build Coastguard Worker task.cancel() 236*60517a1eSAndroid Build Coastguard Worker self._send_response({"requestId": request_id, "wasCancelled": True}) 237*60517a1eSAndroid Build Coastguard Worker 238*60517a1eSAndroid Build Coastguard Worker async def _process_compile_request(self, request: "JsonWorkRequest") -> None: 239*60517a1eSAndroid Build Coastguard Worker options = self._options_from_request(request) 240*60517a1eSAndroid Build Coastguard Worker # _compile performs a varity of blocking IO calls, so run it separately 241*60517a1eSAndroid Build Coastguard Worker await asyncio.to_thread(_compile, options) 242*60517a1eSAndroid Build Coastguard Worker self._send_response( 243*60517a1eSAndroid Build Coastguard Worker { 244*60517a1eSAndroid Build Coastguard Worker "requestId": request.get("requestId", 0), 245*60517a1eSAndroid Build Coastguard Worker "exitCode": 0, 246*60517a1eSAndroid Build Coastguard Worker } 247*60517a1eSAndroid Build Coastguard Worker ) 248*60517a1eSAndroid Build Coastguard Worker 249*60517a1eSAndroid Build Coastguard Worker def _options_from_request(self, request: "JsonWorkRequest") -> "argparse.Namespace": 250*60517a1eSAndroid Build Coastguard Worker options = self._parser.parse_args(request["arguments"]) 251*60517a1eSAndroid Build Coastguard Worker if request.get("sandboxDir"): 252*60517a1eSAndroid Build Coastguard Worker prefix = request["sandboxDir"] 253*60517a1eSAndroid Build Coastguard Worker options.srcs = [os.path.join(prefix, v) for v in options.srcs] 254*60517a1eSAndroid Build Coastguard Worker options.pycs = [os.path.join(prefix, v) for v in options.pycs] 255*60517a1eSAndroid Build Coastguard Worker return options 256*60517a1eSAndroid Build Coastguard Worker 257*60517a1eSAndroid Build Coastguard Worker def _send_response(self, response: "JsonWorkResponse") -> None: 258*60517a1eSAndroid Build Coastguard Worker _logger.info("request %s: respond: %s", response.get("requestId"), response) 259*60517a1eSAndroid Build Coastguard Worker self._writer.write(json.dumps(response).encode("utf8") + b"\n") 260*60517a1eSAndroid Build Coastguard Worker 261*60517a1eSAndroid Build Coastguard Worker 262*60517a1eSAndroid Build Coastguard Workerdef main(args: "list[str]") -> int: 263*60517a1eSAndroid Build Coastguard Worker options = _create_parser().parse_args(args) 264*60517a1eSAndroid Build Coastguard Worker 265*60517a1eSAndroid Build Coastguard Worker # Persistent workers are started with the `--persistent_worker` flag. 266*60517a1eSAndroid Build Coastguard Worker # See the following docs for details on persistent workers: 267*60517a1eSAndroid Build Coastguard Worker # https://bazel.build/remote/persistent 268*60517a1eSAndroid Build Coastguard Worker # https://bazel.build/remote/multiplex 269*60517a1eSAndroid Build Coastguard Worker # https://bazel.build/remote/creating 270*60517a1eSAndroid Build Coastguard Worker if options.persistent_worker: 271*60517a1eSAndroid Build Coastguard Worker global asyncio, itertools, json, logging, os, traceback, _logger 272*60517a1eSAndroid Build Coastguard Worker import asyncio 273*60517a1eSAndroid Build Coastguard Worker import itertools 274*60517a1eSAndroid Build Coastguard Worker import json 275*60517a1eSAndroid Build Coastguard Worker import logging 276*60517a1eSAndroid Build Coastguard Worker import os.path 277*60517a1eSAndroid Build Coastguard Worker import traceback 278*60517a1eSAndroid Build Coastguard Worker 279*60517a1eSAndroid Build Coastguard Worker _logger = logging.getLogger("precompiler") 280*60517a1eSAndroid Build Coastguard Worker # Only configure logging for workers. This prevents non-worker 281*60517a1eSAndroid Build Coastguard Worker # invocations from spamming stderr with logging info 282*60517a1eSAndroid Build Coastguard Worker logging.basicConfig(level=getattr(logging, options.log_level)) 283*60517a1eSAndroid Build Coastguard Worker _logger.info("persistent worker: impl=%s", options.worker_impl) 284*60517a1eSAndroid Build Coastguard Worker if options.worker_impl == "serial": 285*60517a1eSAndroid Build Coastguard Worker _SerialPersistentWorker(sys.stdin, sys.stdout).run() 286*60517a1eSAndroid Build Coastguard Worker elif options.worker_impl == "async": 287*60517a1eSAndroid Build Coastguard Worker asyncio.run(_AsyncPersistentWorker.main(sys.stdin, sys.stdout)) 288*60517a1eSAndroid Build Coastguard Worker else: 289*60517a1eSAndroid Build Coastguard Worker raise ValueError(f"Unknown worker impl: {options.worker_impl}") 290*60517a1eSAndroid Build Coastguard Worker else: 291*60517a1eSAndroid Build Coastguard Worker _compile(options) 292*60517a1eSAndroid Build Coastguard Worker return 0 293*60517a1eSAndroid Build Coastguard Worker 294*60517a1eSAndroid Build Coastguard Worker 295*60517a1eSAndroid Build Coastguard Workerif __name__ == "__main__": 296*60517a1eSAndroid Build Coastguard Worker sys.exit(main(sys.argv[1:])) 297