xref: /aosp_15_r20/external/angle/build/android/gyp/util/server_utils.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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