2019-10-14 09:07:07 +02:00
|
|
|
#!/usr/bin/env python3
|
2019-10-15 16:52:08 +02:00
|
|
|
# Copyright 2019 Google LLC
|
|
|
|
#
|
|
|
|
# Licensed under the the Apache License v2.0 with LLVM Exceptions (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# https://llvm.org/LICENSE.txt
|
|
|
|
#
|
|
|
|
# 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.
|
2020-08-27 14:44:56 +02:00
|
|
|
"""
|
|
|
|
Interactions with Phabricator.
|
|
|
|
"""
|
2019-10-15 16:52:08 +02:00
|
|
|
|
2020-10-12 16:25:23 +02:00
|
|
|
import logging
|
2020-01-30 17:45:57 +01:00
|
|
|
from typing import Optional, List, Dict
|
2020-10-12 16:25:23 +02:00
|
|
|
import uuid
|
2021-04-27 11:58:55 +02:00
|
|
|
import argparse
|
2020-10-12 16:25:23 +02:00
|
|
|
|
|
|
|
import backoff
|
2019-12-09 12:09:53 +01:00
|
|
|
from phabricator import Phabricator
|
2021-07-28 15:50:57 +02:00
|
|
|
from benedict import benedict
|
2019-12-09 12:09:53 +01:00
|
|
|
|
2019-10-14 09:07:07 +02:00
|
|
|
|
|
|
|
class PhabTalk:
|
2019-12-09 12:09:53 +01:00
|
|
|
"""Talk to Phabricator to upload build results.
|
|
|
|
See https://secure.phabricator.com/conduit/method/harbormaster.sendmessage/
|
2021-07-28 15:50:57 +02:00
|
|
|
You might want to use it as it provides retries on most of the calls.
|
2019-12-09 12:09:53 +01:00
|
|
|
"""
|
2019-10-15 16:52:08 +02:00
|
|
|
|
2020-10-12 16:25:23 +02:00
|
|
|
def __init__(self, token: Optional[str], host: Optional[str] = 'https://reviews.llvm.org/api/',
|
2020-11-25 15:29:50 +01:00
|
|
|
dry_run_updates: bool = False):
|
2019-10-17 17:11:17 +02:00
|
|
|
self._phab = None # type: Optional[Phabricator]
|
2020-11-25 15:29:50 +01:00
|
|
|
self.dry_run_updates = dry_run_updates
|
|
|
|
self._phab = Phabricator(token=token, host=host)
|
|
|
|
self.update_interfaces()
|
2020-10-12 16:25:23 +02:00
|
|
|
|
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
|
|
|
def update_interfaces(self):
|
|
|
|
self._phab.update_interfaces()
|
2019-12-09 12:09:53 +01:00
|
|
|
|
2020-10-12 16:25:23 +02:00
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
2020-01-30 17:45:57 +01:00
|
|
|
def get_revision_id(self, diff: str) -> Optional[str]:
|
2019-10-15 16:52:08 +02:00
|
|
|
"""Get the revision ID for a diff from Phabricator."""
|
|
|
|
result = self._phab.differential.querydiffs(ids=[diff])
|
|
|
|
return 'D' + result[diff]['revisionID']
|
|
|
|
|
2021-07-28 15:50:57 +02:00
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
|
|
|
def get_diff(self, diff_id: int):
|
|
|
|
"""Get a diff from Phabricator based on its diff id."""
|
|
|
|
return self._phab.differential.getdiff(diff_id=diff_id)
|
|
|
|
|
2020-02-06 15:49:14 +01:00
|
|
|
def comment_on_diff(self, diff_id: str, text: str):
|
2019-10-15 16:52:08 +02:00
|
|
|
"""Add a comment to a differential based on the diff_id"""
|
2020-11-25 15:29:50 +01:00
|
|
|
logging.info('Sending comment to diff {}:'.format(diff_id))
|
|
|
|
logging.info(text)
|
2020-01-30 17:45:57 +01:00
|
|
|
revision_id = self.get_revision_id(diff_id)
|
|
|
|
if revision_id is not None:
|
|
|
|
self._comment_on_revision(revision_id, text)
|
2019-10-15 16:52:08 +02:00
|
|
|
|
2020-10-12 16:25:23 +02:00
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
2019-10-17 17:11:17 +02:00
|
|
|
def _comment_on_revision(self, revision: str, text: str):
|
2019-10-15 16:52:08 +02:00
|
|
|
"""Add comment on a differential based on the revision id."""
|
|
|
|
|
|
|
|
transactions = [{
|
2019-12-09 12:09:53 +01:00
|
|
|
'type': 'comment',
|
|
|
|
'value': text
|
2019-10-15 16:52:08 +02:00
|
|
|
}]
|
|
|
|
|
2020-11-25 15:29:50 +01:00
|
|
|
if self.dry_run_updates:
|
|
|
|
logging.info('differential.revision.edit =================')
|
|
|
|
logging.info('Transactions: {}'.format(transactions))
|
2019-10-17 17:11:17 +02:00
|
|
|
return
|
2019-12-09 12:09:53 +01:00
|
|
|
|
2019-10-15 16:52:08 +02:00
|
|
|
# API details at
|
|
|
|
# https://secure.phabricator.com/conduit/method/differential.revision.edit/
|
2019-12-09 12:09:53 +01:00
|
|
|
self._phab.differential.revision.edit(objectIdentifier=revision,
|
|
|
|
transactions=transactions)
|
2020-11-25 15:29:50 +01:00
|
|
|
logging.info('Uploaded comment to Revision D{}:{}'.format(revision, text))
|
2019-10-17 17:11:17 +02:00
|
|
|
|
2020-10-12 16:25:23 +02:00
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
2020-10-05 12:48:34 +02:00
|
|
|
def update_build_status(self, phid: str, working: bool, success: bool, lint: {}, unit: []):
|
2019-12-09 12:09:53 +01:00
|
|
|
"""Submit collected report to Phabricator.
|
2019-10-15 16:52:08 +02:00
|
|
|
"""
|
2019-10-17 17:11:17 +02:00
|
|
|
|
2019-12-09 12:09:53 +01:00
|
|
|
result_type = 'pass'
|
2020-02-06 15:49:14 +01:00
|
|
|
if working:
|
2019-12-09 12:09:53 +01:00
|
|
|
result_type = 'working'
|
2020-02-06 15:49:14 +01:00
|
|
|
elif not success:
|
2019-12-09 12:09:53 +01:00
|
|
|
result_type = 'fail'
|
2019-11-21 15:04:39 +01:00
|
|
|
|
2019-12-12 12:31:56 +01:00
|
|
|
# Group lint messages by file and line.
|
|
|
|
lint_messages = []
|
2020-02-06 15:49:14 +01:00
|
|
|
for v in lint.values():
|
2019-12-12 12:31:56 +01:00
|
|
|
path = ''
|
|
|
|
line = 0
|
|
|
|
descriptions = []
|
|
|
|
for e in v:
|
|
|
|
path = e['path']
|
|
|
|
line = e['line']
|
|
|
|
descriptions.append('{}: {}'.format(e['name'], e['description']))
|
|
|
|
lint_message = {
|
|
|
|
'name': 'Pre-merge checks',
|
|
|
|
'severity': 'warning',
|
|
|
|
'code': 'llvm-premerge-checks',
|
|
|
|
'path': path,
|
|
|
|
'line': line,
|
|
|
|
'description': '\n'.join(descriptions),
|
|
|
|
}
|
|
|
|
lint_messages.append(lint_message)
|
|
|
|
|
2020-11-25 15:29:50 +01:00
|
|
|
if self.dry_run_updates:
|
|
|
|
logging.info('harbormaster.sendmessage =================')
|
|
|
|
logging.info('type: {}'.format(result_type))
|
|
|
|
logging.info('unit: {}'.format(unit))
|
|
|
|
logging.info('lint: {}'.format(lint_messages))
|
2020-02-17 10:23:47 +01:00
|
|
|
return
|
|
|
|
|
2020-10-12 16:25:23 +02:00
|
|
|
self._phab.harbormaster.sendmessage(
|
2020-02-17 10:23:47 +01:00
|
|
|
buildTargetPHID=phid,
|
|
|
|
type=result_type,
|
|
|
|
unit=unit,
|
2020-10-12 16:25:23 +02:00
|
|
|
lint=lint_messages)
|
2020-11-25 15:29:50 +01:00
|
|
|
logging.info('Uploaded build status {}, {} test results and {} lint results'.format(
|
2020-03-25 07:26:55 +01:00
|
|
|
result_type, len(unit), len(lint_messages)))
|
2019-10-17 17:11:17 +02:00
|
|
|
|
2020-10-12 16:25:23 +02:00
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
2020-05-25 16:42:40 +02:00
|
|
|
def create_artifact(self, phid, artifact_key, artifact_type, artifact_data):
|
2020-11-25 15:29:50 +01:00
|
|
|
if self.dry_run_updates:
|
|
|
|
logging.info('harbormaster.createartifact =================')
|
|
|
|
logging.info('artifactKey: {}'.format(artifact_key))
|
|
|
|
logging.info('artifactType: {}'.format(artifact_type))
|
|
|
|
logging.info('artifactData: {}'.format(artifact_data))
|
2020-02-17 10:23:47 +01:00
|
|
|
return
|
2020-10-12 16:25:23 +02:00
|
|
|
self._phab.harbormaster.createartifact(
|
2020-02-06 13:48:37 +01:00
|
|
|
buildTargetPHID=phid,
|
2020-05-25 16:42:40 +02:00
|
|
|
artifactKey=artifact_key,
|
|
|
|
artifactType=artifact_type,
|
2020-10-12 16:25:23 +02:00
|
|
|
artifactData=artifact_data)
|
2020-02-06 13:48:37 +01:00
|
|
|
|
2020-10-02 14:04:29 +02:00
|
|
|
def maybe_add_url_artifact(self, phid: str, url: str, name: str):
|
2020-11-25 15:29:50 +01:00
|
|
|
if self.dry_run_updates:
|
|
|
|
logging.info(f'add ULR artifact "{name}" {url}')
|
|
|
|
return
|
2020-10-02 14:04:29 +02:00
|
|
|
if phid is None:
|
|
|
|
logging.warning('PHID is not provided, cannot create URL artifact')
|
|
|
|
return
|
|
|
|
self.create_artifact(phid, str(uuid.uuid4()), 'uri', {'uri': url, 'ui.external': True, 'name': name})
|
|
|
|
|
2021-07-28 15:50:57 +02:00
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
|
|
|
def user_projects(self, user_phid: str) -> List[str]:
|
|
|
|
"""Returns slugs of all projects user has a membership."""
|
|
|
|
projects = benedict(self._phab.project.search(constraints={'members': [user_phid]}))
|
|
|
|
slugs = []
|
|
|
|
for p in projects.get('data', []):
|
|
|
|
slug = benedict(p).get('fields.slug')
|
|
|
|
if slug:
|
|
|
|
slugs.append(p['fields']['slug'])
|
|
|
|
return slugs
|
|
|
|
|
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
|
|
|
def get_revision(self, revision_id: int):
|
|
|
|
"""Get a revision from Phabricator based on its revision id."""
|
|
|
|
return self._phab.differential.query(ids=[revision_id])[0]
|
|
|
|
|
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=5, logger='', factor=3)
|
|
|
|
def get_diff(self, diff_id: int):
|
|
|
|
"""Get a diff from Phabricator based on its diff id."""
|
|
|
|
return self._phab.differential.getdiff(diff_id=diff_id)
|
|
|
|
|
2019-10-17 17:11:17 +02:00
|
|
|
|
2020-06-03 13:40:22 +02:00
|
|
|
class Step:
|
2020-12-11 16:35:58 +01:00
|
|
|
def __init__(self, name=''):
|
|
|
|
self.name = name
|
2020-06-03 13:40:22 +02:00
|
|
|
self.success = True
|
|
|
|
self.duration = 0.0
|
2020-07-09 15:24:27 +02:00
|
|
|
self.reproduce_commands = []
|
2020-06-03 13:40:22 +02:00
|
|
|
|
|
|
|
def set_status_from_exit_code(self, exit_code: int):
|
|
|
|
if exit_code != 0:
|
|
|
|
self.success = False
|
2020-05-25 16:42:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Report:
|
|
|
|
def __init__(self):
|
2020-06-03 13:40:22 +02:00
|
|
|
self.os = ''
|
|
|
|
self.name = ''
|
2020-05-25 16:42:40 +02:00
|
|
|
self.comments = []
|
|
|
|
self.success = True
|
|
|
|
self.working = False
|
|
|
|
self.unit = [] # type: List
|
|
|
|
self.lint = {}
|
|
|
|
self.test_stats = {
|
|
|
|
'pass': 0,
|
|
|
|
'fail': 0,
|
|
|
|
'skip': 0
|
|
|
|
} # type: Dict[str, int]
|
2020-06-03 13:40:22 +02:00
|
|
|
self.steps = [] # type: List[Step]
|
2020-05-25 16:42:40 +02:00
|
|
|
self.artifacts = [] # type: List
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return str(self.__dict__)
|
|
|
|
|
|
|
|
def add_lint(self, m):
|
|
|
|
key = '{}:{}'.format(m['path'], m['line'])
|
|
|
|
if key not in self.lint:
|
|
|
|
self.lint[key] = []
|
|
|
|
self.lint[key].append(m)
|
|
|
|
|
|
|
|
def add_artifact(self, dir: str, file: str, name: str):
|
|
|
|
self.artifacts.append({'dir': dir, 'file': file, 'name': name})
|
2021-04-27 11:58:55 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
parser = argparse.ArgumentParser(description='Sample interaction with Phabricator')
|
|
|
|
parser.add_argument('--url', type=str, dest='url', default='https://reviews.llvm.org/api/')
|
|
|
|
parser.add_argument('--token', type=str, dest='token', default=None, required=True)
|
|
|
|
parser.add_argument('--diff_id', type=str, dest='diff_id', default=None, required=True)
|
|
|
|
args = parser.parse_args()
|
|
|
|
phabtalk = PhabTalk(args.token, args.url, False)
|
|
|
|
print(phabtalk.get_revision_id(args.diff_id))
|
|
|
|
|