| Index: master/skia_master_scripts/utils.py
|
| diff --git a/master/skia_master_scripts/utils.py b/master/skia_master_scripts/utils.py
|
| deleted file mode 100644
|
| index 55fed8205e0ca85a0d8cf5ac232b63b4cb1754e4..0000000000000000000000000000000000000000
|
| --- a/master/skia_master_scripts/utils.py
|
| +++ /dev/null
|
| @@ -1,529 +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.
|
| -
|
| -
|
| -"""Miscellaneous utilities needed by the Skia buildbot master."""
|
| -
|
| -
|
| -import difflib
|
| -import httplib2
|
| -import json
|
| -import os
|
| -import re
|
| -
|
| -# requires Google APIs client library for Python; see
|
| -# https://code.google.com/p/google-api-python-client/wiki/Installation
|
| -from apiclient.discovery import build
|
| -from buildbot.scheduler import Dependent
|
| -from buildbot.scheduler import Scheduler
|
| -from buildbot.schedulers import timed
|
| -from buildbot.schedulers.filter import ChangeFilter
|
| -from config_private import TRY_SVN_BASEURL
|
| -from master import try_job_svn
|
| -from master import try_job_rietveld
|
| -from master.builders_pools import BuildersPools
|
| -from oauth2client.client import SignedJwtAssertionCredentials
|
| -
|
| -import builder_name_schema
|
| -import config_private
|
| -import os
|
| -import skia_vars
|
| -import subprocess
|
| -
|
| -
|
| -GATEKEEPER_NAME = 'GateKeeper'
|
| -
|
| -TRY_SCHEDULER_SVN = 'skia_try_svn'
|
| -TRY_SCHEDULER_RIETVELD = 'skia_try_rietveld'
|
| -TRY_SCHEDULERS = [TRY_SCHEDULER_SVN, TRY_SCHEDULER_RIETVELD]
|
| -TRY_SCHEDULERS_STR = '|'.join(TRY_SCHEDULERS)
|
| -
|
| -
|
| -def GetListFromEnvVar(name, splitstring=','):
|
| - """ Returns contents of an environment variable, as a list.
|
| -
|
| - If the environment variable is unset or set to empty-string, this returns
|
| - an empty list.
|
| -
|
| - name: string; name of the environment variable to read
|
| - splitstring: string with which to split the env var into list items
|
| - """
|
| - unsplit = os.environ.get(name, None)
|
| - if unsplit:
|
| - return unsplit.split(',')
|
| - else:
|
| - return []
|
| -
|
| -
|
| -def StringDiff(expected, actual):
|
| - """ Returns the diff between two multiline strings, as a multiline string."""
|
| - return ''.join(difflib.unified_diff(expected.splitlines(1),
|
| - actual.splitlines(1)))
|
| -
|
| -
|
| -def ToString(obj):
|
| - """ Returns a string representation of the given object. This differs from the
|
| - built-in string function in that it does not give memory locations.
|
| -
|
| - obj: the object to print.
|
| - """
|
| - def sanitize(obj):
|
| - if isinstance(obj, list) or isinstance(obj, tuple):
|
| - return [sanitize(sub_obj) for sub_obj in obj]
|
| - elif isinstance(obj, dict):
|
| - rv = {}
|
| - for k, v in obj.iteritems():
|
| - rv[k] = sanitize(v)
|
| - return rv
|
| - elif isinstance(obj, str) or obj is None:
|
| - return obj
|
| - else:
|
| - return '<Object>'
|
| - return json.dumps(sanitize(obj), indent=4, sort_keys=True)
|
| -
|
| -
|
| -def FixGitSvnEmail(addr):
|
| - """ Git-svn tacks a git-svn-id onto email addresses. This function removes it.
|
| -
|
| - For example, "skia.buildbots@gmail.com@2bbb7eff-a529-9590-31e7-b0007b416f81"
|
| - becomes, "skia.buildbots@gmail.com". Addresses containing a single '@' will be
|
| - unchanged.
|
| - """
|
| - return '@'.join(addr.split('@')[:2])
|
| -
|
| -
|
| -class SkiaChangeFilter(ChangeFilter):
|
| - """Skia specific subclass of ChangeFilter."""
|
| -
|
| - def __init__(self, builders, **kwargs):
|
| - self._builders = builders
|
| - ChangeFilter.__init__(self, **kwargs)
|
| -
|
| - def filter_change(self, change):
|
| - """Overrides ChangeFilter.filter_change to pass builders to filter_fn.
|
| -
|
| - The code has been copied from
|
| - http://buildbot.net/buildbot/docs/0.8.3/reference/buildbot.schedulers.filter-pysrc.html#ChangeFilter
|
| - with one change: We pass a sequence of builders to the filter function.
|
| - """
|
| - if self.filter_fn is not None and not self.filter_fn(change,
|
| - self._builders):
|
| - return False
|
| - for (filt_list, filt_re, filt_fn, chg_attr) in self.checks:
|
| - chg_val = getattr(change, chg_attr, '')
|
| - if filt_list is not None and chg_val not in filt_list:
|
| - return False
|
| - if filt_re is not None and (
|
| - chg_val is None or not filt_re.match(chg_val)):
|
| - return False
|
| - if filt_fn is not None and not filt_fn(chg_val):
|
| - return False
|
| - return True
|
| -
|
| -
|
| -def _AssertValidString(var, varName='[unknown]'):
|
| - """Raises an exception if a var is not a valid string.
|
| -
|
| - A string is considered valid if it is not None, is not the empty string and is
|
| - not just whitespace.
|
| -
|
| - Args:
|
| - var: the variable to validate
|
| - varName: name of the variable, for error reporting
|
| - """
|
| - if not isinstance(var, str):
|
| - raise Exception('variable "%s" is not a string' % varName)
|
| - if not var:
|
| - raise Exception('variable "%s" is empty' % varName)
|
| - if var.isspace():
|
| - raise Exception('variable "%s" is whitespace' % varName)
|
| -
|
| -
|
| -def _AssertValidStringList(var, varName='[unknown]'):
|
| - """Raises an exception if var is not a list of valid strings.
|
| -
|
| - A list is considered valid if it is either empty or if it contains at
|
| - least one item and each item it contains is also a valid string.
|
| -
|
| - Args:
|
| - var: the variable to validate
|
| - varName: name of the variable, for error reporting
|
| - """
|
| - if not isinstance(var, list):
|
| - raise Exception('variable "%s" is not a list' % varName)
|
| - for index, item in zip(range(len(var)), var):
|
| - _AssertValidString(item, '%s[%d]' % (varName, index))
|
| -
|
| -
|
| -def FileBug(summary, description, owner=None, ccs=None, labels=None):
|
| - """Files a bug to the Skia issue tracker.
|
| -
|
| - Args:
|
| - summary: a single-line string to use as the issue summary
|
| - description: a multiline string to use as the issue description
|
| - owner: email address of the issue owner (as a string), or None if unknown
|
| - ccs: email addresses (list of strings) to CC on the bug
|
| - labels: labels (list of strings) to apply to the bug
|
| -
|
| - Returns:
|
| - A representation of the issue tracker issue that was filed or raises an
|
| - exception if there was a problem.
|
| - """
|
| - project_id = 'skia' # This is the project name: skia
|
| - key_file = 'key.p12' # Key file from the API console, renamed to key.p12
|
| - service_acct = ('352371350305-b3u8jq5sotdh964othi9ntg9d0pelu77'
|
| - '@developer.gserviceaccount.com') # Created with the key
|
| - result = {}
|
| - if not ccs:
|
| - ccs = []
|
| - if not labels:
|
| - labels = []
|
| -
|
| - if owner is not None: # owner can be None
|
| - _AssertValidString(owner, 'owner')
|
| - _AssertValidString(summary, 'summary')
|
| - _AssertValidString(description, 'description')
|
| - _AssertValidStringList(ccs, 'ccs')
|
| - _AssertValidStringList(labels, 'labels')
|
| -
|
| - f = file(key_file, 'rb')
|
| - key = f.read()
|
| - f.close()
|
| -
|
| - # Create an httplib2.Http object to handle the HTTP requests and authorize
|
| - # it with the credentials.
|
| - credentials = SignedJwtAssertionCredentials(
|
| - service_acct,
|
| - key,
|
| - scope='https://www.googleapis.com/auth/projecthosting')
|
| - http = httplib2.Http()
|
| - http = credentials.authorize(http)
|
| -
|
| - service = build("projecthosting", "v2", http=http)
|
| -
|
| - # Insert a new issue into the project.
|
| - body = {
|
| - 'summary': summary,
|
| - 'description': description
|
| - }
|
| -
|
| - insertparams = {
|
| - 'projectId': project_id,
|
| - 'sendEmail': 'true'
|
| - }
|
| -
|
| - if owner is not None:
|
| - owner_value = {
|
| - 'name': owner
|
| - }
|
| - body['owner'] = owner_value
|
| -
|
| - cc_values = []
|
| - for cc in ccs:
|
| - cc_values.append({'name': cc})
|
| - body['cc'] = cc_values
|
| -
|
| - body['labels'] = labels
|
| -
|
| - insertparams['body'] = body
|
| -
|
| - request = service.issues().insert(**insertparams)
|
| - result = request.execute()
|
| -
|
| - return result
|
| -
|
| -
|
| -# Skip buildbot runs of a CL if its commit log message contains the following
|
| -# substring.
|
| -SKIP_BUILDBOT_SUBSTRING = '(SkipBuildbotRuns)'
|
| -
|
| -# If the below regex is found in a CL's commit log message, only run the
|
| -# builders specified therein.
|
| -RUN_BUILDERS_REGEX = '\(RunBuilders:(.+)\)'
|
| -RUN_BUILDERS_RE_COMPILED = re.compile(RUN_BUILDERS_REGEX)
|
| -
|
| -
|
| -def CapWordsToUnderscores(string):
|
| - """ Converts a string containing capitalized words to one in which all
|
| - characters are lowercase and words are separated by underscores.
|
| -
|
| - Example:
|
| - 'Nexus10' becomes 'nexus_10'
|
| -
|
| - string: string; string to manipulate.
|
| - """
|
| - name_parts = []
|
| - for part in re.split('(\d+)', string):
|
| - if re.match('(\d+)', part):
|
| - name_parts.append(part)
|
| - else:
|
| - name_parts.extend(re.findall('[A-Z][a-z]*', part))
|
| - return '_'.join([part.lower() for part in name_parts])
|
| -
|
| -
|
| -def UnderscoresToCapWords(string):
|
| - """ Converts a string lowercase words separated by underscores to one in which
|
| - words are capitalized and not separated by underscores.
|
| -
|
| - Example:
|
| - 'nexus_10' becomes 'Nexus10'
|
| -
|
| - string: string; string to manipulate.
|
| - """
|
| - name_parts = string.split('_')
|
| - return ''.join([part.title() for part in name_parts])
|
| -
|
| -
|
| -# Since we can't modify the existing Helper class, we subclass it here,
|
| -# overriding the necessary parts to get things working as we want.
|
| -class SkiaHelper(object):
|
| - def __init__(self, defaults):
|
| - self._defaults = defaults
|
| - self._builders = []
|
| - self._factories = {}
|
| - self._schedulers = {}
|
| -
|
| - def Builder(self, name, factory, gatekeeper=None, scheduler=None,
|
| - builddir=None, auto_reboot=False, notify_on_missing=False):
|
| - # Override the category with the first two parts of the builder name.
|
| - name_parts = name.split(builder_name_schema.BUILDER_NAME_SEP)
|
| - category = name_parts[0]
|
| - subcategory = name_parts[1] if len(name_parts) > 1 else 'default'
|
| - full_category = '|'.join((category, subcategory))
|
| - self._builders.append({'name': name,
|
| - 'factory': factory,
|
| - 'gatekeeper': gatekeeper,
|
| - 'schedulers': scheduler.split('|'),
|
| - 'builddir': builddir,
|
| - 'category': full_category,
|
| - 'auto_reboot': auto_reboot,
|
| - 'notify_on_missing': notify_on_missing})
|
| -
|
| - def PeriodicScheduler(self, name, minute=0, hour='*', dayOfMonth='*',
|
| - month='*', dayOfWeek='*'):
|
| - """Helper method for the Periodic scheduler."""
|
| - if name in self._schedulers:
|
| - raise ValueError('Scheduler %s already exists' % name)
|
| - self._schedulers[name] = {'type': 'PeriodicScheduler',
|
| - 'builders': [],
|
| - 'minute': minute,
|
| - 'hour': hour,
|
| - 'dayOfMonth': dayOfMonth,
|
| - 'month': month,
|
| - 'dayOfWeek': dayOfWeek}
|
| -
|
| - def Dependent(self, name, parent):
|
| - if name in self._schedulers:
|
| - raise ValueError('Scheduler %s already exists' % name)
|
| - self._schedulers[name] = {'type': 'Dependent',
|
| - 'parent': parent,
|
| - 'builders': []}
|
| -
|
| - def Factory(self, name, factory):
|
| - if name in self._factories:
|
| - raise ValueError('Factory %s already exists' % name)
|
| - self._factories[name] = factory
|
| -
|
| - def Scheduler(self, name, treeStableTimer=60, categories=None):
|
| - if name in self._schedulers:
|
| - raise ValueError('Scheduler %s already exists' % name)
|
| - self._schedulers[name] = {'type': 'Scheduler',
|
| - 'treeStableTimer': treeStableTimer,
|
| - 'builders': [],
|
| - 'categories': categories}
|
| -
|
| - def TryJobSubversion(self, name):
|
| - """ Adds a Subversion-based try scheduler. """
|
| - if name in self._schedulers:
|
| - raise ValueError('Scheduler %s already exists' % name)
|
| - self._schedulers[name] = {'type': 'TryJobSubversion', 'builders': []}
|
| -
|
| - def TryJobRietveld(self, name):
|
| - """ Adds a Rietveld-based try scheduler. """
|
| - if name in self._schedulers:
|
| - raise ValueError('Scheduler %s already exists' % name)
|
| - self._schedulers[name] = {'type': 'TryJobRietveld', 'builders': []}
|
| -
|
| - def Update(self, c):
|
| - for builder in self._builders:
|
| - # Update the schedulers with the builder.
|
| - schedulers = builder['schedulers']
|
| - if schedulers:
|
| - for scheduler in schedulers:
|
| - self._schedulers[scheduler]['builders'].append(builder['name'])
|
| -
|
| - # Construct the category.
|
| - categories = []
|
| - if builder.get('category', None):
|
| - categories.append(builder['category'])
|
| - if builder.get('gatekeeper', None):
|
| - categories.extend(builder['gatekeeper'].split('|'))
|
| - category = '|'.join(categories)
|
| -
|
| - # Append the builder to the list.
|
| - new_builder = {'name': builder['name'],
|
| - 'factory': self._factories[builder['factory']],
|
| - 'category': category,
|
| - 'auto_reboot': builder['auto_reboot']}
|
| - if builder['builddir']:
|
| - new_builder['builddir'] = builder['builddir']
|
| - c['builders'].append(new_builder)
|
| -
|
| - c['builders'].sort(key=lambda builder: builder['name'])
|
| -
|
| - # Process the main schedulers.
|
| - for s_name in self._schedulers:
|
| - scheduler = self._schedulers[s_name]
|
| - if scheduler['type'] == 'Scheduler':
|
| - def filter_fn(change, builders):
|
| - """Filters out if change.comments contains certain keywords.
|
| -
|
| - The change is filtered out if the commit message contains:
|
| - * SKIP_BUILDBOT_SUBSTRING or
|
| - * RUN_BUILDERS_REGEX when the scheduler does not contain any of the
|
| - specified builders
|
| -
|
| - Args:
|
| - change: An instance of changes.Change.
|
| - builders: Sequence of strings. The builders that are run by this
|
| - scheduler.
|
| -
|
| - Returns:
|
| - If the change should be filtered out (i.e. not run by the buildbot
|
| - code) then False is returned else True is returned.
|
| - """
|
| - if SKIP_BUILDBOT_SUBSTRING in change.comments:
|
| - return False
|
| - match_obj = RUN_BUILDERS_RE_COMPILED.search(change.comments)
|
| - if builders and match_obj:
|
| - for builder_to_run in match_obj.group(1).split(','):
|
| - if builder_to_run.strip() in builders:
|
| - break
|
| - else:
|
| - return False
|
| - return True
|
| -
|
| - skia_change_filter = SkiaChangeFilter(
|
| - builders=scheduler['builders'],
|
| - branch=skia_vars.GetGlobalVariable('master_branch_name'),
|
| - filter_fn=filter_fn)
|
| -
|
| - instance = Scheduler(name=s_name,
|
| - treeStableTimer=scheduler['treeStableTimer'],
|
| - builderNames=scheduler['builders'],
|
| - change_filter=skia_change_filter)
|
| - c['schedulers'].append(instance)
|
| - self._schedulers[s_name]['instance'] = instance
|
| -
|
| - # Process the periodic schedulers.
|
| - for s_name in self._schedulers:
|
| - scheduler = self._schedulers[s_name]
|
| - if scheduler['type'] == 'PeriodicScheduler':
|
| - instance = timed.Nightly(
|
| - name=s_name,
|
| - branch=skia_vars.GetGlobalVariable('master_branch_name'),
|
| - builderNames=scheduler['builders'],
|
| - minute=scheduler['minute'],
|
| - hour=scheduler['hour'],
|
| - dayOfMonth=scheduler['dayOfMonth'],
|
| - month=scheduler['month'],
|
| - dayOfWeek=scheduler['dayOfWeek'])
|
| - c['schedulers'].append(instance)
|
| - self._schedulers[s_name]['instance'] = instance
|
| -
|
| - # Process the Rietveld-based try schedulers.
|
| - for s_name in self._schedulers:
|
| - scheduler = self._schedulers[s_name]
|
| - if scheduler['type'] == 'TryJobRietveld':
|
| - pools = BuildersPools(s_name)
|
| - pools[s_name].extend(scheduler['builders'])
|
| - instance = try_job_rietveld.TryJobRietveld(
|
| - name=s_name,
|
| - pools=pools,
|
| - last_good_urls={'skia': None},
|
| - code_review_sites={'skia': config_private.CODE_REVIEW_SITE},
|
| - project='skia')
|
| - c['schedulers'].append(instance)
|
| - self._schedulers[s_name]['instance'] = instance
|
| -
|
| - # Process the svn-based try schedulers.
|
| - for s_name in self._schedulers:
|
| - scheduler = self._schedulers[s_name]
|
| - if scheduler['type'] == 'TryJobSubversion':
|
| - pools = BuildersPools(s_name)
|
| - pools[s_name].extend(scheduler['builders'])
|
| - instance = try_job_svn.TryJobSubversion(
|
| - name=s_name,
|
| - svn_url=TRY_SVN_BASEURL,
|
| - last_good_urls={'skia': None},
|
| - code_review_sites={'skia': config_private.CODE_REVIEW_SITE},
|
| - pools=pools)
|
| - c['schedulers'].append(instance)
|
| - self._schedulers[s_name]['instance'] = instance
|
| -
|
| - # Process the dependent schedulers.
|
| - for s_name in self._schedulers:
|
| - scheduler = self._schedulers[s_name]
|
| - if scheduler['type'] == 'Dependent':
|
| - instance = Dependent(
|
| - s_name,
|
| - self._schedulers[scheduler['parent']]['instance'],
|
| - scheduler['builders'])
|
| - c['schedulers'].append(instance)
|
| - self._schedulers[s_name]['instance'] = instance
|
| -
|
| -
|
| -def CanMergeBuildRequests(req1, req2):
|
| - """ Determine whether or not two BuildRequests can be merged. Note that the
|
| - call to buildbot.sourcestamp.SourceStamp.canBeMergedWith() is conspicuously
|
| - missing. This is because that method verifies that:
|
| - 1. req1.source.repository == req2.source.repository
|
| - 2. req1.source.project == req2.source.project
|
| - 3. req1.source.branch == req2.source.branch
|
| - 4. req1.patch == None and req2.patch = None
|
| - 5. (req1.source.changes and req2.source.changes) or \
|
| - (not req1.source.changes and not req2.source.changes and \
|
| - req1.source.revision == req2.source.revision)
|
| -
|
| - Of the above, we want 1, 2, 3, and 5.
|
| - Instead of 4, we want to make sure that neither request is a Trybot request.
|
| - """
|
| - # Verify that the repositories are the same (#1 above).
|
| - if req1.source.repository != req2.source.repository:
|
| - return False
|
| -
|
| - # Verify that the projects are the same (#2 above).
|
| - if req1.source.project != req2.source.project:
|
| - return False
|
| -
|
| - # Verify that the branches are the same (#3 above).
|
| - if req1.source.branch != req2.source.branch:
|
| - return False
|
| -
|
| - # If either is a try request, don't merge (#4 above).
|
| - if (builder_name_schema.IsTrybot(req1.buildername) or
|
| - builder_name_schema.IsTrybot(req2.buildername)):
|
| - return False
|
| -
|
| - # Verify that either: both requests are associated with changes OR neither
|
| - # request is associated with a change but the revisions match (#5 above).
|
| - if req1.source.changes and not req2.source.changes:
|
| - return False
|
| - if not req1.source.changes and req2.source.changes:
|
| - return False
|
| - if not (req1.source.changes and req2.source.changes):
|
| - if req1.source.revision != req2.source.revision:
|
| - return False
|
| -
|
| - return True
|
| -
|
| -
|
| -def get_current_revision():
|
| - """Obtain the checked-out buildbot code revision."""
|
| - checkout_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
|
| - if os.path.isdir(os.path.join(checkout_dir, '.git')):
|
| - return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
|
| - elif os.path.isdir(os.path.join(checkout_dir, '.svn')):
|
| - return subprocess.check_output(['svnversion', '.']).strip()
|
| - raise Exception('Unable to determine version control system.')
|
|
|