• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# -*- coding: utf-8 -*-
2# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Configuration options for various cbuildbot builders."""
7
8from __future__ import print_function
9
10import copy
11import itertools
12import json
13import numbers
14import os
15import re
16
17from autotest_lib.utils.frozen_chromite.lib import constants
18from autotest_lib.utils.frozen_chromite.lib import osutils
19from autotest_lib.utils.frozen_chromite.utils import memoize
20
21GS_PATH_DEFAULT = 'default'  # Means gs://chromeos-image-archive/ + bot_id
22
23# Contains the valid build config suffixes.
24CONFIG_TYPE_RELEASE = 'release'
25CONFIG_TYPE_FULL = 'full'
26CONFIG_TYPE_FIRMWARE = 'firmware'
27CONFIG_TYPE_FACTORY = 'factory'
28CONFIG_TYPE_TOOLCHAIN = 'toolchain'
29
30# DISPLAY labels are used to group related builds together in the GE UI.
31
32DISPLAY_LABEL_TRYJOB = 'tryjob'
33DISPLAY_LABEL_INCREMENATAL = 'incremental'
34DISPLAY_LABEL_FULL = 'full'
35DISPLAY_LABEL_CHROME_INFORMATIONAL = 'chrome_informational'
36DISPLAY_LABEL_INFORMATIONAL = 'informational'
37DISPLAY_LABEL_RELEASE = 'release'
38DISPLAY_LABEL_CHROME_PFQ = 'chrome_pfq'
39DISPLAY_LABEL_MST_ANDROID_PFQ = 'mst_android_pfq'
40DISPLAY_LABEL_VMMST_ANDROID_PFQ = 'vmmst_android_pfq'
41DISPLAY_LABEL_PI_ANDROID_PFQ = 'pi_android_pfq'
42DISPLAY_LABEL_QT_ANDROID_PFQ = 'qt_android_pfq'
43DISPLAY_LABEL_RVC_ANDROID_PFQ = 'rvc_android_pfq'
44DISPLAY_LABEL_VMRVC_ANDROID_PFQ = 'vmrvc_android_pfq'
45DISPLAY_LABEL_FIRMWARE = 'firmware'
46DISPLAY_LABEL_FACTORY = 'factory'
47DISPLAY_LABEL_TOOLCHAIN = 'toolchain'
48DISPLAY_LABEL_UTILITY = 'utility'
49DISPLAY_LABEL_PRODUCTION_TRYJOB = 'production_tryjob'
50
51# This list of constants should be kept in sync with GoldenEye code.
52ALL_DISPLAY_LABEL = {
53    DISPLAY_LABEL_TRYJOB,
54    DISPLAY_LABEL_INCREMENATAL,
55    DISPLAY_LABEL_FULL,
56    DISPLAY_LABEL_CHROME_INFORMATIONAL,
57    DISPLAY_LABEL_INFORMATIONAL,
58    DISPLAY_LABEL_RELEASE,
59    DISPLAY_LABEL_CHROME_PFQ,
60    DISPLAY_LABEL_MST_ANDROID_PFQ,
61    DISPLAY_LABEL_VMMST_ANDROID_PFQ,
62    DISPLAY_LABEL_PI_ANDROID_PFQ,
63    DISPLAY_LABEL_QT_ANDROID_PFQ,
64    DISPLAY_LABEL_RVC_ANDROID_PFQ,
65    DISPLAY_LABEL_VMRVC_ANDROID_PFQ,
66    DISPLAY_LABEL_FIRMWARE,
67    DISPLAY_LABEL_FACTORY,
68    DISPLAY_LABEL_TOOLCHAIN,
69    DISPLAY_LABEL_UTILITY,
70    DISPLAY_LABEL_PRODUCTION_TRYJOB,
71}
72
73# These values must be kept in sync with the ChromeOS LUCI builders.
74#
75# https://chrome-internal.googlesource.com/chromeos/
76#     infra/config/+/refs/heads/master/luci/cr-buildbucket.cfg
77LUCI_BUILDER_FACTORY = 'Factory'
78LUCI_BUILDER_FULL = 'Full'
79LUCI_BUILDER_INCREMENTAL = 'Incremental'
80LUCI_BUILDER_INFORMATIONAL = 'Informational'
81LUCI_BUILDER_INFRA = 'Infra'
82LUCI_BUILDER_LEGACY_RELEASE = 'LegacyRelease'
83LUCI_BUILDER_PFQ = 'PFQ'
84LUCI_BUILDER_RAPID = 'Rapid'
85LUCI_BUILDER_RELEASE = 'Release'
86LUCI_BUILDER_STAGING = 'Staging'
87LUCI_BUILDER_TRY = 'Try'
88
89ALL_LUCI_BUILDER = {
90    LUCI_BUILDER_FACTORY,
91    LUCI_BUILDER_FULL,
92    LUCI_BUILDER_INCREMENTAL,
93    LUCI_BUILDER_INFORMATIONAL,
94    LUCI_BUILDER_INFRA,
95    LUCI_BUILDER_LEGACY_RELEASE,
96    LUCI_BUILDER_PFQ,
97    LUCI_BUILDER_RAPID,
98    LUCI_BUILDER_RELEASE,
99    LUCI_BUILDER_STAGING,
100    LUCI_BUILDER_TRY,
101}
102
103
104def isTryjobConfig(build_config):
105    """Is a given build config a tryjob config, or a production config?
106
107  Args:
108    build_config: A fully populated instance of BuildConfig.
109
110  Returns:
111    Boolean. True if it's a tryjob config.
112  """
113    return build_config.luci_builder in [LUCI_BUILDER_RAPID, LUCI_BUILDER_TRY]
114
115# In the Json, this special build config holds the default values for all
116# other configs.
117DEFAULT_BUILD_CONFIG = '_default'
118
119# Constants for config template file
120CONFIG_TEMPLATE_BOARDS = 'boards'
121CONFIG_TEMPLATE_NAME = 'name'
122CONFIG_TEMPLATE_EXPERIMENTAL = 'experimental'
123CONFIG_TEMPLATE_LEADER_BOARD = 'leader_board'
124CONFIG_TEMPLATE_BOARD_GROUP = 'board_group'
125CONFIG_TEMPLATE_BUILDER = 'builder'
126CONFIG_TEMPLATE_RELEASE = 'RELEASE'
127CONFIG_TEMPLATE_CONFIGS = 'configs'
128CONFIG_TEMPLATE_ARCH = 'arch'
129CONFIG_TEMPLATE_RELEASE_BRANCH = 'release_branch'
130CONFIG_TEMPLATE_REFERENCE_BOARD_NAME = 'reference_board_name'
131CONFIG_TEMPLATE_MODELS = 'models'
132CONFIG_TEMPLATE_MODEL_NAME = 'name'
133CONFIG_TEMPLATE_MODEL_BOARD_NAME = 'board_name'
134CONFIG_TEMPLATE_MODEL_TEST_SUITES = 'test_suites'
135CONFIG_TEMPLATE_MODEL_CQ_TEST_ENABLED = 'cq_test_enabled'
136
137CONFIG_X86_INTERNAL = 'X86_INTERNAL'
138CONFIG_X86_EXTERNAL = 'X86_EXTERNAL'
139CONFIG_ARM_INTERNAL = 'ARM_INTERNAL'
140CONFIG_ARM_EXTERNAL = 'ARM_EXTERNAL'
141
142
143def IsCanaryMaster(builder_run):
144    """Returns True if this build type is master-release"""
145    return (builder_run.config.build_type == constants.CANARY_TYPE
146            and builder_run.config.master
147            and builder_run.manifest_branch == 'master')
148
149
150def IsPFQType(b_type):
151    """Returns True if this build type is a PFQ."""
152    return b_type in (constants.PFQ_TYPE, constants.ANDROID_PFQ_TYPE)
153
154
155def IsCanaryType(b_type):
156    """Returns True if this build type is a Canary."""
157    return b_type == constants.CANARY_TYPE
158
159
160def IsMasterAndroidPFQ(config):
161    """Returns True if this build is master Android PFQ type."""
162    return config.build_type == constants.ANDROID_PFQ_TYPE and config.master
163
164
165def GetHWTestEnv(builder_run_config, model_config=None, suite_config=None):
166    """Return the env of a suite to run for a given build/model.
167
168  Args:
169    builder_run_config: The BuildConfig object inside a BuilderRun object.
170    model_config: A ModelTestConfig object to test against.
171    suite_config: A HWTestConfig object to test against.
172
173  Returns:
174    A string variable to indiate the hwtest environment.
175  """
176    enable_suite = True if suite_config is None else suite_config.enable_skylab
177    enable_model = True if model_config is None else model_config.enable_skylab
178    if (builder_run_config.enable_skylab_hw_tests and enable_suite
179                and enable_model):
180        return constants.ENV_SKYLAB
181
182    return constants.ENV_AUTOTEST
183
184
185class AttrDict(dict):
186    """Dictionary with 'attribute' access.
187
188  This is identical to a dictionary, except that string keys can be addressed as
189  read-only attributes.
190  """
191
192    def __getattr__(self, name):
193        """Support attribute-like access to each dict entry."""
194        if name in self:
195            return self[name]
196
197        # Super class (dict) has no __getattr__ method, so use __getattribute__.
198        return super(AttrDict, self).__getattribute__(name)
199
200
201class BuildConfig(AttrDict):
202    """Dictionary of explicit configuration settings for a cbuildbot config
203
204  Each dictionary entry is in turn a dictionary of config_param->value.
205
206  See DefaultSettings for details on known configurations, and their
207  documentation.
208  """
209
210    def deepcopy(self):
211        """Create a deep copy of this object.
212
213    This is a specialized version of copy.deepcopy() for BuildConfig objects. It
214    speeds up deep copies by 10x because we know in advance what is stored
215    inside a BuildConfig object and don't have to do as much introspection. This
216    function is called a lot during setup of the config objects so optimizing it
217    makes a big difference. (It saves seconds off the load time of this module!)
218    """
219        result = BuildConfig(self)
220
221        # Here is where we handle all values that need deepcopy instead of shallow.
222        for k, v in result.items():
223            if v is not None:
224                if k == 'child_configs':
225                    result[k] = [x.deepcopy() for x in v]
226                elif k in ('vm_tests', 'vm_tests_override', 'hw_tests',
227                           'hw_tests_override', 'tast_vm_tests'):
228                    result[k] = [copy.copy(x) for x in v]
229                # type(v) is faster than isinstance.
230                elif type(v) is list:  # pylint: disable=unidiomatic-typecheck
231                    result[k] = v[:]
232
233        return result
234
235    def apply(self, *args, **kwargs):
236        """Apply changes to this BuildConfig.
237
238    Note: If an override is callable, it will be called and passed the prior
239    value for the given key (or None) to compute the new value.
240
241    Args:
242      args: Dictionaries or templates to update this config with.
243      kwargs: Settings to inject; see DefaultSettings for valid values.
244
245    Returns:
246      self after changes are applied.
247    """
248        inherits = list(args)
249        inherits.append(kwargs)
250
251        for update_config in inherits:
252            for name, value in update_config.items():
253                if callable(value):
254                    # If we are applying to a fixed value, we resolve to a fixed value.
255                    # Otherwise, we save off a callable to apply later, perhaps with
256                    # nested callables (IE: we curry them). This allows us to use
257                    # callables in templates, and apply templates to each other and still
258                    # get the expected result when we use them later on.
259                    #
260                    # Delaying the resolution of callables is safe, because "Add()" always
261                    # applies against the default, which has fixed values for everything.
262
263                    if name in self:
264                        # apply it to the current value.
265                        if callable(self[name]):
266                            # If we have no fixed value to resolve with, stack the callables.
267                            def stack(new_callable, old_callable):
268                                """Helper method to isolate namespace for closure."""
269                                return lambda fixed: new_callable(
270                                        old_callable(fixed))
271
272                            self[name] = stack(value, self[name])
273                        else:
274                            # If the current value was a fixed value, apply the callable.
275                            self[name] = value(self[name])
276                    else:
277                        # If we had no value to apply it to, save it for later.
278                        self[name] = value
279
280                elif name == '_template':
281                    # We never apply _template. You have to set it through Add.
282                    pass
283
284                else:
285                    # Simple values overwrite whatever we do or don't have.
286                    self[name] = value
287
288        return self
289
290    def derive(self, *args, **kwargs):
291        """Create a new config derived from this one.
292
293    Note: If an override is callable, it will be called and passed the prior
294    value for the given key (or None) to compute the new value.
295
296    Args:
297      args: Mapping instances to mixin.
298      kwargs: Settings to inject; see DefaultSettings for valid values.
299
300    Returns:
301      A new _config instance.
302    """
303        return self.deepcopy().apply(*args, **kwargs)
304
305    def AddSlave(self, slave):
306        """Assign slave config(s) to a build master.
307
308    A helper for adding slave configs to a master config.
309    """
310        assert self.master
311        if self['slave_configs'] is None:
312            self['slave_configs'] = []
313        self.slave_configs.append(slave.name)
314        self.slave_configs.sort()
315
316    def AddSlaves(self, slaves):
317        """Assign slave config(s) to a build master.
318
319    A helper for adding slave configs to a master config.
320    """
321        assert self.master
322        if self['slave_configs'] is None:
323            self['slave_configs'] = []
324        self.slave_configs.extend(slave_config.name for slave_config in slaves)
325        self.slave_configs.sort()
326
327
328class VMTestConfig(object):
329    """Config object for virtual machine tests suites.
330
331  Attributes:
332    test_type: Test type to be run.
333    test_suite: Test suite to be run in VMTest.
334    timeout: Number of seconds to wait before timing out waiting for
335             results.
336    retry: Whether we should retry tests that fail in a suite run.
337    max_retries: Integer, maximum job retries allowed at suite level.
338                 None for no max.
339    warn_only: Boolean, failure on VM tests warns only.
340    use_ctest: Use the old ctest code path rather than the new chromite one.
341  """
342    DEFAULT_TEST_TIMEOUT = 90 * 60
343
344    def __init__(self,
345                 test_type,
346                 test_suite=None,
347                 timeout=DEFAULT_TEST_TIMEOUT,
348                 retry=False,
349                 max_retries=constants.VM_TEST_MAX_RETRIES,
350                 warn_only=False,
351                 use_ctest=True):
352        """Constructor -- see members above."""
353        self.test_type = test_type
354        self.test_suite = test_suite
355        self.timeout = timeout
356        self.retry = retry
357        self.max_retries = max_retries
358        self.warn_only = warn_only
359        self.use_ctest = use_ctest
360
361    def __eq__(self, other):
362        return self.__dict__ == other.__dict__
363
364
365class GCETestConfig(object):
366    """Config object for GCE tests suites.
367
368  Attributes:
369    test_type: Test type to be run.
370    test_suite: Test suite to be run in GCETest.
371    timeout: Number of seconds to wait before timing out waiting for
372             results.
373    use_ctest: Use the old ctest code path rather than the new chromite one.
374  """
375    DEFAULT_TEST_TIMEOUT = 60 * 60
376
377    def __init__(self,
378                 test_type,
379                 test_suite=None,
380                 timeout=DEFAULT_TEST_TIMEOUT,
381                 use_ctest=True):
382        """Constructor -- see members above."""
383        self.test_type = test_type
384        self.test_suite = test_suite
385        self.timeout = timeout
386        self.use_ctest = use_ctest
387
388    def __eq__(self, other):
389        return self.__dict__ == other.__dict__
390
391
392class TastVMTestConfig(object):
393    """Config object for a Tast virtual-machine-based test suite.
394
395  Attributes:
396    name: String containing short human-readable name describing test suite.
397    test_exprs: List of string expressions describing which tests to run; this
398                is passed directly to the 'tast run' command. See
399                https://goo.gl/UPNEgT for info about test expressions.
400    timeout: Number of seconds to wait before timing out waiting for
401             results.
402  """
403    DEFAULT_TEST_TIMEOUT = 60 * 60
404
405    def __init__(self, suite_name, test_exprs, timeout=DEFAULT_TEST_TIMEOUT):
406        """Constructor -- see members above."""
407        # This is an easy mistake to make and results in confusing errors later when
408        # a list of one-character strings gets passed to the tast command.
409        if not isinstance(test_exprs, list):
410            raise TypeError('test_exprs must be list of strings')
411        self.suite_name = suite_name
412        self.test_exprs = test_exprs
413        self.timeout = timeout
414
415    def __eq__(self, other):
416        return self.__dict__ == other.__dict__
417
418
419class MoblabVMTestConfig(object):
420    """Config object for moblab tests suites.
421
422  Attributes:
423    test_type: Test type to be run.
424    timeout: Number of seconds to wait before timing out waiting for
425             results.
426  """
427    DEFAULT_TEST_TIMEOUT = 60 * 60
428
429    def __init__(self, test_type, timeout=DEFAULT_TEST_TIMEOUT):
430        """Constructor -- see members above."""
431        self.test_type = test_type
432        self.timeout = timeout
433
434    def __eq__(self, other):
435        return self.__dict__ == other.__dict__
436
437
438class ModelTestConfig(object):
439    """Model specific config that controls which test suites are executed.
440
441  Attributes:
442    name: The name of the model that will be tested (matches model label)
443    lab_board_name: The name of the board in the lab (matches board label)
444    test_suites: List of hardware test suites that will be executed.
445  """
446
447    def __init__(self,
448                 name,
449                 lab_board_name,
450                 test_suites=None,
451                 enable_skylab=True):
452        """Constructor -- see members above."""
453        self.name = name
454        self.lab_board_name = lab_board_name
455        self.test_suites = test_suites
456        self.enable_skylab = enable_skylab
457
458    def __eq__(self, other):
459        return self.__dict__ == other.__dict__
460
461
462class HWTestConfig(object):
463    """Config object for hardware tests suites.
464
465  Attributes:
466    suite: Name of the test suite to run.
467    timeout: Number of seconds to wait before timing out waiting for
468             results.
469    pool: Pool to use for hw testing.
470    blocking: Setting this to true requires that this suite must PASS for suites
471              scheduled after it to run. This also means any suites that are
472              scheduled before a blocking one are also blocking ones scheduled
473              after. This should be used when you want some suites to block
474              whether or not others should run e.g. only run longer-running
475              suites if some core ones pass first.
476
477              Note, if you want multiple suites to block other suites but run
478              in parallel, you should only mark the last one scheduled as
479              blocking (it effectively serves as a thread/process join).
480    async: Fire-and-forget suite.
481    warn_only: Failure on HW tests warns only (does not generate error).
482    critical: Usually we consider structural failures here as OK.
483    priority:  Priority at which tests in the suite will be scheduled in
484               the hw lab.
485    file_bugs: Should we file bugs if a test fails in a suite run.
486    minimum_duts: minimum number of DUTs required for testing in the hw lab.
487    retry: Whether we should retry tests that fail in a suite run.
488    max_retries: Integer, maximum job retries allowed at suite level.
489                 None for no max.
490    suite_min_duts: Preferred minimum duts. Lab will prioritize on getting such
491                    number of duts even if the suite is competing with
492                    other suites that have higher priority.
493    suite_args: Arguments passed to the suite.  This should be a dict
494                representing keyword arguments.  The value is marshalled
495                using repr(), so the dict values should be basic types.
496    quota_account: The quotascheduler account to use for all tests in this
497                   suite.
498
499  Some combinations of member settings are invalid:
500    * A suite config may not specify both blocking and async.
501    * A suite config may not specify both warn_only and critical.
502  """
503    _MINUTE = 60
504    _HOUR = 60 * _MINUTE
505    _DAY = 24 * _HOUR
506    # CTS timeout ~ 2 * expected runtime in case other tests are using the CTS
507    # pool.
508    # Must not exceed the buildbucket build timeout set at
509    # https://chrome-internal.googlesource.com/chromeos/infra/config/+/8f12edac54383831aaed9ed1819ef909a66ecc97/testplatform/main.star#90
510    CTS_QUAL_HW_TEST_TIMEOUT = int(1 * _DAY + 18 * _HOUR)
511    # GTS runs faster than CTS. But to avoid starving GTS by CTS we set both
512    # timeouts equal.
513    GTS_QUAL_HW_TEST_TIMEOUT = CTS_QUAL_HW_TEST_TIMEOUT
514    SHARED_HW_TEST_TIMEOUT = int(3.0 * _HOUR)
515    PALADIN_HW_TEST_TIMEOUT = int(2.0 * _HOUR)
516    BRANCHED_HW_TEST_TIMEOUT = int(10.0 * _HOUR)
517
518    # TODO(jrbarnette) Async HW test phases complete within seconds.
519    # however, the tests they start can require hours to complete.
520    # Chromite code doesn't distinguish "timeout for Autotest" from
521    # timeout in the builder.  This is WRONG WRONG WRONG.  But, until
522    # there's a better fix, we'll allow these phases hours to fail.
523    ASYNC_HW_TEST_TIMEOUT = int(250.0 * _MINUTE)
524
525    def __init__(self,
526                 suite,
527                 pool=constants.HWTEST_QUOTA_POOL,
528                 timeout=SHARED_HW_TEST_TIMEOUT,
529                 warn_only=False,
530                 critical=False,
531                 blocking=False,
532                 file_bugs=False,
533                 priority=constants.HWTEST_BUILD_PRIORITY,
534                 retry=True,
535                 max_retries=constants.HWTEST_MAX_RETRIES,
536                 minimum_duts=0,
537                 suite_min_duts=0,
538                 suite_args=None,
539                 offload_failures_only=False,
540                 enable_skylab=True,
541                 quota_account=constants.HWTEST_QUOTA_ACCOUNT_BVT,
542                 **kwargs):
543        """Constructor -- see members above."""
544        # Python 3.7+ made async a reserved keyword.
545        asynchronous = kwargs.pop('async', False)
546        setattr(self, 'async', asynchronous)
547        assert not kwargs, 'Excess kwargs found: %s' % (kwargs, )
548
549        assert not asynchronous or not blocking, '%s is async and blocking' % suite
550        assert not warn_only or not critical
551        self.suite = suite
552        self.pool = pool
553        self.timeout = timeout
554        self.blocking = blocking
555        self.warn_only = warn_only
556        self.critical = critical
557        self.file_bugs = file_bugs
558        self.priority = priority
559        self.retry = retry
560        self.max_retries = max_retries
561        self.minimum_duts = minimum_duts
562        self.suite_min_duts = suite_min_duts
563        self.suite_args = suite_args
564        self.offload_failures_only = offload_failures_only
565        # Usually whether to run in skylab is controlled by 'enable_skylab_hw_test'
566        # in build config. But for some particular suites, we want to exclude them
567        # from Skylab even if the build config is migrated to Skylab.
568        self.enable_skylab = enable_skylab
569        self.quota_account = quota_account
570
571    def _SetCommonBranchedValues(self):
572        """Set the common values for branched builds."""
573        self.timeout = max(HWTestConfig.BRANCHED_HW_TEST_TIMEOUT, self.timeout)
574
575        # Set minimum_duts default to 0, which means that lab will not check the
576        # number of available duts to meet the minimum requirement before creating
577        # a suite job for branched build.
578        self.minimum_duts = 0
579
580    def SetBranchedValuesForSkylab(self):
581        """Set suite values for branched builds for skylab."""
582        self._SetCommonBranchedValues()
583
584        if (constants.SKYLAB_HWTEST_PRIORITIES_MAP[self.priority] <
585                    constants.SKYLAB_HWTEST_PRIORITIES_MAP[
586                            constants.HWTEST_DEFAULT_PRIORITY]):
587            self.priority = constants.HWTEST_DEFAULT_PRIORITY
588
589    def SetBranchedValues(self):
590        """Changes the HW Test timeout/priority values to branched values."""
591        self._SetCommonBranchedValues()
592
593        # Only reduce priority if it's lower.
594        new_priority = constants.HWTEST_PRIORITIES_MAP[
595                constants.HWTEST_DEFAULT_PRIORITY]
596        if isinstance(self.priority, numbers.Integral):
597            self.priority = min(self.priority, new_priority)
598        elif constants.HWTEST_PRIORITIES_MAP[self.priority] > new_priority:
599            self.priority = new_priority
600
601    @property
602    def timeout_mins(self):
603        return self.timeout // 60
604
605    def __eq__(self, other):
606        return self.__dict__ == other.__dict__
607
608
609class NotificationConfig(object):
610    """Config object for defining notification settings.
611
612  Attributes:
613    email: Email address that receives failure notifications.
614    threshold: Number of consecutive failures that should occur in order to
615              be notified. This number should be greater than or equal to 1. If
616              none is specified, default is 1.
617    template: Email template luci-notify should use when sending the email
618              notification. If none is specified, uses the default template.
619  """
620    DEFAULT_TEMPLATE = 'legacy_release'
621    DEFAULT_THRESHOLD = 1
622
623    def __init__(self,
624                 email,
625                 threshold=DEFAULT_THRESHOLD,
626                 template=DEFAULT_TEMPLATE):
627        """Constructor -- see members above."""
628        self.email = email
629        self.threshold = threshold
630        self.template = template
631        self.threshold = threshold
632
633    @property
634    def email_notify(self):
635        return {'email': self.email, 'template': self.template}
636
637    def __eq__(self, other):
638        return self.__dict__ == other.__dict__
639
640
641def DefaultSettings():
642    # Enumeration of valid settings; any/all config settings must be in this.
643    # All settings must be documented.
644    return dict(
645            # The name of the template we inherit settings from.
646            _template=None,
647
648            # The name of the config.
649            name=None,
650
651            # A list of boards to build.
652            boards=None,
653
654            # A list of ModelTestConfig objects that represent all of the models
655            # supported by a given unified build and their corresponding test config.
656            models=[],
657
658            # This value defines what part of the Golden Eye UI is responsible for
659            # displaying builds of this build config. The value is required, and
660            # must be in ALL_DISPLAY_LABEL.
661            # TODO: Make the value required after crbug.com/776955 is finished.
662            display_label=None,
663
664            # This defines which LUCI Builder to use. It must match an entry in:
665            #
666            # https://chrome-internal.git.corp.google.com/chromeos/
667            #    manifest-internal/+/infra/config/cr-buildbucket.cfg
668            #
669            luci_builder=LUCI_BUILDER_LEGACY_RELEASE,
670
671            # The profile of the variant to set up and build.
672            profile=None,
673
674            # This bot pushes changes to the overlays.
675            master=False,
676
677            # A basic_builder is a special configuration which does not perform tests
678            # or mutate external config.
679            basic_builder=False,
680
681            # If this bot triggers slave builds, this will contain a list of
682            # slave config names.
683            slave_configs=None,
684
685            # If False, this flag indicates that the CQ should not check whether
686            # this bot passed or failed. Set this to False if you are setting up a
687            # new bot. Once the bot is on the waterfall and is consistently green,
688            # mark the builder as important=True.
689            important=True,
690
691            # If True, build config should always be run as if --debug was set
692            # on the cbuildbot command line. This is different from 'important'
693            # and is usually correlated with tryjob build configs.
694            debug=False,
695
696            # If True, use the debug instance of CIDB instead of prod.
697            debug_cidb=False,
698
699            # Timeout for the build as a whole (in seconds).
700            build_timeout=(5 * 60 + 30) * 60,
701
702            # A list of NotificationConfig objects describing who to notify of builder
703            # failures.
704            notification_configs=[],
705
706            # An integer. If this builder fails this many times consecutively, send
707            # an alert email to the recipients health_alert_recipients. This does
708            # not apply to tryjobs. This feature is similar to the ERROR_WATERMARK
709            # feature of upload_symbols, and it may make sense to merge the features
710            # at some point.
711            health_threshold=0,
712
713            # List of email addresses to send health alerts to for this builder. It
714            # supports automatic email address lookup for the following sheriff
715            # types:
716            #     'tree': tree sheriffs
717            #     'chrome': chrome gardeners
718            health_alert_recipients=[],
719
720            # Whether this is an internal build config.
721            internal=False,
722
723            # Whether this is a branched build config. Used for pfq logic.
724            branch=False,
725
726            # The name of the manifest to use. E.g., to use the buildtools manifest,
727            # specify 'buildtools'.
728            manifest=constants.DEFAULT_MANIFEST,
729
730            # emerge use flags to use while setting up the board, building packages,
731            # making images, etc.
732            useflags=[],
733
734            # Set the variable CHROMEOS_OFFICIAL for the build. Known to affect
735            # parallel_emerge, cros_set_lsb_release, and chromeos_version.sh. See
736            # bug chromium-os:14649
737            chromeos_official=False,
738
739            # Use binary packages for building the toolchain. (emerge --getbinpkg)
740            usepkg_toolchain=True,
741
742            # Use binary packages for build_packages and setup_board.
743            usepkg_build_packages=True,
744
745            # Does this profile need to sync chrome?  If None, we guess based on
746            # other factors.  If True/False, we always do that.
747            sync_chrome=None,
748
749            # Use the newest ebuilds for all the toolchain packages.
750            latest_toolchain=False,
751
752            # This is only valid when latest_toolchain is True. If you set this to a
753            # commit-ish, the gcc ebuild will use it to build the toolchain
754            # compiler.
755            gcc_githash=None,
756
757            # Wipe and replace the board inside the chroot.
758            board_replace=False,
759
760            # Wipe and replace chroot, but not source.
761            chroot_replace=True,
762
763            # Create the chroot on a loopback-mounted chroot.img instead of a bare
764            # directory.  Required for snapshots; otherwise optional.
765            chroot_use_image=True,
766
767            # Uprevs the local ebuilds to build new changes since last stable.
768            # build.  If master then also pushes these changes on success. Note that
769            # we uprev on just about every bot config because it gives us a more
770            # deterministic build system (the tradeoff being that some bots build
771            # from source more frequently than if they never did an uprev). This way
772            # the release/factory/etc... builders will pick up changes that devs
773            # pushed before it runs, but after the corresponding PFQ bot ran (which
774            # is what creates+uploads binpkgs).  The incremental bots are about the
775            # only ones that don't uprev because they mimic the flow a developer
776            # goes through on their own local systems.
777            uprev=True,
778
779            # Select what overlays to look at for revving and prebuilts. This can be
780            # any constants.VALID_OVERLAYS.
781            overlays=constants.PUBLIC_OVERLAYS,
782
783            # Select what overlays to push at. This should be a subset of overlays
784            # for the particular builder.  Must be None if not a master.  There
785            # should only be one master bot pushing changes to each overlay per
786            # branch.
787            push_overlays=None,
788
789            # Uprev Android, values of 'latest_release', or None.
790            android_rev=None,
791
792            # Which Android branch build do we try to uprev from.
793            android_import_branch=None,
794
795            # Android package name.
796            android_package=None,
797
798            # Uprev Chrome, values of 'tot', 'stable_release', or None.
799            chrome_rev=None,
800
801            # Exit the builder right after checking compilation.
802            # TODO(mtennant): Should be something like "compile_check_only".
803            compilecheck=False,
804
805            # If True, run DebugInfoTest stage.
806            debuginfo_test=False,
807
808            # Runs the tests that the signer would run. This should only be set if
809            # 'recovery' is in images.
810            signer_tests=False,
811
812            # Runs unittests for packages.
813            unittests=True,
814
815            # A list of the packages to blacklist from unittests.
816            unittest_blacklist=[],
817
818            # Generates AFDO data. Will capture a profile of chrome using a hwtest
819            # to run a predetermined set of benchmarks.
820            # FIXME(tcwang): Keep this config during transition to async AFDO
821            afdo_generate=False,
822
823            # Generates AFDO data asynchronously. Will capture a profile of chrome
824            # using a hwtest to run a predetermined set of benchmarks.
825            afdo_generate_async=False,
826
827            # Verify and publish kernel profiles.
828            kernel_afdo_verify=False,
829
830            # Verify and publish chrome profiles.
831            chrome_afdo_verify=False,
832
833            # Generate Chrome orderfile. Will build Chrome with C3 ordering and
834            # generate an orderfile for uploading as a result.
835            orderfile_generate=False,
836
837            # Verify unvetted Chrome orderfile. Will use the most recent unvetted
838            # orderfile and build Chrome. Upload the orderfile to vetted bucket
839            # as a result.
840            orderfile_verify=False,
841
842            # Generates AFDO data, builds the minimum amount of artifacts and
843            # assumes a non-distributed builder (i.e.: the whole process in a single
844            # builder).
845            afdo_generate_min=False,
846
847            # Update the Chrome ebuild with the AFDO profile info.
848            afdo_update_chrome_ebuild=False,
849
850            # Update the kernel ebuild with the AFDO profile info.
851            afdo_update_kernel_ebuild=False,
852
853            # Uses AFDO data. The Chrome build will be optimized using the AFDO
854            # profile information found in Chrome's source tree.
855            afdo_use=True,
856
857            # A list of VMTestConfig objects to run by default.
858            vm_tests=[
859                    VMTestConfig(constants.VM_SUITE_TEST_TYPE,
860                                 test_suite='smoke'),
861                    VMTestConfig(constants.SIMPLE_AU_TEST_TYPE)
862            ],
863
864            # A list of all VMTestConfig objects to use if VM Tests are forced on
865            # (--vmtest command line or trybot). None means no override.
866            vm_tests_override=None,
867
868            # If true, in addition to upload vm test result to artifact folder, report
869            # results to other dashboard as well.
870            vm_test_report_to_dashboards=False,
871
872            # The number of times to run the VMTest stage. If this is >1, then we
873            # will run the stage this many times, stopping if we encounter any
874            # failures.
875            vm_test_runs=1,
876
877            # If True, run SkylabHWTestStage instead of HWTestStage for suites that
878            # use pools other than pool:cts.
879            enable_skylab_hw_tests=False,
880
881            # If set, this is the URL of the bug justifying why hw_tests are disabled
882            # on a builder that should always have hw_tests.
883            hw_tests_disabled_bug='',
884
885            # If True, run SkylabHWTestStage instead of HWTestStage for suites that
886            # use pool:cts.
887            enable_skylab_cts_hw_tests=False,
888
889            # A list of HWTestConfig objects to run.
890            hw_tests=[],
891
892            # A list of all HWTestConfig objects to use if HW Tests are forced on
893            # (--hwtest command line or trybot). None means no override.
894            hw_tests_override=None,
895
896            # If true, uploads artifacts for hw testing. Upload payloads for test
897            # image if the image is built. If not, dev image is used and then base
898            # image.
899            upload_hw_test_artifacts=True,
900
901            # If true, uploads individual image tarballs.
902            upload_standalone_images=True,
903
904            # A list of GCETestConfig objects to use. Currently only some lakitu
905            # builders run gce tests.
906            gce_tests=[],
907
908            # Whether to run CPEExport stage. This stage generates portage depgraph
909            # data that is used for bugs reporting (see go/why-cpeexport). Only
910            # release builders should run this stage.
911            run_cpeexport=False,
912
913            # Whether to run BuildConfigsExport stage. This stage generates build
914            # configs (see crbug.com/974795 project). Only release builders should
915            # run this stage.
916            run_build_configs_export=False,
917
918            # A list of TastVMTestConfig objects describing Tast-based test suites
919            # that should be run in a VM.
920            tast_vm_tests=[],
921
922            # Default to not run moblab tests. Currently the blessed moblab board runs
923            # these tests.
924            moblab_vm_tests=[],
925
926            # List of patterns for portage packages for which stripped binpackages
927            # should be uploaded to GS. The patterns are used to search for packages
928            # via `equery list`.
929            upload_stripped_packages=[
930                    # Used by SimpleChrome workflow.
931                    'chromeos-base/chromeos-chrome',
932                    'sys-kernel/*kernel*',
933            ],
934
935            # Google Storage path to offload files to.
936            #   None - No upload
937            #   GS_PATH_DEFAULT - 'gs://chromeos-image-archive/' + bot_id
938            #   value - Upload to explicit path
939            gs_path=GS_PATH_DEFAULT,
940
941            # TODO(sosa): Deprecate binary.
942            # Type of builder.  Check constants.VALID_BUILD_TYPES.
943            build_type=constants.PFQ_TYPE,
944
945            # Whether to schedule test suites by suite_scheduler. Generally only
946            # True for "release" builders.
947            suite_scheduling=False,
948
949            # The class name used to build this config.  See the modules in
950            # cbuildbot / builders/*_builders.py for possible values.  This should
951            # be the name in string form -- e.g. "simple_builders.SimpleBuilder" to
952            # get the SimpleBuilder class in the simple_builders module.  If not
953            # specified, we'll fallback to legacy probing behavior until everyone
954            # has been converted (see the scripts/cbuildbot.py file for details).
955            builder_class_name=None,
956
957            # List of images we want to build -- see build_image for more details.
958            images=['test'],
959
960            # Image from which we will build update payloads.  Must either be None
961            # or name one of the images in the 'images' list, above.
962            payload_image=None,
963
964            # Whether to build a netboot image.
965            factory_install_netboot=True,
966
967            # Whether to build the factory toolkit.
968            factory_toolkit=True,
969
970            # Whether to build factory packages in BuildPackages.
971            factory=True,
972
973            # Flag to control if all packages for the target are built. If disabled
974            # and unittests are enabled, the unit tests and their dependencies
975            # will still be built during the testing stage.
976            build_packages=True,
977
978            # Tuple of specific packages we want to build.  Most configs won't
979            # specify anything here and instead let build_packages calculate.
980            packages=[],
981
982            # Do we push a final release image to chromeos-images.
983            push_image=False,
984
985            # Do we upload debug symbols.
986            upload_symbols=False,
987
988            # Whether we upload a hwqual tarball.
989            hwqual=False,
990
991            # Run a stage that generates release payloads for signed images.
992            paygen=False,
993
994            # If the paygen stage runs, generate tests, and schedule auto-tests for
995            # them.
996            paygen_skip_testing=False,
997
998            # If the paygen stage runs, don't generate any delta payloads. This is
999            # only done if deltas are broken for a given board.
1000            paygen_skip_delta_payloads=False,
1001
1002            # Run a stage that generates and uploads package CPE information.
1003            cpe_export=True,
1004
1005            # Run a stage that generates and uploads debug symbols.
1006            debug_symbols=True,
1007
1008            # Do not package the debug symbols in the binary package. The debug
1009            # symbols will be in an archive with the name cpv.debug.tbz2 in
1010            # /build/${BOARD}/packages and uploaded with the prebuilt.
1011            separate_debug_symbols=True,
1012
1013            # Include *.debug files for debugging core files with gdb in debug.tgz.
1014            # These are very large. This option only has an effect if debug_symbols
1015            # and archive are set.
1016            archive_build_debug=False,
1017
1018            # Run a stage that archives build and test artifacts for developer
1019            # consumption.
1020            archive=True,
1021
1022            # Git repository URL for our manifests.
1023            #  https://chromium.googlesource.com/chromiumos/manifest
1024            #  https://chrome-internal.googlesource.com/chromeos/manifest-internal
1025            manifest_repo_url=None,
1026
1027            # Whether we are using the manifest_version repo that stores per-build
1028            # manifests.
1029            manifest_version=False,
1030
1031            # Use a different branch of the project manifest for the build.
1032            manifest_branch=None,
1033
1034            # LKGM for ChromeOS generated for Chrome builds that are blessed from
1035            # canary runs.
1036            use_chrome_lkgm=False,
1037
1038            # Upload prebuilts for this build. Valid values are PUBLIC, PRIVATE, or
1039            # False.
1040            prebuilts=False,
1041
1042            # Use SDK as opposed to building the chroot from source.
1043            use_sdk=True,
1044
1045            # The description string to print out for config when user runs --list.
1046            description=None,
1047
1048            # Boolean that enables parameter --git-sync for upload_prebuilts.
1049            git_sync=False,
1050
1051            # A list of the child config groups, if applicable. See the AddGroup
1052            # method.
1053            child_configs=[],
1054
1055            # Whether this config belongs to a config group.
1056            grouped=False,
1057
1058            # layout of build_image resulting image. See
1059            # scripts/build_library/legacy_disk_layout.json or
1060            # overlay-<board>/scripts/disk_layout.json for possible values.
1061            disk_layout=None,
1062
1063            # If enabled, run the PatchChanges stage.  Enabled by default. Can be
1064            # overridden by the --nopatch flag.
1065            postsync_patch=True,
1066
1067            # Reexec into the buildroot after syncing.  Enabled by default.
1068            postsync_reexec=True,
1069
1070            # Run the binhost_test stage. Only makes sense for builders that have no
1071            # boards.
1072            binhost_test=False,
1073
1074            # If specified, it is passed on to the PushImage script as '--sign-types'
1075            # commandline argument.  Must be either None or a list of image types.
1076            sign_types=None,
1077
1078            # TODO(sosa): Collapse to one option.
1079            # ========== Dev installer prebuilts options =======================
1080
1081            # Upload prebuilts for this build to this bucket. If it equals None the
1082            # default buckets are used.
1083            binhost_bucket=None,
1084
1085            # Parameter --key for upload_prebuilts. If it equals None, the default
1086            # values are used, which depend on the build type.
1087            binhost_key=None,
1088
1089            # Parameter --binhost-base-url for upload_prebuilts. If it equals None,
1090            # the default value is used.
1091            binhost_base_url=None,
1092
1093            # Upload dev installer prebuilts.
1094            dev_installer_prebuilts=False,
1095
1096            # Enable rootfs verification on the image.
1097            rootfs_verification=True,
1098
1099            # Build the Chrome SDK.
1100            chrome_sdk=False,
1101
1102            # If chrome_sdk is set to True, this determines whether we attempt to
1103            # build Chrome itself with the generated SDK.
1104            chrome_sdk_build_chrome=True,
1105
1106            # If chrome_sdk is set to True, this determines whether we use goma to
1107            # build chrome.
1108            chrome_sdk_goma=True,
1109
1110            # Run image tests. This should only be set if 'base' is in our list of
1111            # images.
1112            image_test=False,
1113
1114            # ==================================================================
1115            # Workspace related options.
1116
1117            # Which branch should WorkspaceSyncStage checkout, if run.
1118            workspace_branch=None,
1119
1120            # ==================================================================
1121            # The documentation associated with the config.
1122            doc=None,
1123
1124            # ==================================================================
1125            # The goma related options.
1126
1127            # Which goma client to use.
1128            goma_client_type=None,
1129
1130            # Try to use goma to build all packages.
1131            build_all_with_goma=False,
1132
1133            # This is a LUCI Scheduler schedule string. Setting this will create
1134            # a LUCI Scheduler for this build on swarming (not buildbot).
1135            # See: https://goo.gl/VxSzFf
1136            schedule=None,
1137
1138            # This is the list of git repos which can trigger this build in swarming.
1139            # Implies that schedule is set, to "triggered".
1140            # The format is of the form:
1141            #   [ (<git repo url>, (<ref1>, <ref2>, …)),
1142            #    …]
1143            triggered_gitiles=None,
1144
1145            # If true, skip package retries in BuildPackages step.
1146            nobuildretry=False,
1147
1148            # Attempt to run this build on the same bot each time it builds.
1149            # This is only meaningful for slave builds run on swarming. This
1150            # should only be used with LUCI Builders that use a reserved
1151            # role to avoid having bots stolen by other builds while
1152            # waiting on a new master build.
1153            build_affinity=False,
1154    )
1155
1156
1157def GerritInstanceParameters(name, instance):
1158    param_names = [
1159            '_GOB_INSTANCE', '_GERRIT_INSTANCE', '_GOB_HOST', '_GERRIT_HOST',
1160            '_GOB_URL', '_GERRIT_URL'
1161    ]
1162
1163    gob_instance = instance
1164    gerrit_instance = '%s-review' % instance
1165    gob_host = constants.GOB_HOST % gob_instance
1166    gerrit_host = constants.GOB_HOST % gerrit_instance
1167    gob_url = 'https://%s' % gob_host
1168    gerrit_url = 'https://%s' % gerrit_host
1169
1170    params = [
1171            gob_instance, gerrit_instance, gob_host, gerrit_host, gob_url,
1172            gerrit_url
1173    ]
1174
1175    return dict([('%s%s' % (name, pn), p)
1176                 for pn, p in zip(param_names, params)])
1177
1178
1179def DefaultSiteParameters():
1180    # Enumeration of valid site parameters; any/all site parameters must be here.
1181    # All site parameters should be documented.
1182    default_site_params = {}
1183
1184    manifest_project = 'chromiumos/manifest'
1185    manifest_int_project = 'chromeos/manifest-internal'
1186    external_remote = 'cros'
1187    internal_remote = 'cros-internal'
1188    chromium_remote = 'chromium'
1189    chrome_remote = 'chrome'
1190    aosp_remote = 'aosp'
1191    weave_remote = 'weave'
1192
1193    internal_change_prefix = 'chrome-internal:'
1194    external_change_prefix = 'chromium:'
1195
1196    # Gerrit instance site parameters.
1197    default_site_params.update(GerritInstanceParameters(
1198            'EXTERNAL', 'chromium'))
1199    default_site_params.update(
1200            GerritInstanceParameters('INTERNAL', 'chrome-internal'))
1201    default_site_params.update(GerritInstanceParameters('AOSP', 'android'))
1202    default_site_params.update(GerritInstanceParameters('WEAVE', 'weave'))
1203
1204    default_site_params.update(
1205            # Parameters to define which manifests to use.
1206            MANIFEST_PROJECT=manifest_project,
1207            MANIFEST_INT_PROJECT=manifest_int_project,
1208            MANIFEST_PROJECTS=(manifest_project, manifest_int_project),
1209            MANIFEST_URL=os.path.join(default_site_params['EXTERNAL_GOB_URL'],
1210                                      manifest_project),
1211            MANIFEST_INT_URL=os.path.join(
1212                    default_site_params['INTERNAL_GERRIT_URL'],
1213                    manifest_int_project),
1214
1215            # CrOS remotes specified in the manifests.
1216            EXTERNAL_REMOTE=external_remote,
1217            INTERNAL_REMOTE=internal_remote,
1218            GOB_REMOTES={
1219                    default_site_params['EXTERNAL_GOB_INSTANCE']:
1220                    external_remote,
1221                    default_site_params['INTERNAL_GOB_INSTANCE']:
1222                    internal_remote,
1223            },
1224            CHROMIUM_REMOTE=chromium_remote,
1225            CHROME_REMOTE=chrome_remote,
1226            AOSP_REMOTE=aosp_remote,
1227            WEAVE_REMOTE=weave_remote,
1228
1229            # Only remotes listed in CROS_REMOTES are considered branchable.
1230            # CROS_REMOTES and BRANCHABLE_PROJECTS must be kept in sync.
1231            GERRIT_HOSTS={
1232                    external_remote:
1233                    default_site_params['EXTERNAL_GERRIT_HOST'],
1234                    internal_remote:
1235                    default_site_params['INTERNAL_GERRIT_HOST'],
1236                    aosp_remote: default_site_params['AOSP_GERRIT_HOST'],
1237                    weave_remote: default_site_params['WEAVE_GERRIT_HOST'],
1238            },
1239            CROS_REMOTES={
1240                    external_remote: default_site_params['EXTERNAL_GOB_URL'],
1241                    internal_remote: default_site_params['INTERNAL_GOB_URL'],
1242                    aosp_remote: default_site_params['AOSP_GOB_URL'],
1243                    weave_remote: default_site_params['WEAVE_GOB_URL'],
1244            },
1245            GIT_REMOTES={
1246                    chromium_remote: default_site_params['EXTERNAL_GOB_URL'],
1247                    chrome_remote: default_site_params['INTERNAL_GOB_URL'],
1248                    external_remote: default_site_params['EXTERNAL_GOB_URL'],
1249                    internal_remote: default_site_params['INTERNAL_GOB_URL'],
1250                    aosp_remote: default_site_params['AOSP_GOB_URL'],
1251                    weave_remote: default_site_params['WEAVE_GOB_URL'],
1252            },
1253
1254            # Prefix to distinguish internal and external changes. This is used
1255            # when a user specifies a patch with "-g", when generating a key for
1256            # a patch to use in our PatchCache, and when displaying a custom
1257            # string for the patch.
1258            INTERNAL_CHANGE_PREFIX=internal_change_prefix,
1259            EXTERNAL_CHANGE_PREFIX=external_change_prefix,
1260            CHANGE_PREFIX={
1261                    external_remote: external_change_prefix,
1262                    internal_remote: internal_change_prefix,
1263            },
1264
1265            # List of remotes that are okay to include in the external manifest.
1266            EXTERNAL_REMOTES=(
1267                    external_remote,
1268                    chromium_remote,
1269                    aosp_remote,
1270                    weave_remote,
1271            ),
1272
1273            # Mapping 'remote name' -> regexp that matches names of repositories on
1274            # that remote that can be branched when creating CrOS branch.
1275            # Branching script will actually create a new git ref when branching
1276            # these projects. It won't attempt to create a git ref for other projects
1277            # that may be mentioned in a manifest. If a remote is missing from this
1278            # dictionary, all projects on that remote are considered to not be
1279            # branchable.
1280            BRANCHABLE_PROJECTS={
1281                    external_remote: r'(chromiumos|aosp)/(.+)',
1282                    internal_remote: r'chromeos/(.+)',
1283            },
1284
1285            # Additional parameters used to filter manifests, create modified
1286            # manifests, and to branch manifests.
1287            MANIFEST_VERSIONS_GOB_URL=(
1288                    '%s/chromiumos/manifest-versions' %
1289                    default_site_params['EXTERNAL_GOB_URL']),
1290            MANIFEST_VERSIONS_GOB_URL_TEST=(
1291                    '%s/chromiumos/manifest-versions-test' %
1292                    default_site_params['EXTERNAL_GOB_URL']),
1293            MANIFEST_VERSIONS_INT_GOB_URL=(
1294                    '%s/chromeos/manifest-versions' %
1295                    default_site_params['INTERNAL_GOB_URL']),
1296            MANIFEST_VERSIONS_INT_GOB_URL_TEST=(
1297                    '%s/chromeos/manifest-versions-test' %
1298                    default_site_params['INTERNAL_GOB_URL']),
1299            MANIFEST_VERSIONS_GS_URL='gs://chromeos-manifest-versions',
1300
1301            # Standard directories under buildroot for cloning these repos.
1302            EXTERNAL_MANIFEST_VERSIONS_PATH='manifest-versions',
1303            INTERNAL_MANIFEST_VERSIONS_PATH='manifest-versions-internal',
1304
1305            # GS URL in which to archive build artifacts.
1306            ARCHIVE_URL='gs://chromeos-image-archive',
1307    )
1308
1309    return default_site_params
1310
1311
1312class SiteConfig(dict):
1313    """This holds a set of named BuildConfig values."""
1314
1315    def __init__(self, defaults=None, templates=None):
1316        """Init.
1317
1318    Args:
1319      defaults: Dictionary of key value pairs to use as BuildConfig values.
1320                All BuildConfig values should be defined here. If None,
1321                the DefaultSettings() is used. Most sites should use
1322                DefaultSettings(), and then update to add any site specific
1323                values needed.
1324      templates: Dictionary of template names to partial BuildConfigs
1325                 other BuildConfigs can be based on. Mostly used to reduce
1326                 verbosity of the config dump file format.
1327    """
1328        super(SiteConfig, self).__init__()
1329        self._defaults = DefaultSettings()
1330        if defaults:
1331            self._defaults.update(defaults)
1332        self._templates = AttrDict() if templates is None else AttrDict(
1333                templates)
1334
1335    def GetDefault(self):
1336        """Create the canonical default build configuration."""
1337        # Enumeration of valid settings; any/all config settings must be in this.
1338        # All settings must be documented.
1339        return BuildConfig(**self._defaults)
1340
1341    def GetTemplates(self):
1342        """Get the templates of the build configs"""
1343        return self._templates
1344
1345    @property
1346    def templates(self):
1347        return self._templates
1348
1349    #
1350    # Methods for searching a SiteConfig's contents.
1351    #
1352    def GetBoards(self):
1353        """Return an iterable of all boards in the SiteConfig."""
1354        return set(
1355                itertools.chain.from_iterable(x.boards for x in self.values()
1356                                              if x.boards))
1357
1358    def FindFullConfigsForBoard(self, board=None):
1359        """Returns full builder configs for a board.
1360
1361    Args:
1362      board: The board to match. By default, match all boards.
1363
1364    Returns:
1365      A tuple containing a list of matching external configs and a list of
1366      matching internal release configs for a board.
1367    """
1368        ext_cfgs = []
1369        int_cfgs = []
1370
1371        for name, c in self.items():
1372            if c['boards'] and (board is None or board in c['boards']):
1373                if name.endswith(
1374                        '-%s' % CONFIG_TYPE_RELEASE) and c['internal']:
1375                    int_cfgs.append(c.deepcopy())
1376                elif name.endswith(
1377                        '-%s' % CONFIG_TYPE_FULL) and not c['internal']:
1378                    ext_cfgs.append(c.deepcopy())
1379
1380        return ext_cfgs, int_cfgs
1381
1382    def FindCanonicalConfigForBoard(self, board, allow_internal=True):
1383        """Get the canonical cbuildbot builder config for a board."""
1384        ext_cfgs, int_cfgs = self.FindFullConfigsForBoard(board)
1385        # If both external and internal builds exist for this board, prefer the
1386        # internal one unless instructed otherwise.
1387        both = (int_cfgs if allow_internal else []) + ext_cfgs
1388
1389        if not both:
1390            raise ValueError('Invalid board specified: %s.' % board)
1391        return both[0]
1392
1393    def GetSlaveConfigMapForMaster(self,
1394                                   master_config,
1395                                   options=None,
1396                                   important_only=True):
1397        """Gets the slave builds triggered by a master config.
1398
1399    If a master builder also performs a build, it can (incorrectly) return
1400    itself.
1401
1402    Args:
1403      master_config: A build config for a master builder.
1404      options: The options passed on the commandline. This argument is required
1405      for normal operation, but we accept None to assist with testing.
1406      important_only: If True, only get the important slaves.
1407
1408    Returns:
1409      A slave_name to slave_config map, corresponding to the slaves for the
1410      master represented by master_config.
1411
1412    Raises:
1413      AssertionError if the given config is not a master config or it does
1414        not have a manifest_version.
1415    """
1416        assert master_config.master
1417        assert master_config.slave_configs is not None
1418
1419        slave_name_config_map = {}
1420        if options is not None and options.remote_trybot:
1421            return {}
1422
1423        # Look up the build configs for all slaves named by the master.
1424        slave_name_config_map = {
1425                name: self[name]
1426                for name in master_config.slave_configs
1427        }
1428
1429        if important_only:
1430            # Remove unimportant configs from the result.
1431            slave_name_config_map = {
1432                    k: v
1433                    for k, v in slave_name_config_map.items() if v.important
1434            }
1435
1436        return slave_name_config_map
1437
1438    def GetSlavesForMaster(self,
1439                           master_config,
1440                           options=None,
1441                           important_only=True):
1442        """Get a list of qualified build slave configs given the master_config.
1443
1444    Args:
1445      master_config: A build config for a master builder.
1446      options: The options passed on the commandline. This argument is optional,
1447               and only makes sense when called from cbuildbot.
1448      important_only: If True, only get the important slaves.
1449    """
1450        slave_map = self.GetSlaveConfigMapForMaster(
1451                master_config, options=options, important_only=important_only)
1452        return list(slave_map.values())
1453
1454    #
1455    # Methods used when creating a Config programmatically.
1456    #
1457    def Add(self, name, template=None, *args, **kwargs):
1458        """Add a new BuildConfig to the SiteConfig.
1459
1460    Examples:
1461      # Creates default build named foo.
1462      site_config.Add('foo')
1463
1464      # Creates default build with board 'foo_board'
1465      site_config.Add('foo',
1466                      boards=['foo_board'])
1467
1468      # Creates build based on template_build for 'foo_board'.
1469      site_config.Add('foo',
1470                      template_build,
1471                      boards=['foo_board'])
1472
1473      # Creates build based on template for 'foo_board'. with mixin.
1474      # Inheritance order is default, template, mixin, arguments.
1475      site_config.Add('foo',
1476                      template_build,
1477                      mixin_build_config,
1478                      boards=['foo_board'])
1479
1480      # Creates build without a template but with mixin.
1481      # Inheritance order is default, template, mixin, arguments.
1482      site_config.Add('foo',
1483                      None,
1484                      mixin_build_config,
1485                      boards=['foo_board'])
1486
1487    Args:
1488      name: The name to label this configuration; this is what cbuildbot
1489            would see.
1490      template: BuildConfig to use as a template for this build.
1491      args: BuildConfigs to patch into this config. First one (if present) is
1492            considered the template. See AddTemplate for help on templates.
1493      kwargs: BuildConfig values to explicitly set on this config.
1494
1495    Returns:
1496      The BuildConfig just added to the SiteConfig.
1497    """
1498        assert name not in self, ('%s already exists.' % name)
1499
1500        inherits, overrides = args, kwargs
1501        if template:
1502            inherits = (template, ) + inherits
1503
1504        # Make sure we don't ignore that argument silently.
1505        if '_template' in overrides:
1506            raise ValueError('_template cannot be explicitly set.')
1507
1508        result = self.GetDefault()
1509        result.apply(*inherits, **overrides)
1510
1511        # Select the template name based on template argument, or nothing.
1512        resolved_template = template.get('_template') if template else None
1513        assert not resolved_template or resolved_template in self.templates, \
1514            '%s inherits from non-template %s' % (name, resolved_template)
1515
1516        # Our name is passed as an explicit argument. We use the first build
1517        # config as our template, or nothing.
1518        result['name'] = name
1519        result['_template'] = resolved_template
1520        self[name] = result
1521        return result
1522
1523    def AddWithoutTemplate(self, name, *args, **kwargs):
1524        """Add a config containing only explicitly listed values (no defaults)."""
1525        self.Add(name, None, *args, **kwargs)
1526
1527    def AddGroup(self, name, *args, **kwargs):
1528        """Create a new group of build configurations.
1529
1530    Args:
1531      name: The name to label this configuration; this is what cbuildbot
1532            would see.
1533      args: Configurations to build in this group. The first config in
1534            the group is considered the primary configuration and is used
1535            for syncing and creating the chroot.
1536      kwargs: Override values to use for the parent config.
1537
1538    Returns:
1539      A new BuildConfig instance.
1540    """
1541        child_configs = [x.deepcopy().apply(grouped=True) for x in args]
1542        return self.Add(name, args[0], child_configs=child_configs, **kwargs)
1543
1544    def AddForBoards(self,
1545                     suffix,
1546                     boards,
1547                     per_board=None,
1548                     template=None,
1549                     *args,
1550                     **kwargs):
1551        """Create configs for all boards in |boards|.
1552
1553    Args:
1554      suffix: Config name is <board>-<suffix>.
1555      boards: A list of board names as strings.
1556      per_board: A dictionary of board names to BuildConfigs, or None.
1557      template: The template to use for all configs created.
1558      *args: Mixin templates to apply.
1559      **kwargs: Additional keyword arguments to be used in AddConfig.
1560
1561    Returns:
1562      List of the configs created.
1563    """
1564        result = []
1565
1566        for board in boards:
1567            config_name = '%s-%s' % (board, suffix)
1568
1569            # Insert the per_board value as the last mixin, if it exists.
1570            mixins = args + (dict(boards=[board]), )
1571            if per_board and board in per_board:
1572                mixins = mixins + (per_board[board], )
1573
1574            # Create the new config for this board.
1575            result.append(self.Add(config_name, template, *mixins, **kwargs))
1576
1577        return result
1578
1579    def ApplyForBoards(self, suffix, boards, *args, **kwargs):
1580        """Update configs for all boards in |boards|.
1581
1582    Args:
1583      suffix: Config name is <board>-<suffix>.
1584      boards: A list of board names as strings.
1585      *args: Mixin templates to apply.
1586      **kwargs: Additional keyword arguments to be used in AddConfig.
1587
1588    Returns:
1589      List of the configs updated.
1590    """
1591        result = []
1592
1593        for board in boards:
1594            config_name = '%s-%s' % (board, suffix)
1595            assert config_name in self, ('%s does not exist.' % config_name)
1596
1597            # Update the config for this board.
1598            result.append(self[config_name].apply(*args, **kwargs))
1599
1600        return result
1601
1602    def AddTemplate(self, name, *args, **kwargs):
1603        """Create a template named |name|.
1604
1605    Templates are used to define common settings that are shared across types
1606    of builders. They help reduce duplication in config_dump.json, because we
1607    only define the template and its settings once.
1608
1609    Args:
1610      name: The name of the template.
1611      args: See the docstring of BuildConfig.derive.
1612      kwargs: See the docstring of BuildConfig.derive.
1613    """
1614        assert name not in self._templates, ('Template %s already exists.' %
1615                                             name)
1616
1617        template = BuildConfig()
1618        template.apply(*args, **kwargs)
1619        template['_template'] = name
1620        self._templates[name] = template
1621
1622        return template
1623
1624    def _MarshalBuildConfig(self, name, config):
1625        """Hide the defaults from a given config entry.
1626
1627    Args:
1628      name: Default build name (usually dictionary key).
1629      config: A config entry.
1630
1631    Returns:
1632      The same config entry, but without any defaults.
1633    """
1634        defaults = self.GetDefault()
1635        defaults['name'] = name
1636
1637        template = config.get('_template')
1638        if template:
1639            defaults.apply(self._templates[template])
1640            defaults['_template'] = None
1641
1642        result = {}
1643        for k, v in config.items():
1644            if defaults.get(k) != v:
1645                if k == 'child_configs':
1646                    result['child_configs'] = [
1647                            self._MarshalBuildConfig(name, child)
1648                            for child in v
1649                    ]
1650                else:
1651                    result[k] = v
1652
1653        return result
1654
1655    def _MarshalTemplates(self):
1656        """Return a version of self._templates with only used templates.
1657
1658    Templates have callables/delete keys resolved against GetDefault() to
1659    ensure they can be safely saved to json.
1660
1661    Returns:
1662      Dict copy of self._templates with all unreferenced templates removed.
1663    """
1664        defaults = self.GetDefault()
1665
1666        # All templates used. We ignore child configs since they
1667        # should exist at top level.
1668        used = set(c.get('_template', None) for c in self.values())
1669        used.discard(None)
1670
1671        result = {}
1672
1673        for name in used:
1674            # Expand any special values (callables, etc)
1675            expanded = defaults.derive(self._templates[name])
1676            # Recover the '_template' value which is filtered out by derive.
1677            expanded['_template'] = name
1678            # Hide anything that matches the default.
1679            save = {k: v for k, v in expanded.items() if defaults.get(k) != v}
1680            result[name] = save
1681
1682        return result
1683
1684    def SaveConfigToString(self):
1685        """Save this Config object to a Json format string."""
1686        default = self.GetDefault()
1687
1688        config_dict = {}
1689        config_dict['_default'] = default
1690        config_dict['_templates'] = self._MarshalTemplates()
1691        for k, v in self.items():
1692            config_dict[k] = self._MarshalBuildConfig(k, v)
1693
1694        return PrettyJsonDict(config_dict)
1695
1696    def SaveConfigToFile(self, config_file):
1697        """Save this Config to a Json file.
1698
1699    Args:
1700      config_file: The file to write too.
1701    """
1702        json_string = self.SaveConfigToString()
1703        osutils.WriteFile(config_file, json_string)
1704
1705    def DumpExpandedConfigToString(self):
1706        """Dump the SiteConfig to Json with all configs full expanded.
1707
1708    This is intended for debugging default/template behavior. The dumped JSON
1709    can't be reloaded (at least not reliably).
1710    """
1711        return PrettyJsonDict(self)
1712
1713    def DumpConfigCsv(self):
1714        """Dump the SiteConfig to CSV with all configs fully expanded.
1715
1716    This supports configuration analysis and debugging.
1717    """
1718        raw_config = json.loads(self.DumpExpandedConfigToString())
1719        header_keys = {'builder_name', 'test_type', 'device'}
1720        csv_rows = []
1721        for builder_name, values in raw_config.items():
1722            row = {'builder_name': builder_name}
1723            tests = {}
1724            raw_devices = []
1725            for key, value in values.items():
1726                header_keys.add(key)
1727                if value:
1728                    if isinstance(value, list):
1729                        if '_tests' in key:
1730                            tests[key] = value
1731                        elif key == 'models':
1732                            raw_devices = value
1733                        else:
1734                            # Ignoring this for now for test analysis.
1735                            if key != 'child_configs':
1736                                row[key] = ' | '.join(
1737                                        str(array_val) for array_val in value)
1738                    else:
1739                        row[key] = value
1740
1741            if tests:
1742                for test_type, test_entries in tests.items():
1743                    for test_entry in test_entries:
1744                        test_row = copy.deepcopy(row)
1745                        test_row['test_type'] = test_type
1746                        raw_test = json.loads(test_entry)
1747                        for test_key, test_value in raw_test.items():
1748                            if test_value:
1749                                header_keys.add(test_key)
1750                                test_row[test_key] = test_value
1751                        csv_rows.append(test_row)
1752                        if raw_devices:
1753                            for raw_device in raw_devices:
1754                                device = json.loads(raw_device)
1755                                test_suite = test_row.get('suite', '')
1756                                test_suites = device.get('test_suites', [])
1757                                if test_suite and test_suites and test_suite in test_suites:
1758                                    device_row = copy.deepcopy(test_row)
1759                                    device_row['device'] = device['name']
1760                                    csv_rows.append(device_row)
1761            else:
1762                csv_rows.append(row)
1763
1764        csv_result = [','.join(header_keys)]
1765        for csv_row in csv_rows:
1766            row_values = []
1767            for header_key in header_keys:
1768                row_values.append('"%s"' % str(csv_row.get(header_key, '')))
1769            csv_result.append(','.join(row_values))
1770
1771        return '\n'.join(csv_result)
1772
1773
1774#
1775# Functions related to working with GE Data.
1776#
1777
1778
1779def LoadGEBuildConfigFromFile(
1780    build_settings_file=constants.GE_BUILD_CONFIG_FILE):
1781    """Load template config dict from a Json encoded file."""
1782    json_string = osutils.ReadFile(build_settings_file)
1783    return json.loads(json_string)
1784
1785
1786def GeBuildConfigAllBoards(ge_build_config):
1787    """Extract a list of board names from the GE Build Config.
1788
1789  Args:
1790    ge_build_config: Dictionary containing the decoded GE configuration file.
1791
1792  Returns:
1793    A list of board names as strings.
1794  """
1795    return [b['name'] for b in ge_build_config['boards']]
1796
1797
1798def GetUnifiedBuildConfigAllBuilds(ge_build_config):
1799    """Extract a list of all unified build configurations.
1800
1801  This dictionary is based on the JSON defined by the proto generated from
1802  GoldenEye.  See cs/crosbuilds.proto
1803
1804  Args:
1805    ge_build_config: Dictionary containing the decoded GE configuration file.
1806
1807  Returns:
1808    A list of unified build configurations (json configs)
1809  """
1810    return ge_build_config.get('reference_board_unified_builds', [])
1811
1812
1813class BoardGroup(object):
1814    """Class holds leader_boards and follower_boards for grouped boards"""
1815
1816    def __init__(self):
1817        self.leader_boards = []
1818        self.follower_boards = []
1819
1820    def AddLeaderBoard(self, board):
1821        self.leader_boards.append(board)
1822
1823    def AddFollowerBoard(self, board):
1824        self.follower_boards.append(board)
1825
1826    def __str__(self):
1827        return ('Leader_boards: %s Follower_boards: %s' %
1828                (self.leader_boards, self.follower_boards))
1829
1830
1831def GroupBoardsByBuilderAndBoardGroup(board_list):
1832    """Group boards by builder and board_group.
1833
1834  Args:
1835    board_list: board list from the template file.
1836
1837  Returns:
1838    builder_group_dict: maps builder to {group_n: board_group_n}
1839    builder_ungrouped_dict: maps builder to a list of ungrouped boards
1840  """
1841    builder_group_dict = {}
1842    builder_ungrouped_dict = {}
1843
1844    for b in board_list:
1845        name = b[CONFIG_TEMPLATE_NAME]
1846        # Invalid build configs being written out with no config templates,
1847        # thus the default. See https://crbug.com/1012278.
1848        for config in b.get(CONFIG_TEMPLATE_CONFIGS, []):
1849            board = {'name': name}
1850            board.update(config)
1851
1852            builder = config[CONFIG_TEMPLATE_BUILDER]
1853            if builder not in builder_group_dict:
1854                builder_group_dict[builder] = {}
1855            if builder not in builder_ungrouped_dict:
1856                builder_ungrouped_dict[builder] = []
1857
1858            board_group = config[CONFIG_TEMPLATE_BOARD_GROUP]
1859            if not board_group:
1860                builder_ungrouped_dict[builder].append(board)
1861                continue
1862            if board_group not in builder_group_dict[builder]:
1863                builder_group_dict[builder][board_group] = BoardGroup()
1864            if config[CONFIG_TEMPLATE_LEADER_BOARD]:
1865                builder_group_dict[builder][board_group].AddLeaderBoard(board)
1866            else:
1867                builder_group_dict[builder][board_group].AddFollowerBoard(
1868                        board)
1869
1870    return (builder_group_dict, builder_ungrouped_dict)
1871
1872
1873def GroupBoardsByBuilder(board_list):
1874    """Group boards by the 'builder' flag."""
1875    builder_to_boards_dict = {}
1876
1877    for b in board_list:
1878        # Invalid build configs being written out with no configs array, thus the
1879        # default. See https://crbug.com/1005803.
1880        for config in b.get(CONFIG_TEMPLATE_CONFIGS, []):
1881            builder = config[CONFIG_TEMPLATE_BUILDER]
1882            if builder not in builder_to_boards_dict:
1883                builder_to_boards_dict[builder] = set()
1884            builder_to_boards_dict[builder].add(b[CONFIG_TEMPLATE_NAME])
1885
1886    return builder_to_boards_dict
1887
1888
1889def GetNonUniBuildLabBoardName(board):
1890    """Return the board name labeled in the lab for non-unibuild."""
1891    # Those special string represent special configuration used in the image,
1892    # and should run on DUT without those string.
1893    # We strip those string from the board so that lab can handle it correctly.
1894    SPECIAL_SUFFIX = [
1895            '-arcnext$',
1896            '-arcvm$',
1897            '-arc-r$',
1898            '-arc-r-userdebug$',
1899            '-connectivitynext$',
1900            '-kernelnext$',
1901            '-kvm$',
1902            '-ndktranslation$',
1903            '-cfm$',
1904            '-campfire$',
1905            '-borealis$',
1906    ]
1907    # ARM64 userspace boards use 64 suffix but can't put that in list above
1908    # because of collisions with boards like kevin-arc64.
1909    ARM64_BOARDS = ['cheza64', 'kevin64']
1910    for suffix in SPECIAL_SUFFIX:
1911        board = re.sub(suffix, '', board)
1912    if board in ARM64_BOARDS:
1913        # Remove '64' suffix from the board name.
1914        board = board[:-2]
1915    return board
1916
1917
1918def GetArchBoardDict(ge_build_config):
1919    """Get a dict mapping arch types to board names.
1920
1921  Args:
1922    ge_build_config: Dictionary containing the decoded GE configuration file.
1923
1924  Returns:
1925    A dict mapping arch types to board names.
1926  """
1927    arch_board_dict = {}
1928
1929    for b in ge_build_config[CONFIG_TEMPLATE_BOARDS]:
1930        board_name = b[CONFIG_TEMPLATE_NAME]
1931        # Invalid build configs being written out with no configs array, thus the
1932        # default. See https://crbug.com/947712.
1933        for config in b.get(CONFIG_TEMPLATE_CONFIGS, []):
1934            arch = config[CONFIG_TEMPLATE_ARCH]
1935            arch_board_dict.setdefault(arch, set()).add(board_name)
1936
1937    for b in GetUnifiedBuildConfigAllBuilds(ge_build_config):
1938        board_name = b[CONFIG_TEMPLATE_REFERENCE_BOARD_NAME]
1939        arch = b[CONFIG_TEMPLATE_ARCH]
1940        arch_board_dict.setdefault(arch, set()).add(board_name)
1941
1942    return arch_board_dict
1943
1944
1945#
1946# Functions related to loading/saving Json.
1947#
1948class ObjectJSONEncoder(json.JSONEncoder):
1949    """Json Encoder that encodes objects as their dictionaries."""
1950
1951    # pylint: disable=method-hidden
1952    def default(self, o):
1953        return self.encode(o.__dict__)
1954
1955
1956def PrettyJsonDict(dictionary):
1957    """Returns a pretty-ified json dump of a dictionary."""
1958    return json.dumps(dictionary,
1959                      cls=ObjectJSONEncoder,
1960                      sort_keys=True,
1961                      indent=4,
1962                      separators=(',', ': ')) + '\n'
1963
1964
1965def LoadConfigFromFile(config_file=constants.CHROMEOS_CONFIG_FILE):
1966    """Load a Config a Json encoded file."""
1967    json_string = osutils.ReadFile(config_file)
1968    return LoadConfigFromString(json_string)
1969
1970
1971def LoadConfigFromString(json_string):
1972    """Load a cbuildbot config from it's Json encoded string."""
1973    config_dict = json.loads(json_string)
1974
1975    # Use standard defaults, but allow the config to override.
1976    defaults = DefaultSettings()
1977    defaults.update(config_dict.pop(DEFAULT_BUILD_CONFIG))
1978    _DeserializeConfigs(defaults)
1979
1980    templates = config_dict.pop('_templates', {})
1981    for t in templates.values():
1982        _DeserializeConfigs(t)
1983
1984    defaultBuildConfig = BuildConfig(**defaults)
1985
1986    builds = {
1987            n: _CreateBuildConfig(n, defaultBuildConfig, v, templates)
1988            for n, v in config_dict.items()
1989    }
1990
1991    # config is the struct that holds the complete cbuildbot config.
1992    result = SiteConfig(defaults=defaults, templates=templates)
1993    result.update(builds)
1994
1995    return result
1996
1997
1998def _DeserializeConfig(build_dict,
1999                       config_key,
2000                       config_class,
2001                       preserve_none=False):
2002    """Deserialize config of given type inside build_dict.
2003
2004  Args:
2005    build_dict: The build_dict to update (in place)
2006    config_key: Key for the config inside build_dict.
2007    config_class: The class to instantiate for the config.
2008    preserve_none: If True, None values are preserved as is. By default, they
2009        are dropped.
2010  """
2011    serialized_configs = build_dict.pop(config_key, None)
2012    if serialized_configs is None:
2013        if preserve_none:
2014            build_dict[config_key] = None
2015        return
2016
2017    deserialized_configs = []
2018    for config_string in serialized_configs:
2019        if isinstance(config_string, config_class):
2020            deserialized_config = config_string
2021        else:
2022            # Each test config is dumped as a json string embedded in json.
2023            embedded_configs = json.loads(config_string)
2024            deserialized_config = config_class(**embedded_configs)
2025        deserialized_configs.append(deserialized_config)
2026    build_dict[config_key] = deserialized_configs
2027
2028
2029def _DeserializeConfigs(build_dict):
2030    """Updates a config dictionary with recreated objects.
2031
2032  Notification configs and various test configs are serialized as strings
2033  (rather than JSON objects), so we need to turn them into real objects before
2034  they can be consumed.
2035
2036  Args:
2037    build_dict: The config dictionary to update (in place).
2038  """
2039    _DeserializeConfig(build_dict, 'vm_tests', VMTestConfig)
2040    _DeserializeConfig(build_dict,
2041                       'vm_tests_override',
2042                       VMTestConfig,
2043                       preserve_none=True)
2044    _DeserializeConfig(build_dict, 'models', ModelTestConfig)
2045    _DeserializeConfig(build_dict, 'hw_tests', HWTestConfig)
2046    _DeserializeConfig(build_dict,
2047                       'hw_tests_override',
2048                       HWTestConfig,
2049                       preserve_none=True)
2050    _DeserializeConfig(build_dict, 'gce_tests', GCETestConfig)
2051    _DeserializeConfig(build_dict, 'tast_vm_tests', TastVMTestConfig)
2052    _DeserializeConfig(build_dict, 'moblab_vm_tests', MoblabVMTestConfig)
2053    _DeserializeConfig(build_dict, 'notification_configs', NotificationConfig)
2054
2055
2056def _CreateBuildConfig(name, default, build_dict, templates):
2057    """Create a BuildConfig object from it's parsed JSON dictionary encoding."""
2058    # These build config values need special handling.
2059    child_configs = build_dict.pop('child_configs', None)
2060    template = build_dict.get('_template')
2061
2062    # Use the name passed in as the default build name.
2063    build_dict.setdefault('name', name)
2064
2065    result = default.deepcopy()
2066    # Use update to explicitly avoid apply's special handing.
2067    if template:
2068        result.update(templates[template])
2069    result.update(build_dict)
2070
2071    _DeserializeConfigs(result)
2072
2073    if child_configs is not None:
2074        result['child_configs'] = [
2075                _CreateBuildConfig(name, default, child, templates)
2076                for child in child_configs
2077        ]
2078
2079    return result
2080
2081
2082@memoize.Memoize
2083def GetConfig():
2084    """Load the current SiteConfig.
2085
2086  Returns:
2087    SiteConfig instance to use for this build.
2088  """
2089    return LoadConfigFromFile(constants.CHROMEOS_CONFIG_FILE)
2090
2091
2092@memoize.Memoize
2093def GetSiteParams():
2094    """Get the site parameter configs.
2095
2096  This is the new, preferred method of accessing the site parameters, instead of
2097  SiteConfig.params.
2098
2099  Returns:
2100    AttrDict of site parameters
2101  """
2102    site_params = AttrDict()
2103    site_params.update(DefaultSiteParameters())
2104    return site_params
2105
2106
2107def append_useflags(useflags):
2108    """Used to append a set of useflags to existing useflags.
2109
2110  Useflags that shadow prior use flags will cause the prior flag to be removed.
2111  (e.g. appending '-foo' to 'foo' will cause 'foo' to be removed)
2112
2113  Examples:
2114    new_config = base_config.derive(useflags=append_useflags(['foo', '-bar'])
2115
2116  Args:
2117    useflags: List of string useflags to append.
2118  """
2119    assert isinstance(useflags, (list, set))
2120    shadowed_useflags = {
2121            '-' + flag
2122            for flag in useflags if not flag.startswith('-')
2123    }
2124    shadowed_useflags.update(
2125            {flag[1:]
2126             for flag in useflags if flag.startswith('-')})
2127
2128    def handler(old_useflags):
2129        new_useflags = set(old_useflags or [])
2130        new_useflags.update(useflags)
2131        new_useflags.difference_update(shadowed_useflags)
2132        return sorted(list(new_useflags))
2133
2134    return handler
2135