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