| Index: slave/skia_slave_scripts/build_step.py
|
| diff --git a/slave/skia_slave_scripts/build_step.py b/slave/skia_slave_scripts/build_step.py
|
| deleted file mode 100644
|
| index 9cc611bbc1547b39ff71f07f0d11a8a4ebc22b37..0000000000000000000000000000000000000000
|
| --- a/slave/skia_slave_scripts/build_step.py
|
| +++ /dev/null
|
| @@ -1,397 +0,0 @@
|
| -# Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -"""Base class for all slave-side build steps. """
|
| -
|
| -import config
|
| -# pylint: disable=W0611
|
| -import flavor_utils
|
| -import imp
|
| -import multiprocessing
|
| -import os
|
| -import shlex
|
| -import signal
|
| -import subprocess
|
| -import sys
|
| -import time
|
| -import traceback
|
| -
|
| -from playback_dirs import LocalSkpPlaybackDirs
|
| -from playback_dirs import StorageSkpPlaybackDirs
|
| -
|
| -BUILDBOT_PATH = os.path.realpath(os.path.join(
|
| - os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir))
|
| -
|
| -# Add important directories to the PYTHONPATH
|
| -sys.path.append(os.path.join(BUILDBOT_PATH))
|
| -sys.path.append(os.path.join(BUILDBOT_PATH, 'site_config'))
|
| -sys.path.append(os.path.join(BUILDBOT_PATH, 'master'))
|
| -sys.path.insert(0, os.path.join(BUILDBOT_PATH, 'common'))
|
| -
|
| -import builder_name_schema
|
| -import slave_hosts_cfg
|
| -import slaves_cfg
|
| -
|
| -from py.utils import misc
|
| -
|
| -
|
| -DEFAULT_TIMEOUT = 4800
|
| -DEFAULT_NO_OUTPUT_TIMEOUT = 3600
|
| -DEFAULT_NUM_CORES = 2
|
| -
|
| -
|
| -GM_EXPECTATIONS_FILENAME = 'expected-results.json'
|
| -GM_IGNORE_FAILURES_FILE = 'ignored-tests.txt'
|
| -
|
| -
|
| -# multiprocessing.Value doesn't accept boolean types, so we have to use an int.
|
| -INT_TRUE = 1
|
| -INT_FALSE = 0
|
| -build_step_stdout_has_written = multiprocessing.Value('i', INT_FALSE)
|
| -
|
| -
|
| -class BuildStepWarning(Exception):
|
| - pass
|
| -
|
| -
|
| -class BuildStepFailure(Exception):
|
| - pass
|
| -
|
| -
|
| -class BuildStepTimeout(Exception):
|
| - pass
|
| -
|
| -
|
| -class BuildStepLogger(object):
|
| - """ Override stdout so that we can keep track of when anything has been
|
| - logged. This enables timeouts based on how long the process has gone without
|
| - writing output.
|
| - """
|
| - def __init__(self):
|
| - self.stdout = sys.stdout
|
| - sys.stdout = self
|
| - build_step_stdout_has_written.value = INT_FALSE
|
| -
|
| - def __del__(self):
|
| - sys.stdout = self.stdout
|
| -
|
| - def fileno(self):
|
| - return self.stdout.fileno()
|
| -
|
| - def write(self, data):
|
| - build_step_stdout_has_written.value = INT_TRUE
|
| - self.stdout.write(data)
|
| -
|
| - def flush(self):
|
| - self.stdout.flush()
|
| -
|
| -
|
| -def _GetBuildSlaveID(desired_slave_name):
|
| - """ Returns the index of the build slave in the list of build slaves running
|
| - on this machine, in the form of a string. """
|
| - for host_dict in slave_hosts_cfg.SLAVE_HOSTS.itervalues():
|
| - for slave_name, slave_id, _ in host_dict.slaves:
|
| - if slave_name == desired_slave_name:
|
| - return slave_id
|
| - raise Exception('No build slave found with name %s' % desired_slave_name)
|
| -
|
| -
|
| -class BuildStep(multiprocessing.Process):
|
| -
|
| - def __init__(self, args, attempts=1, timeout=DEFAULT_TIMEOUT,
|
| - no_output_timeout=DEFAULT_NO_OUTPUT_TIMEOUT):
|
| - """ Constructs a BuildStep instance.
|
| -
|
| - args: dictionary containing arguments to this BuildStep.
|
| - attempts: how many times to try this BuildStep before giving up.
|
| - timeout: maximum time allowed for this BuildStep.
|
| - no_output_timeout: maximum time allowed for this BuildStep to run without
|
| - any output.
|
| - """
|
| - multiprocessing.Process.__init__(self)
|
| -
|
| - self._args = dict(args)
|
| -
|
| - self.timeout = timeout
|
| - self.no_output_timeout = no_output_timeout
|
| - self.attempts = attempts
|
| -
|
| - self._builder_name = args['builder_name']
|
| - self._build_number = args['build_number']
|
| - self._slavename = os.environ['TESTING_SLAVENAME']
|
| -
|
| - # Change to the correct working directory. This is needed on Windows, where
|
| - # our path lengths would otherwise be too long.
|
| - if os.name == 'nt':
|
| - curdir = os.getcwd()
|
| - # The buildslave name and builder name combo is too long. Instead, obtain
|
| - # a unique buildslave ID.
|
| - buildslave_id = _GetBuildSlaveID(self._slavename)
|
| - workdir = os.path.join('C:\\', buildslave_id, self._builder_name,
|
| - curdir[curdir.rfind('build'):])
|
| - print 'chdir to %s' % workdir
|
| - if not os.path.isdir(workdir):
|
| - os.makedirs(workdir)
|
| - os.chdir(workdir)
|
| -
|
| - # Add CWD to the PYTHONPATH
|
| - sys.path.append(os.getcwd())
|
| -
|
| - self._configuration = args['configuration']
|
| - if os.name == 'nt' and 'x86_64' in self._builder_name:
|
| - self._configuration += '_x64'
|
| -
|
| - self._target_platform = args['target_platform']
|
| - self._deps_target_os = \
|
| - None if args['deps_target_os'] == 'None' else args['deps_target_os']
|
| - self._revision = \
|
| - None if args['revision'] == 'None' or args['revision'] == 'HEAD' \
|
| - else args['revision']
|
| - self._got_revision = \
|
| - None if args['got_revision'] == 'None' else args['got_revision']
|
| -
|
| - # Import the flavor-specific build step utils module.
|
| - flavor = args.get('flavor', 'default')
|
| - try:
|
| - flavor_utils_module_name = '%s_build_step_utils' % flavor
|
| - flavor_utils_path = os.path.join(os.path.dirname(__file__),
|
| - 'flavor_utils',
|
| - '%s.py' % flavor_utils_module_name)
|
| - flavor_utils_module = imp.load_source(
|
| - 'flavor_utils.%s' % flavor_utils_module_name, flavor_utils_path)
|
| - flavor_utils_class_name = ''.join([part.title() for part in
|
| - flavor.split('_')])
|
| - flavor_utils_class = getattr(flavor_utils_module,
|
| - '%sBuildStepUtils' % flavor_utils_class_name)
|
| - self._flavor_utils = flavor_utils_class(self)
|
| - except (ImportError, IOError) as e:
|
| - raise Exception('Unrecognized build flavor: %s\n%s' % (flavor, e))
|
| -
|
| - # Trybots should use expectations from the corresponding waterfall bot.
|
| - # This fixes https://code.google.com/p/skia/issues/detail?id=1552
|
| - gm_expected_subdir = builder_name_schema.GetWaterfallBot(
|
| - self._builder_name)
|
| -
|
| - # Figure out where we are going to store images generated by GM.
|
| - self._gm_actual_basedir = os.path.join(os.pardir, os.pardir, 'gm', 'actual')
|
| - self._gm_expected_dir = os.path.join('expectations', 'gm',
|
| - gm_expected_subdir)
|
| - self._gm_actual_dir = os.path.join(self._gm_actual_basedir,
|
| - self._builder_name)
|
| - self._dm_dir = os.path.join(os.pardir, os.pardir, 'dm')
|
| -
|
| - self._resource_dir = 'resources'
|
| - self._make_flags = shlex.split(args['make_flags'].replace('"', ''))
|
| - self._test_args = shlex.split(args['test_args'].replace('"', ''))
|
| - self._gm_args = shlex.split(args['gm_args'].replace('"', ''))
|
| - self._bench_args = shlex.split(args['bench_args'].replace('"', ''))
|
| - self._is_try = args['is_try'] == 'True'
|
| -
|
| - self._default_make_flags = []
|
| - self._default_ninja_flags = []
|
| -
|
| - # TODO(epoger): Throughout the buildbot code, we use various terms to refer
|
| - # to the same thing: "skps", "pictures", "replay", "playback".
|
| - # We should pick one of those terms, and rename things so that we are
|
| - # consistent.
|
| - # See https://codereview.chromium.org/295753002/ for additional discussion.
|
| -
|
| - # Adding the playback directory transfer objects.
|
| - self._local_playback_dirs = LocalSkpPlaybackDirs(
|
| - self._builder_name,
|
| - None if args['perf_output_basedir'] == 'None'
|
| - else args['perf_output_basedir'])
|
| - self._storage_playback_dirs = StorageSkpPlaybackDirs(
|
| - self._builder_name,
|
| - None if args['perf_output_basedir'] == 'None'
|
| - else args['perf_output_basedir'])
|
| -
|
| - self.skp_dir = self._local_playback_dirs.PlaybackSkpDir()
|
| - self.playback_actual_images_dir = (
|
| - self._local_playback_dirs.PlaybackActualImagesDir())
|
| - self.playback_actual_summaries_dir = (
|
| - self._local_playback_dirs.PlaybackActualSummariesDir())
|
| - self.playback_expected_summaries_dir = (
|
| - self._local_playback_dirs.PlaybackExpectedSummariesDir())
|
| -
|
| - # Figure out where we are going to store performance related data.
|
| - if args['perf_output_basedir'] != 'None':
|
| - self._perf_data_dir = os.path.join(args['perf_output_basedir'],
|
| - self._builder_name, 'data')
|
| - self._perf_graphs_dir = os.path.join(args['perf_output_basedir'],
|
| - self._builder_name, 'graphs')
|
| - self._perf_range_input_dir = os.path.join(
|
| - args['perf_output_basedir'], self._builder_name, 'expectations')
|
| - else:
|
| - self._perf_data_dir = None
|
| - self._perf_graphs_dir = None
|
| - self._perf_range_input_dir = None
|
| - self._skimage_in_dir = os.path.join(os.pardir, 'skimage_in')
|
| -
|
| - self._skimage_expected_dir = os.path.join('expectations', 'skimage')
|
| -
|
| - self._skimage_out_dir = os.path.join('out', self._configuration,
|
| - 'skimage_out')
|
| -
|
| - self._device_dirs = self._flavor_utils.GetDeviceDirs()
|
| -
|
| - @property
|
| - def configuration(self):
|
| - return self._configuration
|
| -
|
| - @property
|
| - def builder_name(self):
|
| - return self._builder_name
|
| -
|
| - @property
|
| - def args(self):
|
| - return self._args
|
| -
|
| - # TODO(epoger): remove default_make_flags property once all builds use ninja
|
| - @property
|
| - def default_make_flags(self):
|
| - return self._default_make_flags
|
| -
|
| - @property
|
| - def default_ninja_flags(self):
|
| - return self._default_ninja_flags
|
| -
|
| - @property
|
| - def make_flags(self):
|
| - return self._make_flags
|
| -
|
| - @property
|
| - def perf_data_dir(self):
|
| - return self._perf_data_dir
|
| -
|
| - @property
|
| - def resource_dir(self):
|
| - return self._resource_dir
|
| -
|
| - @property
|
| - def skimage_in_dir(self):
|
| - return self._skimage_in_dir
|
| -
|
| - @property
|
| - def skimage_expected_dir(self):
|
| - return self._skimage_expected_dir
|
| -
|
| - @property
|
| - def skimage_out_dir(self):
|
| - return self._skimage_out_dir
|
| -
|
| - @property
|
| - def local_playback_dirs(self):
|
| - return self._local_playback_dirs
|
| -
|
| - def _PreRun(self):
|
| - """ Optional preprocessing step defined in the BuildStepUtils. """
|
| - self._flavor_utils.PreRun()
|
| -
|
| - def _Run(self):
|
| - """ Code to be run in a given BuildStep. No return value; throws exception
|
| - on failure. Override this method in subclasses.
|
| - """
|
| - raise Exception('Cannot instantiate abstract BuildStep')
|
| -
|
| - def run(self):
|
| - """ Internal method used by multiprocess.Process. _Run is provided to be
|
| - overridden instead of this method to ensure that this implementation always
|
| - runs.
|
| - """
|
| - # If a BuildStep has exceeded its allotted time, the parent process needs to
|
| - # be able to kill the BuildStep process AND any which it has spawned,
|
| - # without harming itself. On posix platforms, the terminate() method is
|
| - # insufficient; it fails to kill the subprocesses launched by this process.
|
| - # So, we use use the setpgrp() function to set a new process group for the
|
| - # BuildStep process and its children and call os.killpg() to kill the group.
|
| - if os.name == 'posix':
|
| - os.setpgrp()
|
| - try:
|
| - self._Run()
|
| - except BuildStepWarning as e:
|
| - print e
|
| - sys.exit(config.Master.retcode_warnings)
|
| -
|
| - def _WaitFunc(self, attempt):
|
| - """ Waits a number of seconds depending upon the attempt number of a
|
| - retry-able BuildStep before making the next attempt. This can be overridden
|
| - by subclasses and should be defined for attempt in [0, self.attempts - 1]
|
| -
|
| - This default implementation is exponential; we double the wait time with
|
| - each attempt, starting with a 15-second pause between the first and second
|
| - attempts.
|
| - """
|
| - base_secs = 15
|
| - wait = base_secs * (2 ** attempt)
|
| - print 'Retrying in %d seconds...' % wait
|
| - time.sleep(wait)
|
| -
|
| - @staticmethod
|
| - def KillBuildStep(step):
|
| - """ Kills a running BuildStep.
|
| -
|
| - step: the running BuildStep instance to kill.
|
| - """
|
| - # On posix platforms, the terminate() method is insufficient; it fails to
|
| - # kill the subprocesses launched by this process. So, we use use the
|
| - # setpgrp() function to set a new process group for the BuildStep process
|
| - # and its children and call os.killpg() to kill the group.
|
| - if os.name == 'posix':
|
| - os.killpg(os.getpgid(step.pid), signal.SIGTERM)
|
| - elif os.name == 'nt':
|
| - subprocess.call(['taskkill', '/F', '/T', '/PID', str(step.pid)])
|
| - else:
|
| - step.terminate()
|
| -
|
| - @staticmethod
|
| - def RunBuildStep(StepType):
|
| - """ Run a BuildStep, possibly making multiple attempts and handling
|
| - timeouts.
|
| -
|
| - StepType: class type which subclasses BuildStep, indicating what step should
|
| - be run. StepType should override _Run().
|
| - """
|
| - # pylint: disable=W0612
|
| - logger = BuildStepLogger()
|
| - args = misc.ArgsToDict(sys.argv)
|
| - attempt = 0
|
| - while True:
|
| - step = StepType(args=args)
|
| - try:
|
| - start_time = time.time()
|
| - last_written_time = start_time
|
| - # pylint: disable=W0212
|
| - step._PreRun()
|
| - step.start()
|
| - while step.is_alive():
|
| - current_time = time.time()
|
| - if current_time - start_time > step.timeout:
|
| - BuildStep.KillBuildStep(step)
|
| - raise BuildStepTimeout('Build step exceeded timeout of %d seconds' %
|
| - step.timeout)
|
| - elif current_time - last_written_time > step.no_output_timeout:
|
| - BuildStep.KillBuildStep(step)
|
| - raise BuildStepTimeout(
|
| - 'Build step exceeded %d seconds with no output' %
|
| - step.no_output_timeout)
|
| - time.sleep(1)
|
| - if build_step_stdout_has_written.value == INT_TRUE:
|
| - last_written_time = time.time()
|
| - print 'Build Step Finished.'
|
| - if step.exitcode == 0:
|
| - return 0
|
| - elif step.exitcode == config.Master.retcode_warnings:
|
| - # A warning is considered to be an acceptable finishing state.
|
| - return config.Master.retcode_warnings
|
| - else:
|
| - raise BuildStepFailure('Build step failed.')
|
| - except Exception:
|
| - print traceback.format_exc()
|
| - if attempt + 1 >= step.attempts:
|
| - raise
|
| - # pylint: disable=W0212
|
| - step._WaitFunc(attempt)
|
| - attempt += 1
|
| - print '**** %s, attempt %d ****' % (StepType.__name__, attempt + 1)
|
|
|