File: builders.py

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (153 lines) | stat: -rw-r--r-- 5,587 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utils for interacting with builders & builder props in src."""

import json
import logging
import pathlib
import subprocess

_THIS_DIR = pathlib.Path(__file__).resolve().parent
_SRC_DIR = _THIS_DIR.parents[1]
# TODO(crbug.com/41492688): Support src-internal configs too. When this is done,
# ensure tools/utr/recipe.py is not using the public reclient instance
_BUILDER_PROP_DIRS = _SRC_DIR.joinpath('infra', 'config', 'generated',
                                       'builders')
_INTERNAL_BUILDER_PROP_DIRS = _SRC_DIR.joinpath('internal', 'infra', 'config',
                                                'generated', 'builders')


def find_builder_props(builder_name, bucket_name=None, project_name=None):
  """Finds the checked-in json props file for the builder.

  Args:
    builder_name: Builder name of the builder
    bucket_name: Bucket name of the builder
    project_name: Project name of the builder

  Returns:
    Tuple of (Dict of the builder's input props, LUCI project of the builder).
      Both elements will be None if the builder wasn't found.
  """

  def _walk_props_dir(props_dir):
    matches = []
    if not props_dir.exists():
      return matches
    for bucket_path in props_dir.iterdir():
      if not bucket_path.is_dir() or (bucket_name
                                      and bucket_path.name != bucket_name):
        continue
      for builder_path in bucket_path.iterdir():
        if builder_path.name != builder_name:
          continue
        prop_file = builder_path.joinpath('properties.json')
        if not prop_file.exists():
          logging.warning(
              'Found generated dir for builder at %s, but no prop file?',
              builder_path)
          continue
        matches.append(prop_file)
    return matches

  possible_matches = []
  if not project_name or project_name.startswith('chrome'):
    matches = _walk_props_dir(_INTERNAL_BUILDER_PROP_DIRS)
    if matches:
      project_name = project_name or 'chrome'
      possible_matches += matches

  if not project_name or project_name.startswith('chromium'):
    matches = _walk_props_dir(_BUILDER_PROP_DIRS)
    if matches:
      project_name = project_name or 'chromium'
      possible_matches += matches

  if not possible_matches:
    if (project_name and project_name.startswith('chrome')
        and not _INTERNAL_BUILDER_PROP_DIRS.exists()):
      logging.warning(
          '[red]%s looks like an internal builder, but src-internal is not '
          'present in this checkout. Make sure checkout_src_internal is set to '
          'True in your .gclient file and run `gclient sync`.[/]', builder_name)
    # Try also fetching the props from buildbucket. This will give us needed
    # vals like recipe and builder-group name for builders that aren't
    # bootstrapped.
    if bucket_name and project_name:
      logging.info(
          'Prop file not found, attempting to fetch props from buildbucket.')
      props = fetch_props_from_buildbucket(builder_name, bucket_name,
                                           project_name)
      if props:
        return props, project_name
    logging.error(
        '[red]No props found. Are you sure you have the correct project '
        '("%s"), bucket ("%s"), and builder name ("%s")?[/]', project_name,
        bucket_name, builder_name)
    if not _INTERNAL_BUILDER_PROP_DIRS.exists():
      logging.warning(
          'src-internal not detected in this checkout. Perhaps the builder '
          'is a "chrome" one, in which: case make sure to add src-internal to '
          "your checkout if a you're a Googler.")
    return None, None
  if len(possible_matches) > 1:
    logging.error(
        '[red]Found multiple prop files for builder %s. Pass in a project '
        '("-p") and bucket name ("-B").[/]', builder_name)
    for m in possible_matches:
      logging.error(m)
    return None, None

  prop_file = possible_matches[0]
  logging.debug('Found prop file %s', prop_file)
  with open(possible_matches[0]) as f:
    props = json.load(f)

  return props, project_name


def fetch_props_from_buildbucket(builder_name, bucket_name, project_name):
  """Calls out to buildbucket for the input props for the given builder

  Args:
    builder_name: Builder name of the builder
    bucket_name: Bucket name of the builder
    project_name: Project name of the builder

  Returns:
    Dict of the builder's input props
  """
  input_json = {
      'id': {
          'project': project_name,
          'bucket': bucket_name,
          'builder': builder_name,
      }
  }
  cmd = [
      'luci-auth',
      'context',
      '--',
      'prpc',
      'call',
      'cr-buildbucket.appspot.com',
      'buildbucket.v2.Builders.GetBuilder',
  ]
  logging.debug('Running prpc:')
  logging.debug(' '.join(cmd))
  p = subprocess.run(cmd,
                     input=json.dumps(input_json),
                     text=True,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
                     check=False)
  if p.returncode:
    logging.warning('Error fetching the build template from buildbucket')
    # Use the "basic_logger" here (and below) to avoid rich from coloring random
    # bits of the printed error.
    logging.getLogger('basic_logger').warning(p.stdout.strip())
    return None
  builder_info = json.loads(p.stdout)
  props_s = builder_info.get('config', {}).get('properties', '{}')
  return json.loads(props_s)