From: Thomas Petazzoni Date: Mon, 12 Sep 2016 20:54:52 +0000 (+0200) Subject: support/scripts/get-developers: add new script X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=183d9b654c72d82dea254d9c434e69ddfab76658;p=buildroot.git support/scripts/get-developers: add new script This script, and its companion library, is more-or-less Buildroot's equivalent to the kernel get_maintainer.pl script: it allows to get the list of developers to whom a set of patches should be sent to. To do so, it first relies on a text file, named DEVELOPERS, at the root of the Buildroot source tree (added in a followup commit) to list the developers and the files they are interested in. The DEVELOPERS file's format is simple: N: Firstname Lastname F: path/to/file F: path/to/another/file This allows to associate developers with the files they are looking after, be they related to a package, a defconfig, a filesystem image, a package infrastructure, the documentation, or anything else. When a directory is given, the tool assumes that the developer handles all files and subdirectories in this directory. For example "package/qt5/" can be used for the developers looking after all the Qt5 packages. Conventional shell patterns can be used, so "package/python-*" can be used for the developers who want to look after all packages matching "python-*". A few files are recognized specially: - .mk files are parsed, and if they contain $(eval $(-package)), the developer is assumed to be looking after the corresponding package. This way, autobuilder failures for this package can be reported directly to this developer. - arch/Config.in. files are recognized as "the developer is looking after the architecture". In this case, get-developer parses the arch/Config.in. to get the list of possible BR2_ARCH values. This way, autobuilder failures for this package can be reported directly to this developer. - pkg/pkg-.mk are recognized as "the developer is looking after the package infrastructure. In this case, any patch that adds or touches a .mk file that uses this infrastructure will be sent to this developer. Examples of usage: $ ./support/scripts/get-developers 0001-ffmpeg-fix-bfin-build.patch git send-email--to buildroot@buildroot.org --to "Luca Ceresoli " --to "Bernd Kuhls " $ ./support/scripts/get-developers -p imx-lib Arnout Vandecappelle Gary Bisson $ ./support/scripts/get-developers -a bfin Waldemar Brodkorb Signed-off-by: Thomas Petazzoni Reviewed-by: Arnout Vandecappelle (Essensium/Mind) Signed-off-by: Peter Korsgaard --- diff --git a/support/scripts/get-developers b/support/scripts/get-developers new file mode 100755 index 0000000000..f73512f17a --- /dev/null +++ b/support/scripts/get-developers @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +import argparse +import getdeveloperlib + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('patches', metavar='P', type=str, nargs='*', + help='list of patches') + parser.add_argument('-a', dest='architecture', action='store', + help='find developers in charge of this architecture') + parser.add_argument('-p', dest='package', action='store', + help='find developers in charge of this package') + parser.add_argument('-c', dest='check', action='store_const', + const=True, help='list files not handled by any developer') + return parser.parse_args() + +def __main__(): + devs = getdeveloperlib.parse_developers() + if devs is None: + sys.exit(1) + args = parse_args() + + # Check that only one action is given + action = 0 + if args.architecture is not None: + action += 1 + if args.package is not None: + action += 1 + if args.check: + action += 1 + if len(args.patches) != 0: + action += 1 + if action > 1: + print("Cannot do more than one action") + return + if action == 0: + print("No action specified") + return + + # Handle the check action + if args.check: + files = getdeveloperlib.check_developers(devs) + for f in files: + print f + + # Handle the architecture action + if args.architecture is not None: + for dev in devs: + if args.architecture in dev.architectures: + print(dev.name) + return + + # Handle the package action + if args.package is not None: + for dev in devs: + if args.package in dev.packages: + print(dev.name) + return + + # Handle the patches action + if len(args.patches) != 0: + (files, infras) = getdeveloperlib.analyze_patches(args.patches) + matching_devs = set() + for dev in devs: + # See if we have developers matching by package name + for f in files: + if dev.hasfile(f): + matching_devs.add(dev.name) + # See if we have developers matching by package infra + for i in infras: + if i in dev.infras: + matching_devs.add(dev.name) + + result = "--to buildroot@buildroot.org" + for dev in matching_devs: + result += " --to \"%s\"" % dev + + if result != "": + print("git send-email %s" % result) + +__main__() + diff --git a/support/scripts/getdeveloperlib.py b/support/scripts/getdeveloperlib.py new file mode 100644 index 0000000000..7b390417fc --- /dev/null +++ b/support/scripts/getdeveloperlib.py @@ -0,0 +1,201 @@ +import sys +import os +import re +import argparse +import glob +import subprocess + +# +# Patch parsing functions +# + +FIND_INFRA_IN_PATCH = re.compile("^\+\$\(eval \$\((host-)?([^-]*)-package\)\)$") + +def analyze_patch(patch): + """Parse one patch and return the list of files modified, added or + removed by the patch.""" + files = set() + infras = set() + with open(patch, "r") as f: + for line in f: + # If the patch is adding a package, find which infra it is + m = FIND_INFRA_IN_PATCH.match(line) + if m: + infras.add(m.group(2)) + if not line.startswith("+++ "): + continue + line.strip() + fname = line[line.find("/") + 1 : ].strip() + if fname == "dev/null": + continue + files.add(fname) + return (files, infras) + +FIND_INFRA_IN_MK = re.compile("^\$\(eval \$\((host-)?([^-]*)-package\)\)$") + +def fname_get_package_infra(fname): + """Checks whether the file name passed as argument is a Buildroot .mk + file describing a package, and find the infrastructure it's using.""" + if not fname.endswith(".mk"): + return None + + if not os.path.exists(fname): + return None + + with open(fname, "r") as f: + for l in f: + l = l.strip() + m = FIND_INFRA_IN_MK.match(l) + if m: + return m.group(2) + return None + +def get_infras(files): + """Search in the list of files for .mk files, and collect the package + infrastructures used by those .mk files.""" + infras = set() + for fname in files: + infra = fname_get_package_infra(fname) + if infra: + infras.add(infra) + return infras + +def analyze_patches(patches): + """Parse a list of patches and returns the list of files modified, + added or removed by the patches, as well as the list of package + infrastructures used by those patches (if any)""" + allfiles = set() + allinfras = set() + for patch in patches: + (files, infras) = analyze_patch(patch) + allfiles = allfiles | files + allinfras = allinfras | infras + allinfras = allinfras | get_infras(allfiles) + return (allfiles, allinfras) + +# +# DEVELOPERS file parsing functions +# + +class Developer: + def __init__(self, name, files): + self.name = name + self.files = files + self.packages = parse_developer_packages(files) + self.architectures = parse_developer_architectures(files) + self.infras = parse_developer_infras(files) + + def hasfile(self, f): + f = os.path.abspath(f) + for fs in self.files: + if f.startswith(fs): + return True + return False + +def parse_developer_packages(fnames): + """Given a list of file patterns, travel through the Buildroot source + tree to find which packages are implemented by those file + patterns, and return a list of those packages.""" + packages = set() + for fname in fnames: + for root, dirs, files in os.walk(fname): + for f in files: + path = os.path.join(root, f) + if fname_get_package_infra(path): + pkg = os.path.splitext(f)[0] + packages.add(pkg) + return packages + +def parse_arches_from_config_in(fname): + """Given a path to an arch/Config.in.* file, parse it to get the list + of BR2_ARCH values for this architecture.""" + arches = set() + with open(fname, "r") as f: + parsing_arches = False + for l in f: + l = l.strip() + if l == "config BR2_ARCH": + parsing_arches = True + continue + if parsing_arches: + m = re.match("^\s*default \"([^\"]*)\".*", l) + if m: + arches.add(m.group(1)) + else: + parsing_arches = False + return arches + +def parse_developer_architectures(fnames): + """Given a list of file names, find the ones starting by + 'arch/Config.in.', and use that to determine the architecture a + developer is working on.""" + arches = set() + for fname in fnames: + if not re.match("^.*/arch/Config\.in\..*$", fname): + continue + arches = arches | parse_arches_from_config_in(fname) + return arches + +def parse_developer_infras(fnames): + infras = set() + for fname in fnames: + m = re.match("^package/pkg-([^.]*).mk$", fname) + if m: + infras.add(m.group(1)) + return infras + +def parse_developers(basepath=None): + """Parse the DEVELOPERS file and return a list of Developer objects.""" + developers = [] + linen = 0 + if basepath == None: + basepath = os.getcwd() + with open(os.path.join(basepath, "DEVELOPERS"), "r") as f: + files = [] + name = None + for l in f: + l = l.strip() + if l.startswith("#"): + continue + elif l.startswith("N:"): + if name is not None or len(files) != 0: + print("Syntax error in DEVELOPERS file, line %d" % linen) + name = l[2:].strip() + elif l.startswith("F:"): + fname = l[2:].strip() + dev_files = glob.glob(os.path.join(basepath, fname)) + if len(dev_files) == 0: + print("WARNING: '%s' doesn't match any file" % fname) + files += dev_files + elif l == "": + if not name: + continue + developers.append(Developer(name, files)) + files = [] + name = None + else: + print("Syntax error in DEVELOPERS file, line %d: '%s'" % (linen, l)) + return None + linen += 1 + # handle last developer + if name is not None: + developers.append(Developer(name, files)) + return developers + +def check_developers(developers, basepath=None): + """Look at the list of files versioned in Buildroot, and returns the + list of files that are not handled by any developer""" + if basepath == None: + basepath = os.getcwd() + cmd = ["git", "--git-dir", os.path.join(basepath, ".git"), "ls-files"] + files = subprocess.check_output(cmd).strip().split("\n") + unhandled_files = [] + for f in files: + handled = False + for d in developers: + if d.hasfile(os.path.join(basepath, f)): + handled = True + break + if not handled: + unhandled_files.append(f) + return unhandled_files