blob: 909ce85164024e39ebe3e29dfc7dfdbdf5f603af [file] [log] [blame]
#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
import re
import logging
import sys
from pathlib import Path
from typing import Dict, Tuple, Any, Optional, List, Union
# Hackery to enable importing of utils from ci/scripts/jenkins
REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
sys.path.append(str(REPO_ROOT / "ci" / "scripts" / "jenkins"))
from git_utils import GitHubRepo
BOT_COMMENT_START = "<!---bot-comment-->"
WELCOME_TEXT = "Thanks for contributing to TVM! Please refer to the contributing guidelines https://tvm.apache.org/docs/contribute/ for useful information and tips. Please request code reviews from [Reviewers](https://github.com/apache/incubator-tvm/blob/master/CONTRIBUTORS.md#reviewers) by @-ing them in a comment."
class BotCommentBuilder:
ALLOWLIST_USERS = {"driazati", "gigiblender", "areusch"}
def __init__(self, github: GitHubRepo, data: Dict[str, Any]):
self.github = github
self.pr_number = data["number"]
self.comment_data = data["comments"]["nodes"]
self.author = data["author"]["login"]
def find_bot_comment(self) -> Optional[Dict[str, Any]]:
"""
Return the existing bot comment or None if it does not exist
"""
for comment in self.comment_data:
logging.info(f"Checking comment {comment}")
if (
comment["author"]["login"] == "github-actions"
and BOT_COMMENT_START in comment["body"]
):
logging.info("Found existing comment")
return comment
logging.info("No existing comment found")
return None
def find_existing_body(self) -> Dict[str, str]:
"""
Find existing dynamic bullet point items
"""
existing_comment = self.find_bot_comment()
if existing_comment is None:
logging.info(f"No existing comment while searching for body items")
return {}
matches = re.findall(
r"<!--bot-comment-([a-z][a-z-]+)-start-->([\S\s]*?)<!--bot-comment-([a-z-]+)-end-->",
existing_comment["body"],
flags=re.MULTILINE,
)
logging.info(f"Fetch body item matches: {matches}")
items = {}
for start, text, end in matches:
if start != end:
raise RuntimeError(
f"Malformed comment found: {start} marker did not have matching end, found instead {end}"
)
items[start] = text.strip().lstrip("* ")
logging.info(f"Found body items: {items}")
return items
def _post_comment(self, body_items: Dict[str, str]):
comment = BOT_COMMENT_START + "\n\n" + WELCOME_TEXT + "\n\n"
for key, content in body_items.items():
line = self.start_key(key) + "\n * " + content.strip() + self.end_key(key)
logging.info(f"Adding line {line}")
comment += line
comment += "\n\n<sub>Generated by [tvm-bot](https://github.com/apache/tvm/blob/main/ci/README.md#github-actions)</sub>"
data = {"body": comment}
url = f"issues/{self.pr_number}/comments"
logging.info(f"Commenting {comment} on {url}")
if self.author not in self.ALLOWLIST_USERS:
logging.info(f"Skipping comment for author {self.author}")
return
existing_comment = self.find_bot_comment()
if existing_comment is None:
# Comment does not exist, post it
r = self.github.post(url, data)
else:
# Comment does exist, update it
comment_url = f"issues/comments/{existing_comment['databaseId']}"
r = self.github.patch(comment_url, data)
logging.info(f"Got response from posting comment: {r}")
def start_key(self, key: str) -> str:
return f"<!--bot-comment-{key}-start-->"
def end_key(self, key: str) -> str:
return f"<!--bot-comment-{key}-end-->"
def post_items(self, items: List[Tuple[str, str]]):
"""
Update or post bullet points in the PR based on 'items' which is a
list of (key, text) pairs
"""
# Find the existing bullet points
body_items = self.find_existing_body()
# Add or update the requested items
for key, text in items:
if text is None or text.strip() == "":
logging.info(f"Skipping {key} since it was empty")
continue
logging.info(f"Updating comment items {key} with {text}")
body_items[key] = text.strip()
# Post or update the comment
# print(body_items)
self._post_comment(body_items=body_items)