| # LLVM buildbot needs to watch multiple projects within a single repository. |
| # TODO: Handle both author and committer for a commit to build a correct blame list later. |
| import re |
| |
| from twisted.python import log |
| from datetime import datetime |
| |
| from twisted.internet import defer |
| from buildbot.util import bytes2unicode |
| from buildbot.plugins import changes |
| |
| class LLVMPoller(changes.GitPoller): |
| """ |
| Poll LLVM repository for changes and submit them to the change master. |
| Following Multiple LLVM Projects. |
| |
| This source will poll a remote LLVM git _monorepo_ for changes and submit |
| them to the change master.""" |
| |
| _repourl = "https://github.com/llvm/llvm-project" |
| _branch = "master" |
| |
| compare_attrs = ["repourl", "branch", "workdir", |
| "pollInterval", "gitbin", "usetimestamps", |
| "category", "project", |
| "projects"] |
| |
| def __init__(self, |
| repourl=_repourl, branch=_branch, |
| **kwargs): |
| |
| self.cleanRe = re.compile(r"Require(?:s?)\s*.*\s*clean build", re.IGNORECASE + re.MULTILINE) |
| self.cleanCfg = re.compile(r"(CMakeLists\.txt$|\.cmake$|\.cmake\.in$)") |
| |
| # TODO: Add support for an optional list of projects. |
| # For now we always watch all the projects. |
| |
| super().__init__(repourl=repourl, branch=branch, **kwargs) |
| |
| def _transform_path(self, fileList): |
| """ |
| Parses the given list of files, and returns a list of two-entry tuples |
| (PROJECT, [FILES]) if PROJECT is watched one, |
| or None otherwise. |
| |
| NOTE: we don't change result path, just extract a project name. |
| """ |
| #log.msg("LLVMPoller: _transform_path: got a file list: %s" % fileList) |
| |
| result = {} |
| |
| # It is possible that this commit does not change anything. |
| if not fileList: |
| return result |
| |
| # turn libcxxabi/include/__cxxabi_config.h into |
| # ("libcxxabi", "libcxxabi/include/__cxxabi_config.h") |
| # and filter projects we are not watching. |
| |
| for path in fileList: |
| if not path: |
| continue |
| |
| pieces = path.split('/') |
| project = pieces[0] if len(pieces) > 1 else None |
| |
| #log.msg("LLVMPoller: _transform_path: processing path %s: project: %s" % (path, project)) |
| # Collect file path for each detected projects. |
| if project in result: |
| result[project].append(path) |
| else: |
| result[project] = [path] |
| |
| log.msg("LLVMPoller: _transform_path: result: %s" % result) |
| return [(k, result[k]) for k in result] |
| |
| @defer.inlineCallbacks |
| def _process_changes(self, newRev, branch): |
| """ |
| Read changes since last change. |
| |
| - Read list of commit hashes. |
| - Extract details from each commit. |
| - Add changes to database. |
| """ |
| |
| # initial run, don't parse all history |
| if not self.lastRev: |
| return |
| |
| # get the change list |
| revListArgs = (['--ignore-missing'] + |
| ['--format=%H', '{}'.format(newRev)] + |
| ['^' + rev |
| for rev in sorted(self.lastRev.values())] + |
| ['--']) |
| self.changeCount = 0 |
| results = yield self._dovccmd('log', revListArgs, path=self.workdir) |
| |
| # process oldest change first |
| revList = results.split() |
| revList.reverse() |
| |
| if self.buildPushesWithNoCommits and not revList: |
| existingRev = self.lastRev.get(branch) |
| if existingRev != newRev: |
| revList = [newRev] |
| if existingRev is None: |
| # This branch was completely unknown, rebuild |
| log.msg('LLVMPoller: rebuilding {} for new branch "{}"'.format( |
| newRev, branch)) |
| else: |
| # This branch is known, but it now points to a different |
| # commit than last time we saw it, rebuild. |
| log.msg('LLVMPoller: rebuilding {} for updated branch "{}"'.format( |
| newRev, branch)) |
| |
| self.changeCount = len(revList) |
| self.lastRev[branch] = newRev |
| |
| if self.changeCount: |
| log.msg('LLVMPoller: processing {} changes: {} from "{}" branch "{}"'.format( |
| self.changeCount, revList, self.repourl, branch)) |
| |
| for rev in revList: |
| dl = defer.DeferredList([ |
| self._get_commit_timestamp(rev), |
| self._get_commit_author(rev), |
| self._get_commit_committer(rev), |
| self._get_commit_files(rev), |
| self._get_commit_comments(rev), |
| ], consumeErrors=True) |
| |
| results = yield dl |
| |
| # check for failures |
| failures = [r[1] for r in results if not r[0]] |
| if failures: |
| for failure in failures: |
| log.err( |
| failure, "while processing changes for {} {}".format(newRev, branch)) |
| # just fail on the first error; they're probably all related! |
| failures[0].raiseException() |
| |
| log.msg('>>> LLVMPoller: begin change adding cycle for revision: %s' % rev) |
| |
| timestamp, author, committer, files, comments = [r[1] for r in results] |
| |
| where = self._transform_path(files) |
| |
| projects = list() |
| properties = dict() |
| |
| #log.msg('LLVMPoller: walking over transformed path/projects: %s' % where) |
| for wh in where: |
| where_project, where_project_files = wh |
| #log.msg('LLVMPoller: processing transformed pair: %s, files:' % where_project, where_project_files) |
| projects += [where_project] |
| |
| if self.cleanRe.search(comments) or \ |
| any([m for f in where_project_files for m in [self.cleanCfg.search(f)] if m]): |
| log.msg("LLVMPoller: creating a change with the 'clean_obj' property for r%s" % rev) |
| properties['clean_obj'] = (True, "change") |
| |
| log.msg("LLVMPoller: creating a change rev=%s" % rev) |
| log.msg(" >>> revision=%s, timestamp=%s, author=%s, committer=%s, project=%s, files=%s, comments=\"%s\", properties=%s" % \ |
| (bytes2unicode(rev, encoding=self.encoding), datetime.fromtimestamp(timestamp), author, committer, |
| projects, files, comments, properties)) |
| |
| yield self.master.data.updates.addChange( |
| author=author, |
| committer=committer if committer != author else None, |
| revision=bytes2unicode(rev, encoding=self.encoding), |
| files=files, |
| comments=comments, |
| when_timestamp=timestamp, |
| branch=bytes2unicode(self._removeHeads(branch)), |
| category=self.category, ## TODO: Figure out if we could support tags here |
| project=",".join(projects) if projects else "llvm-project", |
| # Always promote an external github url of the LLVM project with the changes. |
| repository=self._repourl, |
| src='git', # Must be one of the buildbot.process.users.srcs |
| properties=properties) |