# Copyright 2021 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import contextlib import json import os import pathlib import socket import platform import sys import struct sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..')) from util import build_utils # Use a unix abstract domain socket: # https://man7.org/linux/man-pages/man7/unix.7.html#:~:text=abstract: SOCKET_ADDRESS = '\0chromium_build_server_socket' BUILD_SERVER_ENV_VARIABLE = 'INVOKED_BY_BUILD_SERVER' ADD_TASK = 'add_task' QUERY_BUILD = 'query_build' POLL_HEARTBEAT = 'poll_heartbeat' REGISTER_BUILDER = 'register_builder' CANCEL_BUILD = 'cancel_build' SERVER_SCRIPT = pathlib.Path( build_utils.DIR_SOURCE_ROOT ) / 'build' / 'android' / 'fast_local_dev_server.py' def AssertEnvironmentVariables(): assert os.environ.get('AUTONINJA_BUILD_ID') assert os.environ.get('AUTONINJA_STDOUT_NAME') def MaybeRunCommand(name, argv, stamp_file, use_build_server=False): """Returns True if the command was successfully sent to the build server.""" if not use_build_server or platform.system() == 'Darwin': # Build server does not support Mac. return False # When the build server runs a command, it sets this environment variable. # This prevents infinite recursion where the script sends a request to the # build server, then the build server runs the script, and then the script # sends another request to the build server. if BUILD_SERVER_ENV_VARIABLE in os.environ: return False build_id = os.environ.get('AUTONINJA_BUILD_ID') if not build_id: raise Exception( 'AUTONINJA_BUILD_ID is not set. Should have been set by autoninja.') stdout_name = os.environ.get('AUTONINJA_STDOUT_NAME') # If we get a bad tty (happens when autoninja is not run from the terminal # directly but as part of another script), ignore the build server and build # normally since the build server will not know where to output to otherwise. if not stdout_name or not os.path.exists(stdout_name): return False with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock: try: sock.connect(SOCKET_ADDRESS) except socket.error as e: # [Errno 111] Connection refused. Either the server has not been started # or the server is not currently accepting new connections. if e.errno == 111: raise RuntimeError( '\n\nBuild server is not running and ' 'android_static_analysis="build_server" is set.\n\n') from None raise e SendMessage( sock, { 'name': name, 'message_type': ADD_TASK, 'cmd': [sys.executable] + argv, 'cwd': os.getcwd(), 'build_id': build_id, 'stamp_file': stamp_file, }) # Siso needs the stamp file to be created in order for the build step to # complete. If the task fails when the build server runs it, the build server # will delete the stamp file so that it will be run again next build. build_utils.Touch(stamp_file) return True def MaybeTouch(stamp_file): """Touch |stamp_file| if we are not running under the build_server.""" # If we are running under the build server, the stamp file has already been # touched when the task was created. If we touch it again, siso will consider # the target dirty. if BUILD_SERVER_ENV_VARIABLE in os.environ: return build_utils.Touch(stamp_file) def SendMessage(sock: socket.socket, message: dict): data = json.dumps(message).encode('utf-8') size_prefix = struct.pack('!i', len(data)) sock.sendall(size_prefix + data) def ReceiveMessage(sock: socket.socket) -> dict: size_prefix = b'' remaining = 4 # sizeof(int) while remaining > 0: data = sock.recv(remaining) if not data: return None remaining -= len(data) size_prefix += data remaining, = struct.unpack('!i', size_prefix) received = [] while remaining > 0: data = sock.recv(remaining) if not data: break received.append(data) remaining -= len(data) if received: return json.loads(b''.join(received)) return None