diff options
Diffstat (limited to 'debian/scripts/pypyclean')
-rwxr-xr-x | debian/scripts/pypyclean | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/debian/scripts/pypyclean b/debian/scripts/pypyclean new file mode 100755 index 0000000..83c8907 --- /dev/null +++ b/debian/scripts/pypyclean @@ -0,0 +1,200 @@ +#!/usr/bin/pypy + +import argparse +import collections +import itertools +import os +import shutil +import subprocess +import sys + + +def abort(message): + print >> sys.stderr, message + sys.exit(1) + + +def package_modules(package): + '''Iterate through all python modules in an installed Debian package''' + p = subprocess.Popen(('dpkg', '-L', package), stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + files, stderr = p.communicate() + if p.returncode != 0: + abort('Unable to list files in %s. Is it installed?' % package) + + for fn in files.splitlines(): + if fn.endswith('.py'): + if fn.startswith('/usr/share/doc/'): + continue + yield fn + + +def installed_namespaces(): + '''Return a dictionary of package: frozenset(namespaces)''' + ns_dir = '/usr/share/pypy/ns' + ns_by_pkg = {} + for pkg in os.listdir(ns_dir): + ns_file = os.path.join(ns_dir, pkg) + with open(ns_file) as f: + contents = f.read().decode('utf-8').strip() + namespaces = [line.strip() for line in contents.splitlines()] + ns_by_pkg[pkg] = frozenset(namespaces) + return ns_by_pkg + + +def cleanup_namespaces(package, verbose): + '''Check if a namespace is still being used and, if not: + Remove the __init__.py. + Remove any pycs related to it. + ''' + ns_by_pkg = installed_namespaces() + pkg_namespaces = ns_by_pkg.pop(package, None) + if not pkg_namespaces: + return + foreign_namespaces = reduce(lambda a, b: a | b, ns_by_pkg.values(), set()) + orphan_namespaces = pkg_namespaces - foreign_namespaces + inits_to_remove = [] + for namespace in orphan_namespaces: + init = os.path.join('/usr/lib/pypy/dist-packages', + namespace.replace('.', '/'), + '__init__.py') + if not os.path.exists(init): + print 'Missing namespace init: %s' % init + continue + if os.path.getsize(init) != 0: + print 'Non-empty init, ignoring: %s' % init + continue + inits_to_remove.append(init) + + clean_modules(inits_to_remove, verbose) + for init in inits_to_remove: + os.unlink(init) + + +def cleanup_package_modules(package, verbose): + '''Iterate through all python modules in an installed Debian package that + were in /usr/share/doc/, and previously byte-compiled by + pypy << 6.0.0+dfsg-2. See #904521 + + Can be removed once every pypy library has been re-uploaded, since this was + added. + ''' + p = subprocess.Popen(('dpkg', '-L', package), stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + files, stderr = p.communicate() + if p.returncode != 0: + abort('Unable to list files in %s. Is it installed?' % package) + + modules = [ + fn for fn in files.splitlines() + if fn.startswith('/usr/share/doc/') and fn.endswith('.py')] + + try: + clean_modules(modules, verbose) + except IOError as e: + if e.errno != errno.ENOENT: + raise + + +def find_modules(root): + '''Iterate through all python modules in directory tree root''' + if os.path.isfile(root): + yield root + return + + for dirpath, dirnames, filenames in os.walk(root): + for fn in filenames: + if fn.endswith('.py'): + yield os.path.join(dirpath, fn) + + +def clean_modules(modules, verbose): + '''Remove all .pyc files for every module specified''' + clean = collections.defaultdict(list) + for module in modules: + dir_, basename = os.path.split(module) + clean[dir_].append(os.path.splitext(basename)[0]) + + for dir_, basenames in clean.iteritems(): + pycache = os.path.join(dir_, '__pycache__') + if not os.path.exists(pycache): + continue + + empty = True + for fn in os.listdir(pycache): + if fn.endswith('.pyc') and fn.rsplit('.', 2)[0] in basenames: + if verbose: + print 'Removing %s' % os.path.join(pycache, fn) + os.unlink(os.path.join(pycache, fn)) + else: + empty = False + + if empty: + if verbose: + print 'Pruning %s' % pycache + os.rmdir(pycache) + + +def clean_directories(directories, verbose): + '''Indiscriminately remove __pycache__ directories''' + for root in directories: + for dirpath, dirnames, filenames in os.walk(root): + for dir_ in dirnames: + if dir_ == '__pycache__': + if verbose: + print 'Removing %s' % os.path.join(dirpath, dir_) + shutil.rmtree(os.path.join(dirpath, dir_)) + + +def main(): + parser = argparse.ArgumentParser( + description='Remove byte-compiled files for a package') + parser.add_argument('-p', '--package', metavar='PACKAGE', + action='append', default=[], + help='Debian package to byte-compile ' + '(may be specified multiple times)') + parser.add_argument('directory', nargs='*', + help='Directory tree (or file) to byte-compile') + parser.add_argument('-v', '--verbose', action='store_true', + help='Be more verbose') + parser.add_argument('-q', '--quiet', action='store_true', + help='Be quiet') + args = parser.parse_args() + + if not (args.package or args.directory): + parser.error('Either a package or a directory must be specified') + if args.quiet and args.verbose: + parser.error('--quiet and --verbose cannot both be specified') + + modules_p = set(itertools.chain(*( + package_modules(package) for package in args.package))) + modules_d = set(itertools.chain(*( + find_modules(dir_) for dir_ in args.directory))) + + if args.package and args.directory: + modules = modules_d & modules_p + elif args.package: + modules = modules_p + else: + # Split files from directories, so that we can completely clean any + # specified directories. + modules = set() + directories = set() + for fn in args.directory: + if os.path.isfile(fn) and fn.endswith('.py'): + modules.add(fn) + else: + directories.add(fn) + clean_directories(directories, args.verbose) + + clean_modules(modules, args.verbose) + + for package in args.package: + cleanup_namespaces(package, args.verbose) + cleanup_package_modules(package, args.verbose) + + +if __name__ == '__main__': + main() + +# vim: ft=python |