| #!/usr/bin/env python3 |
| # |
| # Copyright OpenEmbedded Contributors |
| # |
| # SPDX-License-Identifier: MIT |
| # |
| # This script is to be called by b4: |
| # - through the b4.prep-perpatch-check-cmd with "prep-perpatch-check-cmd" as |
| # first argument, |
| # - through b4.send-auto-cc-cmd with "send-auto-cc-cmd" as first argument, |
| # - through b4.send-auto-to-cmd with "send-auto-to-cmd" as first argument, |
| # |
| # When prep-perpatch-check-cmd is passsed: |
| # |
| # This checks that a patch makes changes to at most one project in the poky |
| # combo repo (that is, out of yocto-docs, bitbake, openembedded-core combined |
| # into poky and the poky-specific files). |
| # |
| # Printing something to stdout in this file will result in b4 prep --check fail |
| # for the currently parsed patch. |
| # |
| # It checks that all patches in the series make changes to at most one project. |
| # |
| # When send-auto-cc-cmd is passed: |
| # |
| # This returns the list of Cc recipients for a patch. |
| # |
| # When send-auto-to-cmd is passed: |
| # |
| # This returns the list of To recipients for a patch. |
| # |
| # This script takes as stdin a patch. |
| |
| import pathlib |
| import re |
| import shutil |
| import subprocess |
| import sys |
| |
| cmd = sys.argv[1] |
| |
| patch = sys.stdin.readlines() |
| |
| # Subject field is used to identify the last patch as this script is called for |
| # each patch. We edit the same file in a series by using the References field |
| # unique identifier to check which projects are modified by earlier patches in |
| # the series. To avoid cluttering the disk, the last patch in the list removes |
| # that shared file. |
| re_subject = re.compile(r'^Subject:.*\[.*PATCH.*\s(\d+)/\1') |
| re_ref = re.compile(r'^References: <(.*)>$') |
| |
| subject = None |
| ref = None |
| |
| if not shutil.which("lsdiff"): |
| print("lsdiff missing from host, please install patchutils") |
| sys.exit(-1) |
| |
| try: |
| one_patch_series = False |
| for line in patch: |
| subject = re_subject.match(line) |
| if subject: |
| # Handle [PATCH 1/1] |
| if subject.group(1) == 1: |
| one_patch_series = True |
| break |
| if re.match(r'^Subject: .*\[.*PATCH[^/]*\]', line): |
| # Single patch is named [PATCH] but if there are prefix, it could be |
| # [PATCH prefix], so handle everything that doesn't have a / |
| # character which is used as separator between current patch number |
| # and total patch number |
| one_patch_series = True |
| break |
| |
| if cmd == "prep-perpatch-check-cmd" and not one_patch_series: |
| for line in patch: |
| ref = re_ref.match(line) |
| if ref: |
| break |
| |
| if not ref: |
| print("Failed to find ref to cover letter (References:)...") |
| sys.exit(-2) |
| |
| ref = ref.group(1) |
| series_check = pathlib.Path(f".tmp-{ref}") |
| |
| patch = "".join(patch) |
| |
| if cmd == "send-auto-cc-cmd": |
| # Patches to BitBake documentation should also go to yocto-docs mailing list |
| project_paths = { |
| "yocto-docs": ["bitbake/doc/*"], |
| } |
| else: |
| project_paths = { |
| "bitbake": ["bitbake/*"], |
| "yocto-docs": ["documentation/*"], |
| "poky": [ |
| "meta-poky/*", |
| "meta-yocto-bsp/*", |
| "README.hardware.md", |
| "README.poky.md", |
| # scripts/b4-wrapper-poky.py is only run by b4 when in poky |
| # git repo. With that limitation, changes made to .b4-config |
| # can only be for poky's and not OE-Core's as only poky's is |
| # stored in poky git repo. |
| ".b4-config", |
| ], |
| } |
| |
| # List of projects touched by this patch |
| projs = [] |
| |
| # Any file not matched by any path in project_paths means it is from |
| # OE-Core. |
| # When matching some path in project_paths, remove the matched files from |
| # that list. |
| files_left = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"], |
| input=patch, text=True) |
| files_left = set(files_left) |
| |
| for proj, proj_paths in project_paths.items(): |
| lsdiff_args = [f"--include={path}" for path in proj_paths] |
| files = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"] + lsdiff_args, |
| input=patch, text=True) |
| if len(files): |
| files_left = files_left - set(files) |
| projs.append(proj) |
| continue |
| |
| # Handle patches made with --no-prefix |
| files = subprocess.check_output(["lsdiff"] + lsdiff_args, |
| input=patch, text=True) |
| if len(files): |
| files_left = files_left - set(files) |
| projs.append(proj) |
| |
| # Catch-all for everything not poky-specific or in bitbake/yocto-docs |
| if len(files_left) and cmd != "send-auto-cc-cmd": |
| projs.append("openembedded-core") |
| |
| if cmd == "prep-perpatch-check-cmd": |
| if len(projs) > 1: |
| print(f"Diff spans more than one project ({', '.join(sorted(projs))}), split into multiple commits...") |
| sys.exit(-3) |
| |
| # No need to check other patches in the series as there aren't any |
| if one_patch_series: |
| sys.exit(0) |
| |
| # This should be replaced once b4 supports prep-perseries-check-cmd (or something similar) |
| |
| if series_check.exists(): |
| # NOT race-free if b4 decides to parallelize prep-perpatch-check-cmd |
| series_projs = series_check.read_text().split('\n') |
| else: |
| series_projs = [] |
| |
| series_projs += projs |
| uniq_series_projs = set(series_projs) |
| # NOT race-free, if b4 decides to parallelize prep-perpatch-check-cmd |
| series_check.write_text('\n'.join(uniq_series_projs)) |
| |
| if len(uniq_series_projs) > 1: |
| print(f"Series spans more than one project ({', '.join(sorted(uniq_series_projs))}), split into multiple series...") |
| sys.exit(-4) |
| else: # send-auto-cc-cmd / send-auto-to-cmd |
| ml_projs = { |
| "bitbake": "bitbake-devel@lists.openembedded.org", |
| "yocto-docs": "docs@lists.yoctoproject.org", |
| "poky": "poky@lists.yoctoproject.org", |
| "openembedded-core": "openembedded-core@lists.openembedded.org", |
| } |
| |
| print("\n".join([ml_projs[ml] for ml in projs])) |
| |
| sys.exit(0) |
| finally: |
| # Last patch in the series, cleanup tmp file |
| if subject and ref and series_check.exists(): |
| series_check.unlink() |