# 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",
)}
)