Skip to content

Commit

Permalink
scripts: west_commands: patch: Add gh-fetch subcommand
Browse files Browse the repository at this point in the history
Add a gh-fetch subcommand to the west patch extension to download a patch
file from Github and generate the patch meta data.

The patch info is appended to the patches.yml file.

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
  • Loading branch information
pdgendt committed Jan 8, 2025
1 parent 9d0627a commit 341733f
Showing 1 changed file with 108 additions and 7 deletions.
115 changes: 108 additions & 7 deletions scripts/west_commands/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
import argparse
import hashlib
import os
import re
import shlex
import subprocess
import textwrap
import urllib.request
from pathlib import Path

import pykwalify.core
import yaml
from west.commands import WestCommand

try:
from yaml import CSafeDumper as SafeDumper
from yaml import CSafeLoader as SafeLoader
except ImportError:
from yaml import SafeLoader
from yaml import SafeDumper, SafeLoader

WEST_PATCH_SCHEMA_PATH = Path(__file__).parents[1] / "schemas" / "patch-schema.yml"
with open(WEST_PATCH_SCHEMA_PATH) as f:
Expand Down Expand Up @@ -61,6 +64,11 @@ def do_add_parser(self, parser_adder):
Run "west patch list" to list patches.
See "west patch list --help" for details.
Fetching Patches:
Run "west patch gh-fetch" to fetch patches from Github.
See "west patch gh-fetch --help" for details.
YAML File Format:
The patches.yml syntax is described in "scripts/schemas/patch-schema.yml".
Expand Down Expand Up @@ -166,6 +174,52 @@ def do_add_parser(self, parser_adder):
),
)

gh_fetch_arg_parser = subparsers.add_parser(
"gh-fetch",
help="Fetch patch from Github",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent(
"""
Fetching Patches from Github:
Run "west patch gh-fetch" to fetch a PR from Github and store it as a patch.
The meta data is generated and appended to the provided patches.yml file.
"""
),
)
gh_fetch_arg_parser.add_argument(
"-o",
"--owner",
action="store",
default="zephyrproject-rtos",
help="Github repository owner",
)
gh_fetch_arg_parser.add_argument(
"-r",
"--repo",
action="store",
default="zephyr",
help="Github repository",
)
gh_fetch_arg_parser.add_argument(
"-pr",
"--pull-request",
metavar="ID",
action="store",
required=True,
type=int,
help="Github Pull Request ID",
)
gh_fetch_arg_parser.add_argument(
"-m",
"--module",
metavar="DIR",
action="store",
required=True,
type=Path,
help="Module path",
)

subparsers.add_parser(
"list",
help="List patches",
Expand Down Expand Up @@ -199,18 +253,23 @@ def filter_args(self, args):

def do_run(self, args, _):
self.filter_args(args)

if not os.path.isfile(args.patch_yml):
self.inf(f"no patches to apply: {args.patch_yml} not found")
return
yml = None

west_config = Path(args.west_workspace) / ".west" / "config"
if not os.path.isfile(west_config):
self.die(f"{args.west_workspace} is not a valid west workspace")

if not os.path.isfile(args.patch_yml):
if args.subcommand in ["gh-fetch"]:
yml = {"patches": []}
else:
self.inf(f"no patches to apply: {args.patch_yml} not found")
return

try:
with open(args.patch_yml) as f:
yml = yaml.load(f, Loader=SafeLoader)
if not yml:
with open(args.patch_yml) as f:
yml = yaml.load(f, Loader=SafeLoader)
if not yml:
self.inf(f"{args.patch_yml} is empty")
return
Expand All @@ -225,6 +284,7 @@ def do_run(self, args, _):
"apply": self.apply,
"clean": self.clean,
"list": self.list,
"gh-fetch": self.gh_fetch,
}

method[args.subcommand](args, yml, args.modules)
Expand Down Expand Up @@ -348,6 +408,47 @@ def list(self, args, yml, mods=None):
continue
self.inf(patch_info)

def gh_fetch(self, args, yml, mods=None):
if mods:
self.die(
"Module filters are not available for the gh-fetch subcommand, "
"pass a single -m/--module argument after the subcommand."
)

try:
from github import Github
except ImportError:
self.die("PyGithub not installed")

gh = Github()
pr = gh.get_repo(f"{args.owner}/{args.repo}").get_pull(args.pull_request)

filename = "-".join(re.split("[^a-zA-Z0-9]+", pr.title)) + ".patch"
args.patch_base.mkdir(parents=True, exist_ok=True)
urllib.request.urlretrieve(pr.patch_url, args.patch_base / filename)

with open(args.patch_base / filename, "rb") as fp:
hasher = hashlib.sha256()
hasher.update(fp.read())
pr_sha256 = hasher.hexdigest()

patch_info = {
"path": filename,
"sha256sum": pr_sha256,
"module": str(args.module),
"author": pr.user.name or "Hidden",
"email": pr.user.email or "hidden@github.com",
"date": pr.created_at.strftime("%Y-%m-%d"),
"upstreamable": True,
"merge-pr": pr.html_url,
"merge-status": pr.merged,
}

yml.setdefault("patches", []).append(patch_info)
args.patch_yml.parent.mkdir(parents=True, exist_ok=True)
with open(args.patch_yml, "w") as f:
yaml.dump(yml, f, Dumper=SafeDumper)

@staticmethod
def get_mod_paths(args, yml):
patches = yml.get("patches", [])
Expand Down

0 comments on commit 341733f

Please sign in to comment.