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
  154
  155
  156

infra / config / lib / branches.star [blame]

# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Library containing utilities for providing branch-specific definitions.

The module provide the `branches` struct which provides access to versions of
a subset of luci functions with an additional `branch_selector` keyword argument
that controls what branches the definition is actually executed for. If
`branch_selector` doesn't match the current branch as determined by values on
the `settings` struct in '//project.star', then the resource is not defined.

The valid branch selectors are in the `branches.selector` struct. The
following selectors cause the resource to be defined on main or if a branch
project includes the corresponding platform value in its settings:
* ANDROID_BRANCHES: platform.ANDROID
* CROS_BRANCHES: platform.CROS
* CROS_LTS_BRANCHES: platform.CROS, platform.CROS_LTS
* DESKTOP_BRANCHES: platform.LINUX, platform.MAC, platform.WINDOWS
* FUCHSIA_BRANCHES: platform.FUCHSIA
* IOS_BRANCHES: platform.IOS
* LINUX_BRANCHES: platform.LINUX
* MAC_BRANCHES: platform.MAC
* WINDOWS_BRANCHES: platform.WINDOWS

The MAIN branch selector causes a resource to be defined only on the main
project. The ALL_BRANCHES branch selector causes the resource to be defined on
all branches.

For other uses cases where execution needs to vary by branch, the following are
also accessible via the `branches` struct:
* matches - Allows library code to be written that takes branch-specific
    behavior.
* value - Allows for providing values depending on the platforms that the branch
    is running on.
* exec - Allows for conditionally executing starlark modules.
"""

load("./args.star", "args")
load("//project.star", "PLATFORMS", "platform", "settings")

def _branch_selector(tag, *, platforms = None):
    return struct(
        __branch_selector__ = tag,
        platforms = tuple(platforms or []),
    )

selector = struct(
    # Branch selectors corresponding to the individual platform values (except
    # CROS_LTS_BRANCHES also implies CROS_BRANCHES)
    ANDROID_BRANCHES = _branch_selector("ANDROID_BRANCHES", platforms = [platform.ANDROID]),
    CROS_BRANCHES = _branch_selector("CROS_BRANCHES", platforms = [platform.CROS]),
    CROS_LTS_BRANCHES = _branch_selector("CROS_LTS_BRANCHES", platforms = [platform.CROS, platform.CROS_LTS]),
    FUCHSIA_BRANCHES = _branch_selector("FUCHSIA_BRANCHES", platforms = [platform.FUCHSIA]),
    IOS_BRANCHES = _branch_selector("IOS_BRANCHES", platforms = [platform.IOS]),
    LINUX_BRANCHES = _branch_selector("LINUX_BRANCHES", platforms = [platform.LINUX]),
    MAC_BRANCHES = _branch_selector("MAC_BRANCHES", platforms = [platform.MAC]),
    WINDOWS_BRANCHES = _branch_selector("WINDOWS_BRANCHES", platforms = [platform.WINDOWS]),

    # Linux, Mac & Windows
    DESKTOP_BRANCHES = _branch_selector("DESKTOP_BRANCHES", platforms = [platform.LINUX, platform.MAC, platform.WINDOWS]),

    # Branch selector for just the main project
    MAIN = _branch_selector("MAIN"),

    # Branch selector matching all branches
    ALL_BRANCHES = _branch_selector("ALL_BRANCHES", platforms = PLATFORMS),
)

def _matches(branch_selector, *, platform = None):
    """Returns whether `branch_selector` matches the project settings.

    Args:
      * branch_selector: A single branch selector or a list of branch selectors.
      * platform: A single platform name or a list of platform names to match
        against. If not provided, the branch selectors will be matched against
        the project's platforms.

    Returns:
      True if any of the specified branch selectors matches, False otherwise.
      The main project will match any branch selector iff platform is not
      specified.
    """
    if type(platform) == type(""):
        platforms = [platform]
    elif platform == None:
        platforms = settings.platforms
    else:
        platforms = platform

    for b in args.listify(branch_selector):
        if not hasattr(b, "__branch_selector__"):
            fail("got {!r} for a branch selector, must be one of the branch_selector enum values: {}"
                .format(b, ", ".join(dir(selector))))
        for p in b.platforms:
            if p in platforms:
                return True

    return platform == None and settings.is_main

def _value(*, branch_selector, value):
    """Provide a value that varies depending on the project settings.

    Args:
      * branch_selector: A single branch selector or a list of branch selectors.
      * value: The value if the project's settings match the branch selector(s).

    Returns:
      `value` if `branch_selector` matches the project settings, None otherwise.
    """
    if _matches(branch_selector):
        return value
    return None

def _exec(module, *, branch_selector = selector.MAIN):
    """Execute `module` if `branch_selector` matches the project settings."""
    if not _matches(branch_selector):
        return
    exec(module)

def _make_branch_conditional(fn):
    def conditional_fn(*args, branch_selector = selector.MAIN, **kwargs):
        if not _matches(branch_selector):
            return None
        return fn(*args, **kwargs)

    return conditional_fn

branches = struct(
    selector = selector,

    # Branch functions
    matches = _matches,
    exec = _exec,
    value = _value,

    # Make conditional versions of luci functions that define resources
    # This does not include any of the service configurations
    # This also does not include any functions such as recipe that don't
    # generate config unless they're referred to; it doesn't cause a problem
    # if they're not referred to
    **{a: _make_branch_conditional(getattr(luci, a)) for a in (
        "realm",
        "binding",
        "bucket",
        "builder",
        "gitiles_poller",
        "list_view",
        "list_view_entry",
        "console_view",
        "console_view_entry",
        "external_console_view",
        "cq_group",
        "cq_tryjob_verifier",
    )}
)