| # 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. |
| |
| |
| """Utility class to build the Skia master BuildFactory's. |
| |
| Based on gclient_factory.py and adds Skia-specific steps.""" |
| |
| |
| from buildbot.process.properties import WithProperties |
| from buildbot.status import builder |
| from config_private import AUTOGEN_SVN_BASEURL, SKIA_GIT_URL |
| from master.factory import gclient_factory |
| from master.factory.build_factory import BuildFactory |
| from skia_master_scripts import commands as skia_commands |
| |
| import builder_name_schema |
| import config |
| import config_private |
| import ntpath |
| import os |
| import posixpath |
| import skia_vars |
| import utils |
| |
| |
| # TODO(epoger): My intent is to make the build steps identical on all platforms |
| # and thus remove the need for the whole target_platform parameter. |
| # For now, these must match the target_platform values used in |
| # third_party/chromium_buildbot/scripts/master/factory/gclient_factory.py , |
| # because we pass these values into GClientFactory.__init__() . |
| TARGET_PLATFORM_LINUX = 'linux' |
| TARGET_PLATFORM_MAC = 'mac' |
| TARGET_PLATFORM_WIN32 = 'win32' |
| |
| CONFIG_DEBUG = 'Debug' |
| CONFIG_RELEASE = 'Release' |
| CONFIGURATIONS = [CONFIG_DEBUG, CONFIG_RELEASE] |
| |
| |
| _RUNGYP_STEP_DESCRIPTION = 'RunGYP' |
| _COMPILE_STEP_PREFIX = 'Build' |
| _COMPILE_RETRY_PREFIX = 'Retry_' + _COMPILE_STEP_PREFIX |
| _COMPILE_NO_WERR_PREFIX = 'Retry_NoWarningsAsErrors_' + _COMPILE_STEP_PREFIX |
| |
| |
| class SkiaFactory(BuildFactory): |
| """Encapsulates data and methods common to the Skia master.cfg files.""" |
| |
| def __init__(self, other_repos=None, do_upload_render_results=False, |
| do_upload_bench_results=False, do_patch_step=False, |
| build_subdir='skia', target_platform=None, |
| configuration=CONFIG_DEBUG, default_timeout=8*60*60, |
| deps_target_os=None, environment_variables=None, |
| perf_output_basedir=None, builder_name=None, flavor=None, |
| make_flags=None, test_args=None, gm_args=None, bench_args=None, |
| bench_pictures_cfg='default', compile_warnings_as_errors=False, |
| gyp_defines=None, build_targets=None): |
| """Instantiates a SkiaFactory as appropriate for this target_platform. |
| do_upload_render_results: whether we should upload render results |
| do_upload_bench_results: whether we should upload bench results |
| do_patch_step: whether the build should include a step which applies a |
| patch. This is only applicable for trybots. |
| build_subdir: subdirectory to check out and then build within |
| other_repos: list of other repositories to also check out (or None). Each |
| repo is specified as a tuple: (name, url), where "name" is the target |
| directory and "url" is the source code url. |
| target_platform: a string such as TARGET_PLATFORM_LINUX |
| configuration: 'Debug' or 'Release' |
| default_timeout: default timeout for each command, in seconds |
| deps_target_os: string; the target_os to be specified in the gclient config. |
| environment_variables: dictionary of environment variables that should |
| be passed to all commands |
| perf_output_basedir: path to directory under which to store performance |
| data, or None if we don't want to store performance data |
| builder_name: name of the builder associated with this factory |
| flavor: which "flavor" of slave-side scripts this factory should use |
| make_flags: list of extra flags to pass to the compile step |
| test_args: list of extra flags to pass to the 'tests' executable |
| gm_args: list of extra flags to pass to the 'gm' executable |
| bench_args: list of extra flags to pass to the 'bench' executable |
| bench_pictures_cfg: config name to use for bench_pictures |
| compile_warnings_as_errors: boolean; whether to build with "-Werror" or |
| some equivalent. |
| gyp_defines: optional dict; GYP_DEFINES to be used in the build. |
| build_targets: optional list; the targets to build. Default is set depending |
| on which Build() function is called. |
| """ |
| properties = {} |
| |
| self.skipsteps = utils.GetListFromEnvVar( |
| config_private.SKIPSTEPS_ENVIRONMENT_VARIABLE) |
| self.dontskipsteps = utils.GetListFromEnvVar( |
| config_private.DONTSKIPSTEPS_ENVIRONMENT_VARIABLE) |
| |
| if not make_flags: |
| make_flags = [] |
| self._make_flags = make_flags |
| # Platform-specific stuff. |
| if target_platform == TARGET_PLATFORM_WIN32: |
| self.TargetPath = ntpath |
| else: |
| self.TargetPath = posixpath |
| |
| # Create gclient solutions corresponding to the main build_subdir |
| # and other directories we also wish to check out. |
| self._gclient_solutions = [gclient_factory.GClientSolution( |
| svn_url=SKIA_GIT_URL, name=build_subdir |
| ).GetSpec()] |
| |
| if not other_repos: |
| other_repos = [] |
| repos_to_checkout = set(other_repos) |
| |
| for other_repo in repos_to_checkout: |
| self._gclient_solutions.append(gclient_factory.GClientSolution( |
| svn_url=other_repo[1], name=other_repo[0]).GetSpec()) |
| |
| self._deps_target_os = deps_target_os |
| |
| # Set _default_clobber based on config.Master |
| self._default_clobber = getattr(config.Master, 'default_clobber', False) |
| |
| self._do_upload_render_results = do_upload_render_results |
| self._do_upload_bench_results = (do_upload_bench_results and |
| perf_output_basedir != None) |
| self._do_patch_step = do_patch_step |
| |
| if not environment_variables: |
| self._env_vars = {} |
| else: |
| self._env_vars = dict(environment_variables) |
| |
| self._gyp_defines = dict(gyp_defines or {}) |
| self._gyp_defines['skia_warnings_as_errors'] = \ |
| '%d' % int(compile_warnings_as_errors) |
| |
| self._build_targets = list(build_targets or []) |
| |
| # Get an implementation of SkiaCommands as appropriate for |
| # this target_platform. |
| self._workdir = self.TargetPath.join('build', build_subdir) |
| self._skia_cmd_obj = skia_commands.SkiaCommands( |
| target_platform=target_platform, factory=self, |
| configuration=configuration, workdir=self._workdir, |
| target_arch=None, default_timeout=default_timeout, |
| environment_variables=self._env_vars) |
| |
| self._perf_output_basedir = perf_output_basedir |
| |
| self._configuration = configuration |
| if self._configuration not in CONFIGURATIONS: |
| raise ValueError('Invalid configuration %s. Must be one of: %s' % ( |
| self._configuration, CONFIGURATIONS)) |
| |
| self._skia_svn_username_file = '.skia_svn_username' |
| self._skia_svn_password_file = '.skia_svn_password' |
| self._autogen_svn_username_file = '.autogen_svn_username' |
| self._autogen_svn_password_file = '.autogen_svn_password' |
| self._builder_name = builder_name |
| self._flavor = flavor |
| |
| def _DetermineRevision(build): |
| """ Get the 'revision' property at build time. WithProperties returns the |
| empty string if 'revision' is not defined, which causes failures when we |
| try to pass the revision over a command line, so we use the string "None" |
| to indicate that the revision is not defined. |
| |
| build: instance of Build for the current build. |
| """ |
| props = build.getProperties().asDict() |
| if props.has_key('revision'): |
| if props['revision'][0]: |
| return props['revision'][0] |
| return 'None' |
| |
| if not test_args: |
| test_args = [] |
| if not gm_args: |
| gm_args = [] |
| if not bench_args: |
| bench_args = [] |
| |
| self._common_args = [ |
| '--autogen_svn_baseurl', AUTOGEN_SVN_BASEURL, |
| '--configuration', configuration, |
| '--deps_target_os', self._deps_target_os or 'None', |
| '--builder_name', builder_name, |
| '--build_number', WithProperties('%(buildnumber)s'), |
| '--target_platform', target_platform, |
| '--revision', WithProperties('%(rev)s', rev=_DetermineRevision), |
| '--got_revision', WithProperties('%(got_revision:-None)s'), |
| '--perf_output_basedir', perf_output_basedir or 'None', |
| '--make_flags', '"%s"' % ' '.join(self._make_flags), |
| '--test_args', '"%s"' % ' '.join(test_args), |
| '--gm_args', '"%s"' % ' '.join(gm_args), |
| '--bench_args', '"%s"' % ' '.join(bench_args), |
| '--num_cores', WithProperties('%(num_cores:-None)s'), |
| '--is_try', str(self._do_patch_step), |
| '--bench_pictures_cfg', bench_pictures_cfg, |
| ] |
| BuildFactory.__init__(self, build_factory_properties=properties) |
| |
| def Validate(self): |
| """ Validate the Factory against the known good configuration. """ |
| test_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, |
| 'tools', 'tests', 'factory_configuration') |
| |
| # Write the actual configuration. |
| actual_dir = os.path.join(test_dir, 'actual') |
| if not os.path.exists(actual_dir): |
| os.makedirs(actual_dir) |
| self_as_string = utils.ToString(self.__dict__) |
| with open(os.path.join(actual_dir, self._builder_name), 'w') as f: |
| f.write(self_as_string) |
| |
| # Read the expected configuration. |
| expected_dir = os.path.join(test_dir, 'expected') |
| try: |
| expectation = open(os.path.join(expected_dir, self._builder_name)).read() |
| except IOError: |
| msg = 'No expected factory configuration for %s in %s.' % ( |
| self._builder_name, expected_dir) |
| if config_private.die_on_validation_failure: |
| raise Exception(msg) |
| else: |
| print 'Warning: %s' % msg |
| return |
| |
| # Compare actual to expected. |
| if self_as_string != expectation: |
| if config_private.die_on_validation_failure: |
| raise ValueError('Factory configuration for %s does not match ' |
| 'expectation in %s! Here\'s the diff:\n%s\n' % |
| (self._builder_name, expected_dir, |
| utils.StringDiff(expectation, self_as_string))) |
| else: |
| # We don't print the full diff in this case because: |
| # a. It's generally too long to be easily read in a terminal |
| # b. All of the printing can noticeably slow down the master startup |
| # c. The master prints so much output that it would be easy to miss the |
| # diff if we did print it. |
| print 'Warning: Factory configuration for %s does not match ' \ |
| 'expectation!' % self._builder_name |
| |
| # TODO(borenet): Can kwargs be used to simplify this function declaration? |
| def AddSlaveScript(self, script, description, args=None, timeout=None, |
| halt_on_failure=False, |
| is_upload_render_step=False, is_upload_bench_step=False, |
| is_rebaseline_step=False, get_props_from_stdout=None, |
| workdir=None, do_step_if=None, always_run=False, |
| flunk_on_failure=True, exception_on_failure=False): |
| """ Add a BuildStep consisting of a python script. |
| |
| script: which slave-side python script to run. |
| description: string briefly describing the BuildStep; if this description |
| is in the self.skipsteps list, this BuildStep will be skipped--unless |
| it's in the self.dontskipsteps list, in which case we run it! |
| args: optional list of strings; arguments to pass to the script. |
| timeout: optional integer; maximum time for the BuildStep to complete. |
| halt_on_failure: boolean indicating whether to continue the build if this |
| step fails. |
| is_upload_render_step: boolean; if true, only run if |
| self._do_upload_render_results is True |
| is_upload_bench_step: boolean; if true, only run if |
| self._do_upload_bench_results is True |
| is_rebaseline_step: boolean indicating whether this step is required for |
| rebaseline-only builds. |
| get_props_from_stdout: optional dictionary. Keys are strings indicating |
| build properties to set based on the output of this step. Values are |
| strings containing regular expressions for parsing the property from |
| the output of the step. |
| workdir: optional string indicating the working directory in which to run |
| the script. If this is provided, then the script must be given relative |
| to this directory. |
| do_step_if: optional, function which determines whether or not to run the |
| step. The function is not evaluated until runtime. |
| always_run: boolean indicating whether this step should run even if a |
| previous step which had halt_on_failure has failed. |
| flunk_on_failure: boolean indicating whether the whole build fails if this |
| step fails. |
| exception_on_failure: boolean indicating whether to raise an exception if |
| this step fails. This causes the step to go purple instead of red, and |
| causes the build to stop. Should be used if the build step's failure is |
| typically transient or results from an infrastructure failure rather |
| than a code change. |
| """ |
| if description not in self.dontskipsteps: |
| if description in self.skipsteps: |
| return |
| if is_upload_render_step and not self._do_upload_render_results: |
| return |
| if is_upload_bench_step and not self._do_upload_bench_results: |
| return |
| |
| arguments = list(self._common_args) |
| if args: |
| arguments += args |
| self._skia_cmd_obj.AddSlaveScript( |
| script=script, |
| args=arguments, |
| description=description, |
| timeout=timeout, |
| halt_on_failure=halt_on_failure, |
| is_upload_step=is_upload_render_step or is_upload_bench_step, |
| is_rebaseline_step=is_rebaseline_step, |
| get_props_from_stdout=get_props_from_stdout, |
| workdir=workdir, |
| do_step_if=do_step_if, |
| always_run=always_run, |
| flunk_on_failure=flunk_on_failure, |
| exception_on_failure=exception_on_failure) |
| |
| def AddFlavoredSlaveScript(self, script, args=None, **kwargs): |
| """ Add a flavor-specific BuildStep. |
| |
| Finds a script to run by concatenating the flavor of this BuildFactory with |
| the provided script name. |
| """ |
| flavor_args = ['--flavor', self._flavor or 'default'] |
| self.AddSlaveScript(script, args=list(args or []) + flavor_args, **kwargs) |
| |
| def RunGYP(self, description=_RUNGYP_STEP_DESCRIPTION, do_step_if=None): |
| """ Run GYP to generate build files. |
| |
| description: string; description of this BuildStep. |
| do_step_if: optional, function which determines whether or not to run this |
| step. |
| """ |
| self.AddFlavoredSlaveScript(script='run_gyp.py', description=description, |
| halt_on_failure=True, do_step_if=do_step_if, |
| args=['--gyp_defines', |
| ' '.join('%s=%s' % (k, v) for k, v in |
| self._gyp_defines.items())]) |
| |
| def Make(self, target, description, is_rebaseline_step=False, do_step_if=None, |
| always_run=False, flunk_on_failure=True, halt_on_failure=True): |
| """ Build a single target. |
| |
| target: string; the target to build. |
| description: string; description of this BuildStep. |
| is_rebaseline_step: optional boolean; whether or not this step is required |
| for rebaseline-only builds. |
| do_step_if: optional, function which determines whether or not to run this |
| step. |
| always_run: boolean indicating whether this step should run even if a |
| previous step which had halt_on_failure has failed. |
| flunk_on_failure: boolean indicating whether the whole build fails if this |
| step fails. |
| halt_on_failure: boolean indicating whether to continue the build if this |
| step fails. |
| """ |
| args = ['--target', target, |
| '--gyp_defines', |
| ' '.join('%s=%s' % (k, v) for k, v in self._gyp_defines.items())] |
| self.AddFlavoredSlaveScript(script='compile.py', args=args, |
| description=description, |
| halt_on_failure=halt_on_failure, |
| is_rebaseline_step=is_rebaseline_step, |
| do_step_if=do_step_if, |
| always_run=always_run, |
| flunk_on_failure=flunk_on_failure) |
| |
| def Compile(self, clobber=None, retry_with_clobber_on_failure=True, |
| retry_without_werr_on_failure=False): |
| """ Compile step. Build everything. |
| |
| clobber: optional boolean; whether to 'clean' before building. |
| retry_with_clobber_on_failure: optional boolean; if the build fails, clean |
| and try again, with the same configuration as before. |
| retry_without_werr_on_failure: optional boolean; if the build fails, clean |
| and try again *without* warnings-as-errors. |
| """ |
| if clobber is None: |
| clobber = self._default_clobber |
| |
| |
| if clobber: |
| self.AddFlavoredSlaveScript(script='clean.py', description='Clean', |
| halt_on_failure=True) |
| |
| # Only retry with clobber if we've requested it AND we aren't clobbering on |
| # the first build. |
| maybe_retry_with_clobber = retry_with_clobber_on_failure and not clobber |
| |
| def ShouldRetryWithClobber(step): |
| """ Determine whether the retry step should run. """ |
| if not maybe_retry_with_clobber: |
| return False |
| gyp_or_compile_failed = False |
| retry_failed = False |
| for build_step in step.build.getStatus().getSteps(): |
| if (build_step.isFinished() and |
| build_step.getResults()[0] == builder.FAILURE): |
| if build_step.getName().startswith(_COMPILE_STEP_PREFIX): |
| gyp_or_compile_failed = True |
| elif build_step.getName() == _RUNGYP_STEP_DESCRIPTION: |
| gyp_or_compile_failed = True |
| elif build_step.getName().startswith(_COMPILE_RETRY_PREFIX): |
| retry_failed = True |
| return gyp_or_compile_failed and not retry_failed |
| |
| def ShouldRetryWithoutWarnings(step): |
| """ Determine whether the retry-without-warnings-as-errors step should |
| run. """ |
| if not retry_without_werr_on_failure: |
| return False |
| gyp_or_compile_failed = False |
| retry_failed = False |
| no_warning_retry_failed = False |
| for build_step in step.build.getStatus().getSteps(): |
| if (build_step.isFinished() and |
| build_step.getResults()[0] == builder.FAILURE): |
| if build_step.getName().startswith(_COMPILE_STEP_PREFIX): |
| gyp_or_compile_failed = True |
| elif build_step.getName().startswith(_COMPILE_RETRY_PREFIX): |
| retry_failed = True |
| elif build_step.getName().startswith( |
| _COMPILE_NO_WERR_PREFIX): |
| no_warning_retry_failed = True |
| # If we've already failed a previous retry without warnings, just give up. |
| if no_warning_retry_failed: |
| return False |
| # If we're retrying with clobber, only retry without warnings if a clobber |
| # retry has failed. |
| if maybe_retry_with_clobber: |
| return retry_failed |
| # Only run the retry if the initial compile has failed. |
| return gyp_or_compile_failed |
| |
| for build_target in self._build_targets: |
| self.Make(target=build_target, |
| description=_COMPILE_STEP_PREFIX + \ |
| utils.UnderscoresToCapWords(build_target), |
| flunk_on_failure=not maybe_retry_with_clobber, |
| halt_on_failure=(not maybe_retry_with_clobber and |
| not retry_without_werr_on_failure)) |
| |
| # Try again with a clean build. |
| self.AddFlavoredSlaveScript(script='clean.py', description='Clean', |
| do_step_if=ShouldRetryWithClobber) |
| for build_target in self._build_targets: |
| self.Make(target=build_target, |
| description=_COMPILE_RETRY_PREFIX + \ |
| utils.UnderscoresToCapWords(build_target), |
| flunk_on_failure=True, |
| halt_on_failure=not retry_without_werr_on_failure, |
| do_step_if=ShouldRetryWithClobber) |
| |
| # Try again without warnings-as-errors. |
| self._gyp_defines['skia_warnings_as_errors'] = '0' |
| self.AddFlavoredSlaveScript(script='clean.py', description='Clean', |
| always_run=True, |
| do_step_if=ShouldRetryWithoutWarnings) |
| for build_target in self._build_targets: |
| self.Make(target=build_target, |
| description=_COMPILE_NO_WERR_PREFIX + \ |
| utils.UnderscoresToCapWords(build_target), |
| flunk_on_failure=True, |
| halt_on_failure=True, |
| do_step_if=ShouldRetryWithoutWarnings) |
| |
| def Install(self): |
| """ Install the compiled executables. """ |
| self.AddFlavoredSlaveScript(script='install.py', description='Install', |
| halt_on_failure=True, exception_on_failure=True) |
| |
| def DownloadSKPs(self): |
| """ Download the SKPs. """ |
| self.AddSlaveScript(script='download_skps.py', description='DownloadSKPs', |
| halt_on_failure=True, exception_on_failure=True) |
| |
| def DownloadSKImageFiles(self): |
| """ Download image files for running skimage. """ |
| self.AddSlaveScript(script='download_skimage_files.py', |
| description='DownloadSKImageFiles', |
| halt_on_failure=True, exception_on_failure=True) |
| |
| def RunTests(self): |
| """ Run the unit tests. """ |
| self.AddFlavoredSlaveScript(script='run_tests.py', description='RunTests') |
| |
| def RunDecodingTests(self): |
| """ Run tests of image decoders. """ |
| self.AddFlavoredSlaveScript(script='run_decoding_tests.py', |
| description='RunDecodingTests') |
| |
| def RunDM(self): |
| """Run DM.""" |
| self.AddFlavoredSlaveScript('run_dm.py', description='RunDM') |
| |
| def RunGM(self): |
| """ Run the "GM" tool, saving the images to disk. """ |
| self.AddFlavoredSlaveScript(script='run_gm.py', description='GenerateGMs', |
| is_rebaseline_step=True) |
| |
| def PreRender(self): |
| """ Step to run before the render steps. """ |
| self.AddFlavoredSlaveScript(script='prerender.py', description='PreRender', |
| exception_on_failure=True) |
| |
| def RenderSKPs(self): |
| """ Generate images from .skp's. """ |
| self.AddFlavoredSlaveScript(script='render_skps.py', |
| description='RenderSKPs') |
| |
| def RenderPdfs(self): |
| """ Run the "render_pdfs" tool to generate pdfs from .skp's. """ |
| self.AddFlavoredSlaveScript(script='render_pdfs.py', |
| description='RenderPdfs') |
| |
| def PostRender(self): |
| """ Step to run after the render steps. """ |
| self.AddFlavoredSlaveScript(script='postrender.py', |
| description='PostRender', |
| exception_on_failure=True) |
| |
| def PreBench(self): |
| """ Step to run before the benchmarking steps. """ |
| self.AddFlavoredSlaveScript(script='prebench.py', |
| description='PreBench', |
| exception_on_failure=True) |
| |
| def PostBench(self): |
| """ Step to run after the benchmarking steps. """ |
| self.AddFlavoredSlaveScript(script='postbench.py', |
| description='PostBench', |
| exception_on_failure=True) |
| |
| def CompareGMs(self): |
| """Compare the actually-generated GM images to the checked-in baselines.""" |
| self.AddSlaveScript(script='compare_gms.py', |
| description='CompareGMs', |
| get_props_from_stdout={ |
| 'latest_gm_failures_url': |
| '%s([^\n]*)\n' % skia_vars.GetGlobalVariable( |
| 'latest_gm_failures_preamble')}, |
| is_rebaseline_step=True) |
| |
| def CompareRenderedSKPs(self): |
| """Compare the actual image results of SKP rendering to expectations.""" |
| self.AddSlaveScript(script='compare_rendered_skps.py', |
| description='CompareRenderedSKPs', |
| is_rebaseline_step=True) |
| |
| def RunBench(self): |
| """ Run "bench", piping the output somewhere so we can graph |
| results over time. """ |
| self.AddFlavoredSlaveScript(script='run_bench.py', description='RunBench') |
| |
| def BenchPictures(self): |
| """ Run "bench_pictures" """ |
| self.AddFlavoredSlaveScript(script='bench_pictures.py', |
| description='BenchPictures') |
| |
| def CheckForRegressions(self): |
| """ Check for benchmark regressions. """ |
| self.AddSlaveScript(script='check_for_regressions.py', |
| description='CheckForRegressions') |
| |
| def UpdateScripts(self): |
| """ Update the buildbot scripts on the build slave. |
| |
| Only runs in production. See http://skbug.com/2432 |
| """ |
| description = 'UpdateScripts' |
| if ((config_private.Master.get_active_master().is_production_host) or |
| (description in self.dontskipsteps)): |
| self.AddSlaveScript( |
| script=self.TargetPath.join('..', '..', '..', '..', |
| '..', 'slave', |
| 'skia_slave_scripts', |
| 'update_scripts.py'), |
| description=description, |
| halt_on_failure=True, |
| get_props_from_stdout={'buildbot_revision': |
| 'Skiabot scripts updated to (\w+)'}, |
| workdir='build', |
| exception_on_failure=True) |
| |
| def Update(self): |
| """ Update the Skia code on the build slave. """ |
| args = ['--gclient_solutions', '"%s"' % self._gclient_solutions] |
| self.AddSlaveScript( |
| script=self.TargetPath.join('..', '..', '..', '..', '..', 'slave', |
| 'skia_slave_scripts', 'update.py'), |
| description='Update', |
| args=args, |
| timeout=None, |
| halt_on_failure=True, |
| is_rebaseline_step=True, |
| get_props_from_stdout={'got_revision':'Skia updated to (\w+)'}, |
| workdir='build', |
| exception_on_failure=True) |
| |
| def ApplyPatch(self, alternate_workdir=None, alternate_script=None): |
| """ Apply a patch to the Skia code on the build slave. """ |
| def _GetPatch(build): |
| """Find information about the patch (if any) to apply. |
| |
| Returns: |
| An encoded string containing a tuple of the form (level, url) which |
| indicates where to go to download the patch. |
| """ |
| if build.getSourceStamp().patch and \ |
| 'patch_file_url' in build.getProperties(): |
| # The presence of a patch attached to the Source Stamp and the |
| # 'patch_file_url' build property indicate that the patch came from the |
| # skia_try repo, and was probably submitted using the submit_try script |
| # or "gcl/git-cl try". |
| patch = (build.getSourceStamp().patch[0], |
| build.getProperty('patch_file_url')) |
| return str(patch).encode() |
| elif 'issue' in build.getProperties() and \ |
| 'patchset' in build.getProperties(): |
| # The presence of the 'issue' and 'patchset' build properties indicates |
| # that the patch came from Rietveld. |
| patch = '%s/download/issue%d_%d.diff' % ( |
| config_private.CODE_REVIEW_SITE.rstrip('/'), |
| build.getProperty('issue'), |
| build.getProperty('patchset')) |
| # If the patch came from Rietveld, assume it came from a git repo and |
| # therefore it has a patch level of 1. If this isn't the case, the |
| # slave-side script should detect it and use level 0 instead. |
| return str((1, patch)).encode() |
| else: |
| patch = 'None' |
| return patch |
| |
| if not bool(alternate_workdir) == bool(alternate_script): |
| raise ValueError('alternate_workdir and alternate_script must be provided' |
| ' together.') |
| args = ['--patch', WithProperties('%(patch)s', patch=_GetPatch)] |
| if alternate_script: |
| self.AddSlaveScript(script=alternate_script, |
| description='ApplyPatch', |
| args=args, |
| halt_on_failure=True, |
| workdir=alternate_workdir, |
| exception_on_failure=True) |
| else: |
| self.AddSlaveScript(script='apply_patch.py', |
| description='ApplyPatch', |
| args=args, |
| halt_on_failure=True, |
| exception_on_failure=True) |
| |
| def UpdateSteps(self): |
| """ Update the Skia sources. """ |
| self.UpdateScripts() |
| self.Update() |
| if self._do_patch_step: |
| self.ApplyPatch() |
| |
| def UploadBenchResults(self): |
| """ Upload bench results (performance data). """ |
| self.AddSlaveScript(script='upload_bench_results.py', |
| description='UploadBenchResults', |
| exception_on_failure=True, |
| is_upload_bench_step=True) |
| |
| def UploadBenchResultsToAppEngine(self): |
| """ Upload bench results (performance data) to AppEngine. """ |
| self.AddSlaveScript(script='upload_bench_results_appengine.py', |
| description='UploadBenchResultsToAppengine', |
| exception_on_failure=True) |
| |
| def UploadWebpagePictureBenchResults(self): |
| """ Upload webpage picture bench results (performance data). """ |
| self.AddSlaveScript(script='upload_webpage_picture_bench_results.py', |
| description='UploadWebpagePictureBenchResults', |
| exception_on_failure=True) |
| |
| |
| def UploadGMResults(self): |
| """ Upload the images generated by GM """ |
| args = ['--autogen_svn_username_file', self._autogen_svn_username_file, |
| '--autogen_svn_password_file', self._autogen_svn_password_file] |
| self.AddSlaveScript(script='upload_gm_results.py', args=args, |
| description='UploadGMResults', timeout=5400, |
| is_upload_render_step=True, is_rebaseline_step=True, |
| exception_on_failure=True) |
| |
| def UploadRenderedSKPs(self): |
| """Upload the actual image results of SKP rendering.""" |
| # TODO(epoger): Maybe instead of adding these extra args to only CERTAIN |
| # steps (and thus requiring a master restart when we want to add them to |
| # more steps), maybe we should just provide these extra args to ALL steps? |
| args = ['--autogen_svn_username_file', self._autogen_svn_username_file, |
| '--autogen_svn_password_file', self._autogen_svn_password_file] |
| self.AddSlaveScript(script='upload_rendered_skps.py', args=args, |
| description='UploadRenderedSKPs', |
| is_upload_render_step=True, is_rebaseline_step=True) |
| |
| def UploadSKImageResults(self): |
| self.AddSlaveScript(script='upload_skimage_results.py', |
| description='UploadSKImageResults', |
| is_upload_render_step=True, |
| exception_on_failure=True) |
| |
| def CommonSteps(self, clobber=None): |
| """ Steps which are run at the beginning of all builds. """ |
| self.UpdateSteps() |
| self.DownloadSKPs() |
| self.Compile(clobber) |
| self.Install() |
| |
| def NonPerfSteps(self): |
| """ Add correctness testing BuildSteps. """ |
| self.DownloadSKImageFiles() |
| self.PreRender() |
| self.RunTests() |
| self.RunGM() |
| self.RenderSKPs() |
| self.RenderPdfs() |
| self.RunDecodingTests() |
| self.PostRender() |
| self.UploadGMResults() |
| self.UploadRenderedSKPs() |
| self.UploadSKImageResults() |
| self.CompareGMs() |
| self.CompareRenderedSKPs() |
| |
| def PerfSteps(self): |
| """ Add performance testing BuildSteps. """ |
| self.PreBench() |
| self.RunBench() |
| self.BenchPictures() |
| self.PostBench() |
| self.CheckForRegressions() |
| self.UploadBenchResults() |
| |
| def Build(self, role=None, clobber=None): |
| """Build and return the complete BuildFactory. |
| |
| role: string; the intended role of this builder. The role affects which |
| steps are run. Known values are given in the utils module. |
| clobber: boolean indicating whether we should clean before building |
| """ |
| # Special case: for the ZeroGPUCache bot, we only run GM. |
| if 'ZeroGPUCache' in self._builder_name: |
| self._build_targets = ['gm'] |
| self.UpdateSteps() |
| self.Compile(clobber) |
| self.Install() |
| self.PreRender() |
| self.RunGM() |
| self.PostRender() |
| elif ('TSAN' in self._builder_name and |
| role == builder_name_schema.BUILDER_ROLE_TEST): |
| self._build_targets = ['tests', 'dm'] |
| self.UpdateSteps() |
| self.Compile(clobber) |
| self.Install() |
| self.RunTests() |
| self.RunDM() |
| elif ('Valgrind' in self._builder_name and |
| role == builder_name_schema.BUILDER_ROLE_TEST): |
| if not self._build_targets: |
| self._build_targets = ['most'] |
| self.CommonSteps(clobber) |
| # TODO(borenet):When https://code.google.com//p/skia/issues/detail?id=1711 |
| # is fixed, run self.NonPerfSteps() instead of the below steps. |
| self.DownloadSKImageFiles() |
| self.PreRender() |
| self.RunTests() |
| self.RunGM() |
| self.RenderSKPs() |
| self.RenderPdfs() |
| self.RunDecodingTests() |
| self.PostRender() |
| # (end steps which need to be replaced once #1711 is fixed) |
| |
| self.PreBench() |
| self.RunBench() |
| self.PostBench() |
| elif not role: |
| # If no role is provided, just run everything. |
| if not self._build_targets: |
| self._build_targets = ['most'] |
| self.CommonSteps(clobber) |
| self.NonPerfSteps() |
| self.PerfSteps() |
| elif role == builder_name_schema.BUILDER_ROLE_BUILD: |
| # Compile-only builder. |
| self.UpdateSteps() |
| if not self._build_targets: |
| self._build_targets = [] |
| if (('Win7' in self._builder_name and 'x86_64' in self._builder_name) or |
| ('Ubuntu' in self._builder_name and 'x86-' in self._builder_name) or |
| 'Mac10.6' in self._builder_name or 'Mac10.7' in self._builder_name): |
| # Don't compile the debugger in 64-bit Win7, Mac 10.6, Mac 10.7, or |
| # 32-bit Linux since the Qt SDK doesn't include libraries for those |
| # platforms. |
| self._build_targets.append('most') |
| else: |
| self._build_targets.append('everything') |
| self.Compile(clobber=clobber, |
| retry_without_werr_on_failure=True) |
| else: |
| if not self._build_targets: |
| self._build_targets = ['most'] |
| self.CommonSteps(clobber) |
| if role == builder_name_schema.BUILDER_ROLE_TEST: |
| # Test-running builder. |
| self.NonPerfSteps() |
| if self._configuration == CONFIG_DEBUG: |
| # Debug-mode testers run all steps, but release-mode testers don't. |
| self.PerfSteps() |
| elif role == builder_name_schema.BUILDER_ROLE_PERF: |
| # Perf-only builder. |
| if not self._perf_output_basedir: |
| raise ValueError( |
| 'BuildPerfOnly requires perf_output_basedir to be defined.') |
| if self._configuration != CONFIG_RELEASE: |
| raise ValueError('BuildPerfOnly should run in %s configuration.' % |
| CONFIG_RELEASE) |
| self.PerfSteps() |
| self.Validate() |
| return self |