1#!/usr/bin/env python3 2# 3# Copyright OpenEmbedded Contributors 4# 5# SPDX-License-Identifier: MIT 6# 7# This script is to be called by b4: 8# - through the b4.prep-perpatch-check-cmd with "prep-perpatch-check-cmd" as 9# first argument, 10# - through b4.send-auto-cc-cmd with "send-auto-cc-cmd" as first argument, 11# - through b4.send-auto-to-cmd with "send-auto-to-cmd" as first argument, 12# 13# When prep-perpatch-check-cmd is passsed: 14# 15# This checks that a patch makes changes to at most one project in the poky 16# combo repo (that is, out of yocto-docs, bitbake, openembedded-core combined 17# into poky and the poky-specific files). 18# 19# Printing something to stdout in this file will result in b4 prep --check fail 20# for the currently parsed patch. 21# 22# It checks that all patches in the series make changes to at most one project. 23# 24# When send-auto-cc-cmd is passed: 25# 26# This returns the list of Cc recipients for a patch. 27# 28# When send-auto-to-cmd is passed: 29# 30# This returns the list of To recipients for a patch. 31# 32# This script takes as stdin a patch. 33 34import pathlib 35import re 36import shutil 37import subprocess 38import sys 39 40cmd = sys.argv[1] 41 42patch = sys.stdin.readlines() 43 44# Subject field is used to identify the last patch as this script is called for 45# each patch. We edit the same file in a series by using the References field 46# unique identifier to check which projects are modified by earlier patches in 47# the series. To avoid cluttering the disk, the last patch in the list removes 48# that shared file. 49re_subject = re.compile(r'^Subject:.*\[.*PATCH.*\s(\d+)/\1') 50re_ref = re.compile(r'^References: <(.*)>$') 51 52subject = None 53ref = None 54 55if not shutil.which("lsdiff"): 56 print("lsdiff missing from host, please install patchutils", file=sys.stderr) 57 sys.exit(-1) 58 59try: 60 one_patch_series = False 61 for line in patch: 62 subject = re_subject.match(line) 63 if subject: 64 # Handle [PATCH 1/1] 65 if subject.group(1) == 1: 66 one_patch_series = True 67 break 68 if re.match(r'^Subject: .*\[.*PATCH[^/]*\]', line): 69 # Single patch is named [PATCH] but if there are prefix, it could be 70 # [PATCH prefix], so handle everything that doesn't have a / 71 # character which is used as separator between current patch number 72 # and total patch number 73 one_patch_series = True 74 break 75 76 if cmd == "prep-perpatch-check-cmd" and not one_patch_series: 77 for line in patch: 78 ref = re_ref.match(line) 79 if ref: 80 break 81 82 if not ref: 83 print("Failed to find ref to cover letter (References:)...", file=sys.stderr) 84 sys.exit(-2) 85 86 ref = ref.group(1) 87 series_check = pathlib.Path(f".tmp-{ref}") 88 89 patch = "".join(patch) 90 91 if cmd == "send-auto-cc-cmd": 92 # Patches to BitBake documentation should also go to yocto-docs mailing list 93 project_paths = { 94 "yocto-docs": ["bitbake/doc/*"], 95 } 96 else: 97 project_paths = { 98 "bitbake": ["bitbake/*"], 99 "yocto-docs": ["documentation/*"], 100 "poky": [ 101 "meta-poky/*", 102 "meta-yocto-bsp/*", 103 "README.hardware.md", 104 "README.poky.md", 105 # scripts/b4-wrapper-poky.py is only run by b4 when in poky 106 # git repo. With that limitation, changes made to .b4-config 107 # can only be for poky's and not OE-Core's as only poky's is 108 # stored in poky git repo. 109 ".b4-config", 110 ], 111 } 112 113 # List of projects touched by this patch 114 projs = [] 115 116 # Any file not matched by any path in project_paths means it is from 117 # OE-Core. 118 # When matching some path in project_paths, remove the matched files from 119 # that list. 120 files_left = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"], 121 input=patch, text=True) 122 files_left = set(files_left) 123 124 for proj, proj_paths in project_paths.items(): 125 lsdiff_args = [f"--include={path}" for path in proj_paths] 126 files = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"] + lsdiff_args, 127 input=patch, text=True) 128 if len(files): 129 files_left = files_left - set(files) 130 projs.append(proj) 131 continue 132 133 # Handle patches made with --no-prefix 134 files = subprocess.check_output(["lsdiff"] + lsdiff_args, 135 input=patch, text=True) 136 if len(files): 137 files_left = files_left - set(files) 138 projs.append(proj) 139 140 # Catch-all for everything not poky-specific or in bitbake/yocto-docs 141 if len(files_left) and cmd != "send-auto-cc-cmd": 142 projs.append("openembedded-core") 143 144 if cmd == "prep-perpatch-check-cmd": 145 if len(projs) > 1: 146 print(f"Diff spans more than one project ({', '.join(sorted(projs))}), split into multiple commits...", 147 file=sys.stderr) 148 sys.exit(-3) 149 150 # No need to check other patches in the series as there aren't any 151 if one_patch_series: 152 sys.exit(0) 153 154 # This should be replaced once b4 supports prep-perseries-check-cmd (or something similar) 155 156 if series_check.exists(): 157 # NOT race-free if b4 decides to parallelize prep-perpatch-check-cmd 158 series_projs = series_check.read_text().split('\n') 159 else: 160 series_projs = [] 161 162 series_projs += projs 163 uniq_series_projs = set(series_projs) 164 # NOT race-free, if b4 decides to parallelize prep-perpatch-check-cmd 165 series_check.write_text('\n'.join(uniq_series_projs)) 166 167 if len(uniq_series_projs) > 1: 168 print(f"Series spans more than one project ({', '.join(sorted(uniq_series_projs))}), split into multiple series...", 169 file=sys.stderr) 170 sys.exit(-4) 171 else: # send-auto-cc-cmd / send-auto-to-cmd 172 ml_projs = { 173 "bitbake": "bitbake-devel@lists.openembedded.org", 174 "yocto-docs": "docs@lists.yoctoproject.org", 175 "poky": "poky@lists.yoctoproject.org", 176 "openembedded-core": "openembedded-core@lists.openembedded.org", 177 } 178 179 print("\n".join([ml_projs[ml] for ml in projs])) 180 181 sys.exit(0) 182finally: 183 # Last patch in the series, cleanup tmp file 184 if subject and ref and series_check.exists(): 185 series_check.unlink() 186