#!/usr/bin/python # # Copyright 2006 Mike Pagano # Distributed under the terms of the GNU General Public License v2 # # $Header$ # Author: Mike Pagano # # Portions written ripped from # - equery, by Karl Trygve Kalleberg # - gentoolkit.py, by Karl Trygve Kalleberg # - portage.py # - emerge # __author__ = "Michael Pagano" __email__ = "mpagano@gmail.com" __version__ = "1.4.0" __productname__ = "portpeek" __description__ = "Displays user unmasked ebuilds and installable options from the portage tree" import sys, os, portage, output, string, fileinput sys.path.insert(0, "/usr/lib/gentoolkit/pym") import gentoolkit from portage_const import USER_CONFIG_PATH from portage_versions import catpkgsplit porttree = portage.db[portage.root]["porttree"] settings = portage.config(clone=portage.settings) show_changes_only_flag = False checking_package_unmask = False checking_package_mask = False print_overlay_flag = False info = 0 debug = 1 logLevel = info show_removable_only_flag = False stable_list = [] #parameters options = [ "--keyword", "--unmask", "--mask", "--all", "--changes-only", "--version", "--help", "--removable-only", "--debug", "--fix" ] mappings = { "k":"--keyword", "u":"--unmask", "m":"--mask", "a":"--all", "c":"--changes-only", "V":"--version", "h":"--help", "r":"--removable-only", "d":"--debug", "f":"--fix" } cmdline = [] overlay_list = [settings["PORTDIR"]] def print_usage(): # Print full usage information for this tool to the console. print "\nUsage: " + output.turquoise(__productname__) + output.yellow(" command ") print " " + output.turquoise(__productname__) + output.green(" [ options ]") + output.yellow(" command ") print " " + output.turquoise(__productname__) + output.green(" [-c]") + output.yellow(" [akmu]") print " " + output.turquoise(__productname__) + output.green(" [-r]") + output.yellow(" [akmu]") print " " + output.turquoise(__productname__) + output.green(" [-f]") + output.yellow(" [akmu]") print " " + output.turquoise(__productname__) + output.green(" [-F]") + output.yellow(" [akmu]") print output.yellow(" command ") + " can be " print output.yellow(" -a, --all") + " - show all matches" print output.yellow(" -k, --keyword") + " - show matches from package.keyword only" print output.yellow(" -m, --mask") + " - show matches from package.mask only" print output.yellow(" -u, --unmask") + " - show matched from package.unmask only" print output.yellow(" -f, --fix") + " - will remove the stabled packages without asking for confirmation" print output.yellow(" -h, --help") + " - display this message" print output.yellow(" -d, --debug") + " - display more verbose output for debugging" print output.yellow(" -V, --version") + " - display version info" print output.green("options") + " are " print output.green(" -c, --changes-only") + \ " - show all matches that have upgrade option, use with " + \ "<" + output.yellow(" k ") + "|" + output.yellow(" u ") + \ "|" + output.yellow(" m ") + "|" + \ output.yellow(" a ") + ">" print output.green(" -r, --removable-only") + \ " - show all matches that can be removed from package files, use with " + \ "<" + output.yellow(" k ") + "|" + output.yellow(" u ") + \ "|" + output.yellow(" m ") + "|" + \ output.yellow(" a ") + ">\n" def get_keywords(package, var): mytree = porttree filtered_keywords = "" try: keywords = mytree.dbapi.aux_get(package._cpv, [var]) except KeyError, error: print "!!! Portpeek caught Exception:", error print "!!! This package/version seems to be no longer available, " + \ "please check and update/unmerge it" return "Not Available/Deprecated" filtered_keywords = filter_keywords(keywords[0]) return filtered_keywords def parse_line(line, filename): global info,debug pkgs = None ebuild_output = "" check_pkg = "" not_installed_pkgs = 0 pkg_length = 0 diffs_found = False display_done = False fields = line.replace("\t", " ").split(" ") if len(fields) > 0: package_exists = portage.portdb.xmatch("match-all", fields[0]) if package_exists: pkgs = gentoolkit.find_packages(fields[0], True) if pkgs != None: pkg_length = len(pkgs) not_installed_pkgs = 0 display_done = False for current_package in pkgs: if not current_package.is_installed(): not_installed_pkgs = not_installed_pkgs + 1 # check to see if specific version of pkg is not installed # and display if true check_pkg = fields[0] if check_pkg[0] in "<>": check_pkg = check_pkg[1:]; elif check_pkg[0] in "=": check_pkg = check_pkg[1:]; if (check_pkg == current_package.get_cpv()): if (not checking_package_mask): print_output(info,output.green("\n" + current_package.get_cpv() + ": ") + output.yellow("Not Installed"),current_package) stable_list.append(current_package.get_cpv()) else: print_output(info,output.green("\n" + current_package.get_cpv() + ": ") + output.yellow("Package Masked"),current_package) display_done = True continue keywords = "%s" % (get_keywords(current_package,"KEYWORDS").split()) if (keywords.find("Available/Deprecated") >= 0): continue pkgmask = _get_mask_status(current_package, False) #determine if installed package is unmasked, if so, display keywords as green stable = check_for_stable_release(current_package) if stable: ebuild_output = output.green("Installed: ") + \ output.turquoise(current_package.get_cpv()) + \ output.green(" Keywords " + keywords) stable_list.append(current_package.get_cpv()) else: if (not show_removable_only_flag): ebuild_output = output.green("Installed: ") + \ output.turquoise(current_package.get_cpv()) + \ output.yellow(" Keywords " + keywords) if (checking_package_unmask): if (not is_pkg_package_masked(current_package.get_cpv())): ebuild_output = ebuild_output + output.green(" Not package masked") stable_list.append(current_package.get_cpv()) ebuild_search_key_printed = False if stable: diffs_found = False ebuild_search_key_printed = True print_output(info,"\n" + ebuild_output,current_package) elif not show_changes_only_flag and not show_removable_only_flag: diffs_found = False ebuild_search_key_printed = True print_output(info,"\n" + ebuild_output,current_package) all_pkgs = gentoolkit.sort_package_list(gentoolkit.find_packages((current_package.get_category() + \ "/" + current_package.get_name()), True)) for a_package in all_pkgs: if not a_package.is_installed(): pkgmask = _get_mask_status(a_package, False) print_output(debug,output.blue("Checking package: " + a_package.get_cpv() +".pkgmask is " + str(pkgmask))) if (current_package.compare_version(a_package)) < 0: diffs_found = True keylist = a_package.get_env_var("KEYWORDS") keywords = "%s" % (filter_keywords(keylist)).split() #if a_package is masked if pkgmask > 0 and not show_removable_only_flag: if show_changes_only_flag and not ebuild_search_key_printed: print_output (info, "\n" + ebuild_output, current_package) ebuild_search_key_printed = True check_for_stable_release(current_package) if (pkgmask >= 3): print_output (info,output.red("Available: " + a_package.get_cpv() + " [M] Keywords: " + keywords),a_package) else: print_output (info,output.brown("Available: " + a_package.get_cpv() + " Keywords: " + keywords),a_package) else: if show_changes_only_flag and not ebuild_search_key_printed: print_output (info,"\n" + ebuild_output,current_package) ebuild_search_key_printed = True print_output(info,output.green("Available: " + a_package.get_cpv() + " Keywords: " + keywords),a_package) # display if pkg/cat is not installed (missing version) if not_installed_pkgs == pkg_length: if not display_done: if (not checking_package_mask): print_output(info,output.green("\n" + fields[0] + ": ") + output.yellow("Not Installed"),current_package) stable_list.append(current_package.get_cpv()) else: print_output (info,output.green("\n" + current_package.get_cpv() + ": ") + output.yellow("Package Masked"),current_package) else: diffs_found = True print output.red ("\nPackage: " + fields[0] + " not found. Please check " + filename + " to validate entry") show_all_versions(fields[0]) current_package = "" return diffs_found # adding support for etc/portage/package.keywords//package.keywords def get_recursive_info(filename): # determine if filename is a directory if os.path.isdir(filename): # get listing of directory filenames = os.listdir(filename) for file_name in filenames: get_recursive_info(filename+os.path.sep+file_name) else: get_info(filename) def get_info(filename): diffs_found = False no_file = False filedescriptor = None try: filedescriptor = open(filename) for line in filedescriptor.readlines(): line = line.strip(); if len(line) <= 0: continue elif line.find("#") >= 0: # found '#' remove comment line = line[0:line.find("#")] line = line.strip() if len(line) <= 0: continue diffs_found = parse_line(line, filename); except IOError: print output.red("Could not find file " + filename) no_file = True if not diffs_found and no_file: print output.brown("No ebuild options found.") # close file if (filedescriptor != None): filedescriptor.close() # parts blatantly stolen from equery # if pure is true, then get "true" mask status that is # not affected by entries in /etc/portage/package.* def _get_mask_status(pkg, pure): pkgmask = 0 if pkg.is_masked(): pkgmask = pkgmask + 3 if pure: keywords = portage.portdb.aux_get(pkg.get_cpv(), ["KEYWORDS"]) keywords = keywords[0].split() else: keywords = pkg.get_env_var("KEYWORDS").split() # first check for stable arch, stop there if it is found if gentoolkit.settings["ARCH"] in keywords: return 0 if "~" + gentoolkit.settings["ARCH"] in keywords: pkgmask = pkgmask + 1 elif "-*" in keywords or "-" + gentoolkit.settings["ARCH"] in keywords: pkgmask = pkgmask + 2 return pkgmask def is_pkg_package_masked(cpv): mysplit = catpkgsplit(cpv) if not mysplit: raise ValueError("invalid CPV: %s" % cpv) if not portage.portdb.cpv_exists(cpv): raise KeyError("CPV %s does not exist" % cpv) mycp = mysplit[0] + "/" + mysplit[1] if settings.pmaskdict.has_key(mycp): for package in settings.pmaskdict[mycp]: if cpv in portage.portdb.xmatch("match-all", package): return True return False # filter out keywords for archs other than the current one def filter_keywords(keywords): filtered_keywords = "" key_list = keywords.split() for key in key_list: key = string.replace(key, "[", "") key = string.replace(key, "]", "") key = string.replace(key, ",", "") if gentoolkit.settings["ARCH"] in key: if len(filtered_keywords) != 0: filtered_keywords = filtered_keywords + " " filtered_keywords = filtered_keywords + key elif "-*" in key: if len(filtered_keywords) != 0: filtered_keywords = filtered_keywords + " " filtered_keywords = filtered_keywords + key return filtered_keywords # check to see if we have a stable release # in our package.* files that we can remove def check_for_stable_release(pkg): if not pkg.is_masked(): status = _get_mask_status(pkg, True) if status == 0: return True return False #print version info def print_version(): # Print the version of this tool to the console. print __productname__ + "(" + __version__ + ") - " + \ __description__ print "Author(s): " + __author__ def unique(s): """Return a list of the elements in s, but without duplicates. For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3], unique("abcabc") some permutation of ["a", "b", "c"], and unique(([1, 2], [2, 3], [1, 2])) some permutation of [[2, 3], [1, 2]]. For best speed, all sequence elements should be hashable. Then unique() will usually work in linear time. If not possible, the sequence elements should enjoy a total ordering, and if list(s).sort() doesn't raise TypeError it's assumed that they do enjoy a total ordering. Then unique() will usually work in O(N*log2(N)) time. If that's not possible either, the sequence elements must support equality-testing. Then unique() will usually work in quadratic time. """ n = len(s) if n == 0: return [] # Try using a dict first, as that's the fastest and will usually # work. If it doesn't work, it will usually fail quickly, so it # usually doesn't cost much to *try* it. It requires that all the # sequence elements be hashable, and support equality comparison. u = {} try: for x in s: u[x] = 1 except TypeError: del u # move on to the next method else: return u.keys() # We can't hash all the elements. Second fastest is to sort, # which brings the equal elements together; then duplicates are # easy to weed out in a single pass. # NOTE: Python's list.sort() was designed to be efficient in the # presence of many duplicate elements. This isn't true of all # sort functions in all languages or libraries, so this approach # is more effective in Python than it may be elsewhere. try: t = list(s) t.sort() except TypeError: del t # move on to the next method else: assert n > 0 last = t[0] lasti = i = 1 while i < n: if t[i] != last: t[lasti] = last = t[i] lasti += 1 i += 1 return t[:lasti] # Brute force is all that's left. u = [] for x in s: if x not in u: u.append(x) return u #helper function to print avail pks when version does not exist def show_all_versions(pkg): # is package masked is_package_masked = False pkgArr = portage.pkgsplit(pkg) if pkgArr is None or len(pkgArr) == 0: return # determine if category/package is masked for package in pkgArr: operator = portage.get_operator(package) if operator is not None: package = package[len(operator):] # package is category/package and pkg is category/package-version # is category/package-version we are checking package masked? if portage.settings.pmaskdict.has_key(package): pkg_list = portage.settings.pmaskdict.get(package) # iterate through list array looking for pkg for pkg_check in pkg_list: operator = portage.get_operator(pkg_check) if operator is None: if pkg_check == package: is_package_masked = True all_pkgs = gentoolkit.sort_package_list(gentoolkit.find_packages(package, True)) for current_package in all_pkgs: keywords = "%s" % (current_package.get_env_var("KEYWORDS").split()) keywords = filter_keywords(keywords) keywords = "[" + keywords + "]" ebuild = current_package.get_ebuild_path() if ebuild: pkgmask = _get_mask_status(current_package, True) if is_package_masked: print output.red("Available: " + current_package.get_cpv() + " [M] Keywords: " + keywords) elif pkgmask > 4: print output.red("Available: " + current_package.get_cpv() + " [M] Keywords: " + keywords) elif pkgmask == 4 or pkgmask == 1: print output.brown("Available: " + current_package.get_cpv() + " Keywords: " + keywords) else: print output.green("Available: " + current_package.get_cpv() + " Keywords: " + keywords) stable_list.append(current_package.get_cpv()) def handle_if_overlay(package): overlay_text = "" global print_overlay_flag print_overlay_flag = True ebuild_path,overlay_path = porttree.dbapi.findname2(package._cpv) index = -1 try: index = overlay_list.index(overlay_path) except ValueError,error: overlay_list.append(overlay_path) index = overlay_list.index(overlay_path) overlay_text = " [" + str(index) + "]" return overlay_text # if the overlay_text was displayed to the user # we need to display the string at the end # this array will store the overlays to be displayed def print_overlay_text(): global print_overlay_flag if (not print_overlay_flag): return if (len(overlay_list) <= 1): return; index = 1 for x in overlay_list[1:]: print output.turquoise("[" + str(index) + "] ") + x index = index + 1 print "\n" #helper function to print output def print_output(log_level,output_string, package=None): global logLevel if package != None: if (package.is_overlay()): output_string = output_string + output.turquoise(handle_if_overlay(package)) if (log_level <= logLevel): print output_string # remove stabled files that are no longer needed from package.keywords # or package.mask # include support for etc/portage/package.keywords//package.keywords def cleanFile (filename): if ( len(stable_list) == 0): return removeDups = [] for i in stable_list: if not removeDups.count(i): removeDups.append(i) removedDict = {} try: # determine if filename is a directory if os.path.isdir(filename): # get listing of directory filenames = os.listdir(filename) for file_name in filenames: cleanFile(filename+os.path.sep+file_name) else: #go through stable array and remove line if found for line in fileinput.input(filename,inplace =1): for item in removeDups: line = line.strip() if not item in line: print line else: removedDict[filename] = item except OSError,error: print output.red("Modify/Read access to file: " + filename + " failed: ") ,error if (len(removedDict) > 0): for key in removedDict.keys(): print output.red("Removing from: ") + output.yellow(key) + ": " + output.green(removedDict[key]) + "\n" # main if __name__ == "__main__": if len(sys.argv) == 1: print_usage() sys.exit(1) # soooooo stolen from emerge tmpcmdline = sys.argv[1:] for cmd in tmpcmdline: if cmd[0:1] == "-" and cmd[1:2] != "-": for cmd_item in cmd[1:]: if mappings.has_key(cmd_item): if mappings[cmd_item] in cmdline: print print "*** Warning: Redundant use of ", mappings[cmd_item] else: cmdline.append(mappings[cmd_item]) else: print "!!! Error: -"+cmd_item+" is an invalid option." sys.exit(-1) else: cmdline.append(cmd) #parse long options for cmd in cmdline: if len(cmd)>=2 and cmd[0:2]=="--": try: i = options.index(cmd) continue except ValueError: print "!!! Error: -"+cmd+" is an invalid option." sys.exit(-1) if "--changes-only" in cmdline: cmdline.remove("--changes-only") show_changes_only_flag = True if "--removable-only" in cmdline: cmdline.remove("--removable-only") show_removable_only_flag = True if "--debug" in cmdline: logLevel = debug if "--version" in cmdline: print_version() sys.exit(0) if "--all" in cmdline: tmpcmdline = ["--all"] if "--fix" in cmdline: tmpcmdline.append("--fix") elif "--fixwithall" in cmdline: tmpcmdline.append("--fixwithall") cmdline=tmpcmdline if "--help" in cmdline: print_usage() sys.exit(0) if (show_changes_only_flag and show_removable_only_flag): print "Please select only one of --show-removable (-r) or --changes-only" print "Use --help for more info." sys.exit(0) for cmd in cmdline: if cmd == "--keyword": print output.bold("\npackage.keywords:") get_recursive_info(USER_CONFIG_PATH + "/package.keywords") if "--fix" in cmdline: cleanFile(USER_CONFIG_PATH + "/package.keywords") print output.bold("Done\n") elif cmd == "--unmask": print output.bold("\npackage.unmask:") checking_package_unmask = True get_recursive_info(USER_CONFIG_PATH + "/package.unmask") checking_package_unmask = False if "--fix" in cmdline: cleanFile(USER_CONFIG_PATH + "/package.unmask") elif cmd == "--mask": checking_package_mask = True print output.bold("\npackage.mask:") get_recursive_info(USER_CONFIG_PATH + "/package.mask") print output.bold("Done\n") checking_package_mask = False elif cmd == "--all": print output.bold("\npackage.keywords:") get_recursive_info(USER_CONFIG_PATH + "/package.keywords") print output.bold("\npackage.unmask:") checking_package_unmask = True get_recursive_info(USER_CONFIG_PATH + "/package.unmask") checking_package_unmask = False checking_package_mask = True print output.bold("\npackage.mask:") get_recursive_info(USER_CONFIG_PATH + "/package.mask") print output.bold("Done\n") if "--fix" in cmdline: cleanFile(USER_CONFIG_PATH + "/package.keywords") cleanFile(USER_CONFIG_PATH + "/package.unmask") print_overlay_text() if len(cmdline) == 0: if show_changes_only_flag or show_removable_only_flag: if (show_changes_only_flag): print output.green("-c") + " or " + output.green("--changes-only") + " must be accompanied by one of the following:" else: print output.green("-r") + " or " + output.green("--removable-only") + " must be accompanied by one of the following:" print " " + output.yellow("-k") + " or " + output.yellow("--keyword") print " " + output.yellow("-u") + " or " + output.yellow("--unmask") print " " + output.yellow("-m") + " or " + output.yellow("--mask") print " " + output.yellow("-a") + " or " + output.yellow("--all") print_usage()