ee389beb8c
- Add 72 rebrand workflow files (polkadot→pezkuwi, substrate→bizinikiwi, cumulus→pezcumulus) - Add GitHub actions, issue templates, and configs - Removed unnecessary workflows (fork-sync, gitspiegel, upstream-tracker, sync-templates, backport) - Renamed zombienet test files to match new naming convention
157 lines
4.8 KiB
Python
157 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Generate the PrDoc for a Pull Request with a specific number, audience and bump level.
|
|
|
|
It downloads and parses the patch from the GitHub API to opulate the prdoc with all modified crates.
|
|
This will delete any prdoc that already exists for the PR if `--force` is passed.
|
|
|
|
Usage:
|
|
python generate-prdoc.py --pr 1234 --audience node_dev --bump patch
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
import toml
|
|
import yaml
|
|
import requests
|
|
|
|
from github import Github
|
|
import whatthepatch
|
|
from cargo_workspace import Workspace
|
|
|
|
# Download the patch and pass the info into `create_prdoc`.
|
|
def from_pr_number(n, audience, bump, force):
|
|
print(f"Fetching PR '{n}' from GitHub")
|
|
g = Github()
|
|
|
|
repo = g.get_repo("pezkuwichain/pezkuwi-sdk")
|
|
pr = repo.get_pull(n)
|
|
|
|
patch_url = pr.patch_url
|
|
patch = requests.get(patch_url).text
|
|
|
|
create_prdoc(n, audience, pr.title, pr.body, patch, bump, force)
|
|
|
|
def translate_audience(audience):
|
|
aliases = {
|
|
'runtime_dev': 'Runtime Dev',
|
|
'runtime_user': 'Runtime User',
|
|
'node_dev': 'Node Dev',
|
|
'node_operator': 'Node Operator',
|
|
'todo': 'Todo',
|
|
}
|
|
|
|
mapped = [aliases.get(a) for a in audience]
|
|
if len(mapped) == 1:
|
|
mapped = mapped[0]
|
|
|
|
print(f"Translated audience '{audience}' to '{mapped}'")
|
|
return mapped
|
|
|
|
def create_prdoc(pr, audience, title, description, patch, bump, force):
|
|
path = f"prdoc/pr_{pr}.prdoc"
|
|
|
|
if os.path.exists(path):
|
|
if force == True:
|
|
print(f"Overwriting existing PrDoc for PR {pr}")
|
|
else:
|
|
print(f"PrDoc already exists for PR {pr}. Use --force to overwrite.")
|
|
sys.exit(1)
|
|
else:
|
|
print(f"No preexisting PrDoc for PR {pr}")
|
|
|
|
prdoc = { "title": title, "doc": [{}], "crates": [] }
|
|
audience = translate_audience(audience)
|
|
|
|
prdoc["doc"][0]["audience"] = audience
|
|
prdoc["doc"][0]["description"] = description
|
|
|
|
workspace = Workspace.from_path(".")
|
|
|
|
modified_paths = []
|
|
for diff in whatthepatch.parse_patch(patch):
|
|
new_path = diff.header.new_path
|
|
# Sometimes this lib returns `/dev/null` as the new path...
|
|
if not new_path.startswith("/dev"):
|
|
modified_paths.append(new_path)
|
|
|
|
modified_crates = {}
|
|
for p in modified_paths:
|
|
# Go up until we find a Cargo.toml
|
|
p = os.path.join(workspace.path, p)
|
|
while not os.path.exists(os.path.join(p, "Cargo.toml")):
|
|
if p == '/':
|
|
exit(1)
|
|
p = os.path.dirname(p)
|
|
|
|
with open(os.path.join(p, "Cargo.toml")) as f:
|
|
manifest = toml.load(f)
|
|
|
|
if not "package" in manifest:
|
|
continue
|
|
|
|
crate_name = manifest["package"]["name"]
|
|
if workspace.crate_by_name(crate_name).publish:
|
|
modified_crates[crate_name] = True
|
|
else:
|
|
print(f"Skipping unpublished crate: {crate_name}")
|
|
|
|
for crate_name in modified_crates.keys():
|
|
entry = { "name": crate_name }
|
|
|
|
if bump == 'silent' or bump == 'ignore' or bump == 'no change':
|
|
entry["validate"] = False
|
|
else:
|
|
entry["bump"] = bump
|
|
|
|
print(f"Adding crate {entry}")
|
|
prdoc["crates"].append(entry)
|
|
|
|
# write the parsed PR documentation back to the file
|
|
with open(path, "w") as f:
|
|
yaml.dump(prdoc, f, sort_keys=False)
|
|
print(f"PrDoc for PR {pr} written to {path}")
|
|
|
|
# Make the `description` a multiline string instead of escaping \r\n.
|
|
def setup_yaml():
|
|
def yaml_multiline_string_presenter(dumper, data):
|
|
if len(data.splitlines()) > 1:
|
|
data = '\n'.join([line.rstrip() for line in data.strip().splitlines()])
|
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
|
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data)
|
|
|
|
yaml.add_representer(str, yaml_multiline_string_presenter)
|
|
|
|
# parse_args is also used by cmd/cmd.py
|
|
# if pr_required is False, then --pr is optional, as it can be derived from the PR comment body
|
|
def setup_parser(parser=None, pr_required=True):
|
|
allowed_audiences = ["runtime_dev", "runtime_user", "node_dev", "node_operator", "todo"]
|
|
if parser is None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--pr", type=int, required=pr_required, help="The PR number to generate the PrDoc for.")
|
|
parser.add_argument("--audience", type=str, nargs='*', choices=allowed_audiences, default=["todo"], help="The audience of whom the changes may concern. Example: --audience runtime_dev node_dev")
|
|
parser.add_argument("--bump", type=str, default="major", choices=["patch", "minor", "major", "silent", "ignore", "none"], help="A default bump level for all crates. Example: --bump patch")
|
|
parser.add_argument("--force", action="store_true", help="Whether to overwrite any existing PrDoc.")
|
|
return parser
|
|
|
|
def snake_to_title(s):
|
|
return ' '.join(word.capitalize() for word in s.split('_'))
|
|
|
|
def main(args):
|
|
print(f"Args: {args}, force: {args.force}")
|
|
setup_yaml()
|
|
try:
|
|
from_pr_number(args.pr, args.audience, args.bump, args.force)
|
|
return 0
|
|
except Exception as e:
|
|
print(f"Error generating prdoc: {e}")
|
|
return 1
|
|
|
|
if __name__ == "__main__":
|
|
parser = setup_parser()
|
|
args = parser.parse_args()
|
|
main(args)
|