blob: 82789c4c1118bf2b45a86b3067808a71d2166848 [file] [log] [blame]
# Copyright 2021 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.
"""Wrapper for 'pw presubmit' in the project source tree."""
import collections
import attr
from recipe_engine import config_types, recipe_api
from RECIPE_MODULES.fuchsia.utils import memoize
@attr.s
class Step(object):
_api = attr.ib()
name = attr.ib()
dir = attr.ib()
_export_dir_name = attr.ib(default=None)
@property
def export_dir(self):
if not self._export_dir_name:
return None # pragma: no cover
return self.dir.join(self._export_dir_name)
class PwPresubmitApi(recipe_api.RecipeApi):
"""Calls to checkout code."""
def __init__(self, props, *args, **kwargs):
super(PwPresubmitApi, self).__init__(*args, **kwargs)
self._command_name = props.command_name or 'python -m pw_cli'
self._input_steps = list(props.step)
self._input_programs = list(props.program)
self._only_on_changed_files = props.only_on_changed_files
self._export_dir_name = props.export_dir_name
self._root = None
self._checkout_root = None
self._step_objects = None
@property
def command_name(self):
return self._command_name
@property
def root(self):
return self._root
@property
def export_dir_name(self):
return self._export_dir_name
def _step(self, name):
return Step(
self.m,
name,
self.root.join(name),
export_dir_name=self.export_dir_name,
)
def init(self, checkout_root):
if not self._input_steps and not self._input_programs:
raise self.m.step.StepFailure('no step or program properties')
self._root = self.m.path['start_dir'].join('presubmit')
self._checkout_root = checkout_root
self._step_objects = collections.OrderedDict()
for step_name in self._input_steps:
self._step_objects[step_name] = self._step(step_name)
if self._input_programs:
with self.m.step.nest('get steps from programs'):
for program in self._input_programs:
# To get step_test_data line to pass pylint.
raw_io_stream_output = (
self.m.raw_io.test_api.stream_output_text
)
program_steps = (
self._run(
['--program', program, '--only-list-steps'],
name=program,
stdout=self.m.raw_io.output_text(),
step_test_data=lambda: raw_io_stream_output(
'{0}_0\n{0}_1\n'.format(program),
),
)
.stdout.strip()
.splitlines()
)
for step_name in program_steps:
self._step_objects[step_name] = self._step(step_name)
if not self._step_objects:
raise self.m.step.StepFailure('no steps to execute')
def steps(self):
# We shouldn't get to here, but in case the caller doesn't call init()
# first we'll check anyway.
if not self._step_objects: # pragma: no cover
raise self.m.step.StepFailure('no steps to execute')
return self._step_objects.values()
def _step_timeout(self):
# Amount of time elapsed in the run.
elapsed_time = (
self.m.time.time() - self.m.buildbucket.build.start_time.seconds
)
# Amount of time before build times out.
time_remaining = (
self.m.buildbucket.build.execution_timeout.seconds - elapsed_time
)
# Give a buffer before build times out and kill this step then. This
# should give enough time to read any logfiles and maybe upload to
# logdog/GCS before the build times out.
step_timeout = time_remaining - 60
# If the timeout would be negative or very small set it to 30 seconds.
# We likely won't have enough information to debug these steps, but in
# case they're fast there's no reason to kill them much before the
# build is terminated.
if step_timeout < 30:
step_timeout = 30
return step_timeout
def _run(self, args, name='run', **kwargs):
cmd = self._command_name.split()
cmd += [
'--directory',
self._checkout_root,
'--loglevel',
'debug',
'presubmit',
'--package-root',
self.m.path['cache'],
'--output-directory',
self._root,
]
cmd.extend(args)
return self.m.step(name, cmd, timeout=self._step_timeout(), **kwargs)
def run(self, step):
with self.m.step.nest(step.name) as pres:
args = []
if self._only_on_changed_files:
args.extend(('--base', 'HEAD~1'))
args.extend(('--step', step.name))
self._run(args, name=step.name)
if step.export_dir:
self.m.file.ensure_directory(
'mkdir {}'.format(self.export_dir_name), step.export_dir,
)
self.m.build.save_logs(step.dir, step.export_dir)