blob: 9034b3f0aebfed2c57fa248b0e315c4f3943a45f [file]
# Copyright 2025 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Split combined triggers into separate builds by branch."""
from __future__ import annotations
import dataclasses
import urllib.parse
from typing import Any
from recipe_engine import recipe_api
from PB.go.chromium.org.luci.buildbucket.proto import (
builds_service as builds_service_pb,
)
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb
from PB.go.chromium.org.luci.scheduler.api.scheduler.v1 import (
triggers as triggers_pb,
)
from PB.recipe_engine import result as result_pb
from PB.recipe_modules.pigweed.split_triggers.properties import InputProperties
@dataclasses.dataclass(frozen=True)
class RemoteBranch:
remote: str
ref: str
class SplitTriggersApi(recipe_api.RecipeApi):
"""Split combined triggers into separate builds by branch."""
def __init__(self, props: InputProperties, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self._props = props
def __call__(self) -> result_pb.RawResult | None:
"""Split combined triggers into separate builds by branch."""
with self.m.step.nest('split triggers'):
return self._split()
def _split(self) -> result_pb.RawResult | None:
pres = self.m.step.empty('properties').presentation
pres.step_summary_text = repr(self.m.properties.thaw())
with self.m.step.nest('processing triggers'):
triggers_by_branch: dict[
RemoteBranch, list[triggers_pb.Trigger]
] = {}
for trigger in self.m.scheduler.triggers:
if trigger.WhichOneof('payload') != 'gitiles':
self.m.step.empty('one non-gitiles trigger')
continue
key = RemoteBranch(trigger.gitiles.repo, trigger.gitiles.ref)
self.m.step.empty(
f'one trigger for {key.remote} {key.ref} '
f'{trigger.gitiles.revision}',
)
triggers_by_branch.setdefault(key, [])
triggers_by_branch[key].append(trigger)
requests: list[builds_service_pb.ScheduleBuildRequest] = []
schedule_kwargs = {}
schedule_kwargs['builder'] = self.m.buildbucket.build.builder.builder
schedule_kwargs['inherit_buildsets'] = False
schedule_kwargs['experiments'] = {'pigweed.disable_git_cache': True}
schedule_kwargs['can_outlive_parent'] = True
num_branches = len(triggers_by_branch)
if num_branches == 0:
self.m.step.empty('no branches')
return None
if num_branches == 1:
self.m.step.empty('only one branch')
return None
with self.m.step.nest('filtering'):
for triggers in triggers_by_branch.values():
# The most recent commit on a branch shows up last in the
# list of commits, so we only need to trigger the build on
# it.
trigger = triggers[-1].gitiles
parsed = urllib.parse.urlparse(trigger.repo)
commit = common_pb.GitilesCommit(
host=parsed.hostname,
project=parsed.path.lstrip('/'),
id=trigger.revision,
ref=trigger.ref,
)
requests.append(
self.m.buildbucket.schedule_request(
gitiles_commit=commit,
**schedule_kwargs,
)
)
self.m.step.empty(
f'selected {trigger.repo} {trigger.ref} {trigger.revision}',
)
with self.m.step.nest(f'launching {len(requests)} builds'):
if self._props.enabled:
summary = [
f'Split the triggers into {len(requests)} based on trigger '
'branches',
]
builds = self.m.buildbucket.schedule(requests)
for build in builds:
branch = build.input.gitiles_commit.ref.removeprefix(
'refs/heads/',
)
summary.append(
f'* {branch}: {self.m.buildbucket.build_url(build=build)}',
)
return result_pb.RawResult(
summary_markdown='\n'.join(summary),
status=common_pb.SUCCESS,
)
else:
self.m.step.empty('dry-run')
for request in requests:
branch = request.gitiles_commit.ref.removeprefix(
'refs/heads/',
)
self.m.step.empty(branch)
return None