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 subprocess 13import struct 14import time 15 16sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..')) 17from util import build_utils 18 19# Use a unix abstract domain socket: 20# https://man7.org/linux/man-pages/man7/unix.7.html#:~:text=abstract: 21SOCKET_ADDRESS = '\0chromium_build_server_socket' 22BUILD_SERVER_ENV_VARIABLE = 'INVOKED_BY_BUILD_SERVER' 23 24ADD_TASK = 'add_task' 25QUERY_BUILD = 'query_build' 26POLL_HEARTBEAT = 'poll_heartbeat' 27REGISTER_BUILDER = 'register_builder' 28CANCEL_BUILD = 'cancel_build' 29 30SERVER_SCRIPT = pathlib.Path( 31 build_utils.DIR_SOURCE_ROOT 32) / 'build' / 'android' / 'fast_local_dev_server.py' 33 34 35def MaybeRunCommand(name, argv, stamp_file, force, experimental=False): 36 """Returns True if the command was successfully sent to the build server.""" 37 38 if platform.system() == "Darwin": 39 # Build server does not support Mac. 40 return False 41 42 # When the build server runs a command, it sets this environment variable. 43 # This prevents infinite recursion where the script sends a request to the 44 # build server, then the build server runs the script, and then the script 45 # sends another request to the build server. 46 if BUILD_SERVER_ENV_VARIABLE in os.environ: 47 return False 48 49 autoninja_tty = os.environ.get('AUTONINJA_STDOUT_NAME') 50 autoninja_build_id = os.environ.get('AUTONINJA_BUILD_ID') 51 if experimental and not autoninja_tty: 52 raise RuntimeError('experimental_build_server=true is set but autoninja ' 53 'is not patched. Please make sure you are using a ' 54 'patched autoninja script.') 55 56 with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock: 57 try: 58 sock.connect(SOCKET_ADDRESS) 59 except socket.error as e: 60 # [Errno 111] Connection refused. Either the server has not been started 61 # or the server is not currently accepting new connections. 62 if e.errno == 111: 63 if force: 64 raise RuntimeError( 65 '\n\nBuild server is not running and ' 66 'android_static_analysis="build_server" is set.\nPlease run ' 67 'this command in a separate terminal:\n\n' 68 '$ build/android/fast_local_dev_server.py\n\n') from None 69 return False 70 raise e 71 72 SendMessage( 73 sock, 74 json.dumps({ 75 'name': name, 76 'message_type': ADD_TASK, 77 'cmd': argv, 78 'cwd': os.getcwd(), 79 'tty': autoninja_tty, 80 'build_id': autoninja_build_id, 81 'experimental': experimental, 82 'stamp_file': stamp_file, 83 }).encode('utf8')) 84 85 # Siso needs the stamp file to be created in order for the build step to 86 # complete. If the task fails when the build server runs it, the build server 87 # will delete the stamp file so that it will be run again next build. 88 pathlib.Path(stamp_file).touch() 89 return True 90 91 92def SendMessage(sock: socket.socket, message: bytes): 93 size_prefix = struct.pack('!i', len(message)) 94 sock.sendall(size_prefix + message) 95 96 97def ReceiveMessage(sock: socket.socket): 98 size_prefix = b'' 99 remaining = 4 # sizeof(int) 100 while remaining > 0: 101 data = sock.recv(remaining) 102 if not data: 103 return None 104 remaining -= len(data) 105 size_prefix += data 106 remaining, = struct.unpack('!i', size_prefix) 107 received = [] 108 while remaining > 0: 109 data = sock.recv(remaining) 110 if not data: 111 break 112 received.append(data) 113 remaining -= len(data) 114 return b''.join(received) 115