# Copyright (C) 2012 Sebastian Pipping # Licender under GPL v2 or later from __future__ import print_function VERSION_STR = '0.5.3' import sys import os import portage import re try: import argparse except ImportError: print("ERROR: You need Python 2.7+ unless you have module argparse " "(package dev-python/argparse on Gentoo) installed independently.", file=sys.stderr) sys.exit(1) _revision_matcher = re.compile('-r([0-9]+)$') _ignore_matcher = re.compile('(?:^# (?:\\$Header|Copyright)|KEYWORDS=)') def get_portdir(): return portage.settings["PORTDIR"] def find_atoms(repository_path, category_package): versions = list() category, package = category_package.split('/') for root, dirs, files in os.walk(os.path.join(repository_path, category_package)): for f in files: if not f.endswith('.ebuild'): continue versions.append(f[len(package) + 1:-len('.ebuild')]) return versions def highest_revision_only(versions): def make_version(version, revison): if revision == 0: return version return '%s-r%s' % (version, revison) d = dict() for v in versions: match = _revision_matcher.search(v) if match is None: revision = 0 version = v else: revision = int(match.group(1)) version = v[:-len(match.group(0))] d[version] = max(d.get(version, 0), revision) return [make_version(version, revison) for version, revison in d.items()] def find_missed_bumps(gentoo_versions, overlay_versions): missed_version_bumps = list() missed_revision_bumps = list() gentoo_zero_revisions = set(_revision_matcher.sub('', gv) for gv in gentoo_versions) for ov in overlay_versions: if '999' in ov: continue newer_than_gentoo = True for gv in gentoo_versions: if '999' in gv: continue if portage.vercmp(ov, gv) <= 0: newer_than_gentoo = False break if newer_than_gentoo: ov_without_revision = _revision_matcher.sub('', ov) if ov_without_revision in gentoo_zero_revisions: missed_revision_bumps.append(ov) else: missed_version_bumps.append(ov) return (missed_revision_bumps, missed_version_bumps) def find_lagging_behind_gentoo(gentoo_versions, overlay_versions): lagging_behind_gentoo = list() for ov in overlay_versions: if '999' in ov: continue for gv in gentoo_versions: if '999' in gv: continue if ov == gv: continue if portage.vercmp(ov, gv) < 0 and ov not in gentoo_versions: lagging_behind_gentoo.append(ov) break return lagging_behind_gentoo def _iter_important_lines(ebuild_f): return (line_br for line_br in ebuild_f if _ignore_matcher.search(line_br) is None) def ebuilds_are_equivalent(filename_a, filename_b): fa = open(filename_a, 'r') fb = open(filename_b, 'r') for (la, lb) in zip(_iter_important_lines(fa), _iter_important_lines(fb)): if la != lb: return False return True def find_ebuild_changes(category_package, overlay_path, gentoo_versions, overlay_versions): ebuild_changes = list() intersection = set(gentoo_versions) & set(overlay_versions) if not intersection: return list() category, package = category_package.split('/') for version in intersection: ebuild_name = '%s-%s.ebuild' % (package, version) filename_a = os.path.join(get_portdir(), category_package, ebuild_name) filename_b = os.path.join(overlay_path, category_package, ebuild_name) if ebuilds_are_equivalent(filename_a, filename_b): continue # print("meld '%s' '%s'" % (filename_a, filename_b)) ebuild_changes.append(version) return ebuild_changes def sorted_versions(versions): return sorted(versions, key=portage.versions.cmp_sort_key(portage.vercmp)) def dump_tree(tree, title): print('===============================================================') print(title) print('===============================================================') for category, package_versions in sorted(tree.items()): print('%s/' % category) for package, versions in sorted(package_versions.items()): print(' %s :: %s' % (package, ', '.join(sorted_versions(versions)))) print() def parse_command_line(args): parser = argparse.ArgumentParser(description='Simple tool for static analysis of Gentoo overlays') parser.add_argument( '--version', action='version', version='%(prog)s ' + VERSION_STR) parser.add_argument('overlay_path', metavar='PATH', action='store', help='Path to Gentoo overlay') conf = parser.parse_args(args=args[1:]) return conf def main(args): conf = parse_command_line(args) def sanitize_overlay_path(path): path = path.rstrip('/') if path == '': return '/' return path conf.overlay_path = sanitize_overlay_path(conf.overlay_path) if not os.path.isdir(conf.overlay_path): print("ERROR: Path \"%s\" is not a diretory" % conf.overlay_path, file=sys.stderr) return 1 missed_revision_bumps_tree = dict() missed_version_bumps_tree = dict() ebuild_changes_tree = dict() lagging_behind_gentoo__tree = dict() for root, dirs, files in os.walk(conf.overlay_path): if '.svn' in dirs: dirs[:] = [d for d in dirs if d != '.svn'] for d in dirs: full_path_overlay = os.path.join(root, d) category_package = full_path_overlay[len(conf.overlay_path + '/'):] if len(category_package.split('/')) != 2: continue full_path_gentoo = os.path.join(get_portdir(), category_package) found = os.path.exists(full_path_gentoo) if not found: continue overlay_versions = find_atoms(conf.overlay_path, category_package) gentoo_versions = find_atoms(get_portdir(), category_package) (missed_revision_bumps, missed_version_bumps) = find_missed_bumps(gentoo_versions, overlay_versions) lagging_behind_gentoo = find_lagging_behind_gentoo(gentoo_versions, overlay_versions) ebuild_changes = find_ebuild_changes(category_package, conf.overlay_path, gentoo_versions, overlay_versions) category, package = category_package.split('/') if missed_revision_bumps: missed_revision_bumps_tree.setdefault(category, dict())[package] = highest_revision_only(missed_revision_bumps) if missed_version_bumps: missed_version_bumps_tree.setdefault(category, dict())[package] = highest_revision_only(missed_version_bumps) if ebuild_changes: ebuild_changes_tree.setdefault(category, dict())[package] = ebuild_changes if lagging_behind_gentoo: lagging_behind_gentoo__tree.setdefault(category, dict())[package] = lagging_behind_gentoo dump_tree(missed_version_bumps_tree, 'Version bumps missing from Gentoo main tree') dump_tree(missed_revision_bumps_tree, 'Revision bumps missing from Gentoo main tree') dump_tree(lagging_behind_gentoo__tree, 'Ebuilds lagging behind the Gentoo main tree') dump_tree(ebuild_changes_tree, 'Ebuilds that differ at same revision') return 0 def go(): ret = main(sys.argv) sys.exit(ret)