• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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