#!/usr/bin/pythonimport portage, portage_dep, os, sys, getoptfrom portage_versions import *VERSION = "0.1"def get_keyword_cpvs(cp, keywords): """ return a list of package versions marked with keywords. """ matched_cpvs = [] for cpv in portage.portdb.cp_list(cp): cpv_keywords = portage.portdb.aux_get(cpv, ["KEYWORDS"])[0].split(' ') for keyword in keywords: if keyword in cpv_keywords: matched_cpvs.append(cpv) break return matched_cpvsdef order_cpvs(cpvs): """ compare package cpvs and order low to high """ low_to_high = {} index = 0 index_len = len(cpvs) - 1 ct = 0 while len(cpvs) != 0: tmp_lowest = cpvs[ct] while ct < len(cpvs): if pkgcmp(pkgsplit(tmp_lowest), pkgsplit(cpvs[ct])) == 1: tmp_lowest = cpvs[ct] ct = ct + 1 low_to_high[index] = tmp_lowest cpvs.remove(tmp_lowest) index = index + 1 ct = 0 return low_to_highdef get_distfiles(cpv, keywords): """ return a list of distfiles used by the package version according to it's keywords. """ distfiles = [] paths = portage.portdb.aux_get(cpv, ["SRC_URI"]) for path in paths: parsed = portage_dep.paren_reduce(path) new_paths = portage_dep.use_reduce(parsed, keywords) # SRC_URI can return several unpredictable nested lists hence # the numerous and ugly for and while loops. for new_path in new_paths: if type(new_path) == list: for n in new_path: if type(n) == str: distfiles.append(os.path.basename(n)) else: while type(n) == list: n = n[-1] distfiles.append(os.path.basename(n)) else: distfiles.append(os.path.basename(new_path)) return distfilesdef usage(): myprog = os.path.basename(sys.argv[0]) print myprog+": Selectively clean old or unused distfiles on gentoo systems to free up disk space." print "" print "Usage: "+myprog+" --below-stable=[value] --above-stable=[value] [--keywords=[keywords]] [--destdir=[path]] [--yes]" print "" print "Options:" print " -h, --help Display this help message and exit." print " -b [value], --below-stable=[value] Number of package versions below the current stable to consider. 'all' may be used." print " -a [value], --above-stable=[value] Number of package versions above the current stable to consider. 'all' may be used." print " -k [keywords], --keywords=[keywords] Consider keywords. Defaults to ACCEPT_KEYWORDS portage setting." print " -d [path], --distdir=[path] Override DISTDIR portage setting with [path]." print " -y, --yes Automatically answer yes to all prompts." print " -v, --version Display version information and exit." print ""class Options: def __init__(self): self.long_opts = ["help", "below-stable=", "above-stable=", "keywords=", "distdir=", "yes", "version"] self.short_opts = "hb:a:k:d:yv" def parse(self): try: self.opts, self.args = getopt.getopt(sys.argv[1:], self.short_opts, self.long_opts) except getopt.GetoptError, e: sys.stderr.write("Error: "+str(e)+"\n\n") usage() sys.exit(1) self.help = 0 self.below_stable = "" self.above_stable = "" self.keywords = [] self.distdir = "" self.yes = 0 self.version = 0 for o, a in self.opts: if o in ("-h", "--help"): usage() sys.exit(0) if o in ("-b", "--below-stable"): self.below_stable = a if o in ("-a", "--above-stable"): self.above_stable = a if o in ("-k", "--keywords"): self.keywords.extend(a.split(' ')) if o in ("-d", "--distdir"): self.distdir = a if o in ("-y", "--yes"): self.yes = 1 if o in ("-v", "--version"): self.version = VERSION print self.version sys.exit(0) def validate(self): if self.below_stable == "" or self.above_stable == "": sys.stderr.write("Error: You must specify --below-stable= and --above-stable= options!\n\n") usage() sys.exit(1) if self.below_stable != "all": try: self.below_stable = int(self.below_stable) except: sys.stderr.write("Error: '"+self.below+"' is not an integer!\n") sys.exit(1) if self.above_stable != "all": try: self.above_stable = int(self.above_stable) except: sys.stderr.write("Error: '"+self.above_stable+"' is not an integer!\n") sys.exit(1) if self.distdir != "": if not os.path.isdir(self.distdir): sys.stderr.write("Error: distdir '"+self.distdir+"' does not exist!\n") sys.exit(1)if __name__ == "__main__": if os.getuid() != 0: sys.stderr.write("You must be root to run this program.\n") sys.exit(1) # get and validate options options = Options() options.parse() options.validate() # assign options if options.keywords == []: options.keywords.extend(portage.settings["ACCEPT_KEYWORDS"].split(' ')) if options.distdir == "": options.distdir = portage.settings["DISTDIR"] # local variables total_freed_space = 0 remove_list = [] M = 1048576 # Megabytes # iterate one package at a time. cp: package category, cpv: package version. for cp in portage.portdb.cp_all(): excluded_cpvs = [] cpvs = portage.portdb.cp_list(cp) stable_cpvs = get_keyword_cpvs(cp, options.keywords) if len(stable_cpvs) == 0: continue # rearrange packages from lowest version to highest in a dict so we # can easily reference which versions to keep and delete. ordered_cpvs = order_cpvs(cpvs) del(cpvs) current_stable_cpv = stable_cpvs[-1] for i in range(len(ordered_cpvs)): if ordered_cpvs.get(i) == current_stable_cpv: current_stable_cpv_index = i if options.below_stable == "all": below_stable_index = 0 else: below_stable_index = (current_stable_cpv_index - options.below_stable) if options.above_stable == "all": above_stable_index = (len(ordered_cpvs) - 1) else: above_stable_index = (current_stable_cpv_index + options.above_stable) ct = below_stable_index while ct <= above_stable_index: try: if ordered_cpvs[ct] not in excluded_cpvs: excluded_cpvs.append(ordered_cpvs[ct]) except KeyError: pass ct = ct + 1 # single out versions not satisfied above and add to list for removal for key in ordered_cpvs: cpv = ordered_cpvs.get(key) if cpv not in excluded_cpvs: distfiles = get_distfiles(cpv, options.keywords) for distfile in distfiles: if distfile != '': distfile_path = os.path.join(options.distdir, distfile) if os.path.isfile(distfile_path): if distfile_path not in remove_list: remove_list.append(distfile_path) if len(remove_list) == 0: print "Nothing to clean." sys.exit(0) # report our findings max_columns = 70 for distfile_path in remove_list: whole = (os.stat(distfile_path)[6] / M) remain = (os.stat(distfile_path)[6] % M) size = str(round((whole + float("."+str(remain))), 1)) format_distfile_path = distfile_path+":" format_size = "["+size+"M]" fill_spaces = " "*(max_columns - len(format_distfile_path)) print format_distfile_path+fill_spaces+format_size total_freed_space = total_freed_space + float(size) print "\nTotal Size: "+str(round(total_freed_space, 1))+"M\n" # execute removal if desired. sys.stdout.write("Remove files? [Y/n]") if options.yes: response = 'y' else: response = raw_input() response = response.lower() if response == 'y' or response == '': for distfile_path in remove_list: print "Removing: "+distfile_path try: os.remove(distfile_path) except Exception, e: sys.stderr.write("Could not remove '"+distfile_path+"': "+str(e)+"\n") sys.exit(0)