1# Copyright 2021 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import contextlib 6import json 7import os 8import pathlib 9import socket 10import platform 11import sys 12import struct 13 14sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..')) 15from util import build_utils 16 17# Use a unix abstract domain socket: 18# https://man7.org/linux/man-pages/man7/unix.7.html#:~:text=abstract: 19SOCKET_ADDRESS = '\0chromium_build_server_socket' 20BUILD_SERVER_ENV_VARIABLE = 'INVOKED_BY_BUILD_SERVER' 21 22ADD_TASK = 'add_task' 23QUERY_BUILD = 'query_build' 24POLL_HEARTBEAT = 'poll_heartbeat' 25REGISTER_BUILDER = 'register_builder' 26CANCEL_BUILD = 'cancel_build' 27 28SERVER_SCRIPT = pathlib.Path( 29 build_utils.DIR_SOURCE_ROOT 30) / 'build' / 'android' / 'fast_local_dev_server.py' 31 32 33def AssertEnvironmentVariables(): 34 assert os.environ.get('AUTONINJA_BUILD_ID') 35 assert os.environ.get('AUTONINJA_STDOUT_NAME') 36 37 38def MaybeRunCommand(name, argv, stamp_file, use_build_server=False): 39 """Returns True if the command was successfully sent to the build server.""" 40 if not use_build_server or platform.system() == 'Darwin': 41 # Build server does not support Mac. 42 return False 43 44 # When the build server runs a command, it sets this environment variable. 45 # This prevents infinite recursion where the script sends a request to the 46 # build server, then the build server runs the script, and then the script 47 # sends another request to the build server. 48 if BUILD_SERVER_ENV_VARIABLE in os.environ: 49 return False 50 51 build_id = os.environ.get('AUTONINJA_BUILD_ID') 52 if not build_id: 53 raise Exception( 54 'AUTONINJA_BUILD_ID is not set. Should have been set by autoninja.') 55 stdout_name = os.environ.get('AUTONINJA_STDOUT_NAME') 56 # If we get a bad tty (happens when autoninja is not run from the terminal 57 # directly but as part of another script), ignore the build server and build 58 # normally since the build server will not know where to output to otherwise. 59 if not stdout_name or not os.path.exists(stdout_name): 60 return False 61 62 with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock: 63 try: 64 sock.connect(SOCKET_ADDRESS) 65 except socket.error as e: 66 # [Errno 111] Connection refused. Either the server has not been started 67 # or the server is not currently accepting new connections. 68 if e.errno == 111: 69 raise RuntimeError( 70 '\n\nBuild server is not running and ' 71 'android_static_analysis="build_server" is set.\n\n') from None 72 raise e 73 74 SendMessage( 75 sock, { 76 'name': name, 77 'message_type': ADD_TASK, 78 'cmd': [sys.executable] + argv, 79 'cwd': os.getcwd(), 80 'build_id': build_id, 81 'stamp_file': stamp_file, 82 }) 83 84 # Siso needs the stamp file to be created in order for the build step to 85 # complete. If the task fails when the build server runs it, the build server 86 # will delete the stamp file so that it will be run again next build. 87 build_utils.Touch(stamp_file) 88 return True 89 90 91def MaybeTouch(stamp_file): 92 """Touch |stamp_file| if we are not running under the build_server.""" 93 # If we are running under the build server, the stamp file has already been 94 # touched when the task was created. If we touch it again, siso will consider 95 # the target dirty. 96 if BUILD_SERVER_ENV_VARIABLE in os.environ: 97 return 98 build_utils.Touch(stamp_file) 99 100 101def SendMessage(sock: socket.socket, message: dict): 102 data = json.dumps(message).encode('utf-8') 103 size_prefix = struct.pack('!i', len(data)) 104 sock.sendall(size_prefix + data) 105 106 107def ReceiveMessage(sock: socket.socket) -> dict: 108 size_prefix = b'' 109 remaining = 4 # sizeof(int) 110 while remaining > 0: 111 data = sock.recv(remaining) 112 if not data: 113 return None 114 remaining -= len(data) 115 size_prefix += data 116 remaining, = struct.unpack('!i', size_prefix) 117 received = [] 118 while remaining > 0: 119 data = sock.recv(remaining) 120 if not data: 121 break 122 received.append(data) 123 remaining -= len(data) 124 if received: 125 return json.loads(b''.join(received)) 126 return None 127