Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MdeModulePkg: Test change in DxeMain #182

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions .github/scripts/GitHub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
## @file
# GitHub API helper functions.
#
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent
#

import logging
import re
import requests

from collections import OrderedDict
from edk2toollib.utility_functions import RunCmd, RunPythonScript
from io import StringIO
from typing import List

"""GitHub API helper functions."""


def leave_pr_comment(
token: str, owner: str, repo: str, pr_number: str, comment_body: str
):
"""Leaves a comment on a PR.

Args:
token (str): The GitHub token to use for authentication.
owner (str): The GitHub owner (organization) name.
repo (str): The GitHub repository name (e.g. 'edk2').
pr_number (str): The pull request number.
comment_body (str): The comment text. Markdown is supported.
"""
url = f"https://api.github.com/repos/{owner}/{repo}/issues/{pr_number}/comments"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github.v3+json",
}
data = {"body": comment_body}
response = requests.post(url, json=data, headers=headers)
response.raise_for_status()


def get_reviewers_for_current_branch(
workspace_path: str, maintainer_file_path: str, target_branch: str = "master"
) -> List[str]:
"""Get the reviewers for the current branch.

Args:
workspace_path (str): The workspace path.
maintainer_file_path (str): The maintainer file path.
target_branch (str, optional): The name of the target branch that the
current HEAD will merge to. Defaults to "master".

Returns:
List[str]: A list of GitHub usernames.
"""

commit_stream_buffer = StringIO()
cmd_ret = RunCmd(
"git",
f"log --format=format:%H {target_branch}..HEAD",
workingdir=workspace_path,
outstream=commit_stream_buffer,
logging_level=logging.INFO,
)
if cmd_ret != 0:
print(
f"::error title=Commit Lookup Error!::Error getting branch commits: [{cmd_ret}]: {commit_stream_buffer.getvalue()}"
)
return []

raw_reviewers = []
for commit_sha in commit_stream_buffer.getvalue().splitlines():
reviewer_stream_buffer = StringIO()
cmd_ret = RunPythonScript(
maintainer_file_path,
f"-g {commit_sha}",
workingdir=workspace_path,
outstream=reviewer_stream_buffer,
logging_level=logging.INFO,
)
if cmd_ret != 0:
print(
f"::error title=Reviewer Lookup Error!::Error calling GetMaintainer.py: [{cmd_ret}]: {reviewer_stream_buffer.getvalue()}"
)
return []

commit_reviewers = reviewer_stream_buffer.getvalue()

pattern = r"\[(.*?)\]"
matches = re.findall(pattern, commit_reviewers)
if not matches:
return []

print(
f"::debug title=Commit {commit_sha[:7]} Reviewer(s)::{', '.join(matches)}"
)

raw_reviewers.extend(matches)

reviewers = list(OrderedDict.fromkeys([r.strip() for r in raw_reviewers]))

print(f"::debug title=Total Reviewer Set::{', '.join(reviewers)}")

return reviewers


def download_gh_file(github_url: str, local_path: str, token=None):
"""Downloads a file from GitHub.

Args:
github_url (str): The GitHub raw file URL.
local_path (str): A local path to write the file contents to.
token (_type_, optional): A GitHub authentication token.
Only needed for a private repo. Defaults to None.
"""
headers = {}
if token:
headers["Authorization"] = f"Bearer {token}"

try:
response = requests.get(github_url, headers=headers)
response.raise_for_status()
except requests.exceptions.HTTPError:
print(
f"::error title=HTTP Error!::Error downloading {github_url}: {response.reason}"
)
return

with open(local_path, "w", encoding="utf-8") as file:
file.write(response.text)


def add_reviewers_to_pr(
token: str, owner: str, repo: str, pr_number: str, user_names: List[str]
):
"""Adds the set of GitHub usernames as reviewers to the PR.

Args:
token (str): The GitHub token to use for authentication.
owner (str): The GitHub owner (organization) name.
repo (str): The GitHub repository name (e.g. 'edk2').
pr_number (str): The pull request number.
user_names (List[str]): List of GitHub usernames to add as reviewers.
"""
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github.v3+json",
}
pr_author_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers"

response = requests.get(pr_author_url, headers=headers)
if response.status_code != 200:
print(f"::error title=HTTP Error!::Error getting PR author: {response.reason}")
return
pr_author = response.json().get("user").get("login").strip()
while pr_author in user_names:
user_names.remove(pr_author)
data = {"reviewers": user_names}
response = requests.post(url, json=data, headers=headers)
try:
response.raise_for_status()
except requests.exceptions.HTTPError:
if (
response.status_code == 422
and "Reviews may only be requested from collaborators"
in response.json().get("message")
):
print(
f"::error title=User is not a Collaborator!::{response.json().get('message')}"
)
leave_pr_comment(
token,
owner,
repo,
pr_number,
f"⚠ **WARNING: Cannot add reviewers**: A user specified as a "
f"reviewer for this PR is not a collaborator "
f"of the edk2 repository. Please add them as a collaborator to the "
f"repository and re-request the review.\n\n"
f"Users requested:\n{', '.join(user_names)}",
)
elif response.status_code == 422:
print(
"::error title=Invalid Request!::The request is invalid. "
"Verify the API request string."
)
73 changes: 73 additions & 0 deletions .github/workflows/request-reviews.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

# This workflow automatically adds the appropriate reviewers to a pull request.
#
# The workflow directly reuses logic in the BaseTools/Scripts/GetMaintainer.py script
# to determine the appropriate reviewers, so it matches what a user would see running
# the script locally.
#
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent

name: Add Pull Request Reviewers

on:
pull_request:
branches:
- master
types: [opened, ready_for_review, reopened, synchronize]

env:
GET_MAINTAINER_REL_PATH: "BaseTools/Scripts/GetMaintainer.py"

jobs:
auto-request-review:
name: Add Pull Request Reviewers
# Do not run on draft PRs and only run on PRs in the tianocore organization
if: ${{ '!github.event.pull_request.draft' && github.repository_owner == 'tianocore' }}
runs-on: ubuntu-latest

permissions:
contents: read
issues: write
pull-requests: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Install PIP Modules
run: pip install edk2-pytool-library edk2-pytool-extensions requests

- name: Add Reviewers to Pull Request
shell: python
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_NAME: ${{ github.repository_owner }}
PR_NUMBER: ${{ github.event.number}}
REPO_NAME: ${{ github.event.pull_request.base.repo.name }}
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
WORKSPACE_PATH: ${{ github.workspace }}
run: |
import os
import sys
sys.path.append(os.path.join(os.environ['WORKSPACE_PATH'], ".github"))
from scripts import GitHub

WORKSPACE_PATH = os.environ['WORKSPACE_PATH']
GET_MAINTAINER_LOCAL_PATH = os.path.join(WORKSPACE_PATH, os.environ['GET_MAINTAINER_REL_PATH'])

reviewers = GitHub.get_reviewers_for_current_branch(WORKSPACE_PATH, GET_MAINTAINER_LOCAL_PATH, f"origin/{os.environ['TARGET_BRANCH']}")
if not reviewers:
print("::notice title=No Reviewers Found!::No reviewers found for this PR.")
sys.exit(1)

print(f"::notice title=Reviewer List::Reviewers found for PR {os.environ['PR_NUMBER']}:\n{', '.join(reviewers)}")

GitHub.add_reviewers_to_pr(os.environ['GH_TOKEN'], os.environ['ORG_NAME'], os.environ['REPO_NAME'], os.environ['PR_NUMBER'], reviewers)
11 changes: 9 additions & 2 deletions BaseTools/Scripts/GetMaintainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ def get_modified_files(repo, args):
PARSER.add_argument('-l', '--lookup',
help='Find section matches for path LOOKUP',
required=False)
PARSER.add_argument('-g', '--github',
action='store_true',
help='Include GitHub usernames in output',
required=False)
ARGS = PARSER.parse_args()

REPO = SetupGit.locate_repo()
Expand All @@ -203,5 +207,8 @@ def get_modified_files(repo, args):

for address in ADDRESSES:
if '<' in address and '>' in address:
address = address.split('>', 1)[0] + '>'
print(' %s' % address)
address, github_id = address.split('>', 1)
address = address + '>'
github_id = github_id.strip() if ARGS.github else ''

print(' %s %s' % (address, github_id))
Loading
Loading