#!/usr/bin/python # Command-line frontend for GARStow # Copyright 2003, 2004, 2005, 2009, 2010, 2012 Adam Sampson import garstowlib.cache from garstowlib.config import * from garstowlib.garball import * from garstowlib.packages import * from garstowlib.utils import * import sys, stat, random, time, optparse def parse_package_list(args, default=[]): """Parse a list of packages specified on the command line, returning a sanitised list of package names (e.g. if the user passes "gnome/gtk", this'll return "gtk").""" if len(args) == 0: args = default pns = get_package_names() cps = get_package_names_rev() packages = [] for arg in args: if arg.endswith("/"): arg = arg[:-1] if arg == "@all": packages += pns.keys() elif arg == "@installed": packages += get_installed().keys() elif arg in pns: # A plain package name (gtk). packages.append(arg) elif arg in cps: # A package name with category (gnome/gtk). packages.append(cps[arg]) else: warn("Ignoring unrecognised package: ", arg) # Remove duplicate packages, keeping the first instance of each seen. unique_packages = [] seen = set() for package in packages: if not package in pns: warn("Ignoring nonexistant package: ", package) if not package in seen: unique_packages.append(package) seen.add(package) return unique_packages def cmd_remove_old(args = []): usage = "usage: %prog remove-old [options]" parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) != 0: parser.error("remove-old takes no arguments") dirs = set() for (name, st) in list_packagesdir().items(): if stat.S_ISDIR(st.st_mode): dirs.add(name) installed = get_installed() for name in installed.values(): dirs.remove(name) for name in dirs: status("Removing ", name) remove_versioned_package(name) def cmd_clean(args): usage = "usage: %prog clean [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) packages = parse_package_list(args, ["@all"]) for n in packages: d = get_source_dir(n) to_clean = ["work", "cookies"] for x in to_clean: if os.access(d + "/" + x, os.F_OK): make_operation(n, "clean") break def cmd_show(args): usage = "usage: %prog show [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) parser.add_option("-r", action="store_true", dest="runtime", help="show only runtime dependencies") parser.add_option("-i", action="store_true", dest="indirect", help="show indirect dependencies") options, args = parser.parse_args(args) for package in parse_package_list(args): print "Package: " + package print "Package version: " + get_version(package) iv = get_installed_version(package) if iv is None: print "Installed version: none" else: print "Installed version: " + iv print "Full name: " + get_package_names()[package] print "Description: " + get_description(package) build = not options.runtime print "Requires: " + (" ".join(get_dependencies(package, build=build))) if options.indirect: print "Requires indirectly: " + (" ".join(get_deps_rec(package, build=build))) print "Required by: " + (" ".join(get_requirements(package, build=build))) if options.indirect: print "Required indirectly by: " + (" ".join(get_reqs_rec(package, build=build))) if len(args) > 1: print def cmd_list(args): usage = "usage: %prog list [options]" parser = optparse.OptionParser(usage=usage) parser.add_option("-b", action="store_true", dest="brief", help="only list package names") options, args = parser.parse_args(args) if len(args) != 0: parser.error("list takes no arguments") pns = get_package_names() ips = get_installed() for package in sorted(ips.keys()): if options.brief: print package elif package in pns: print ips[package] + ": " + get_description(package) else: print package + ": port no longer exists" def cmd_install(args, upgrade=False): if upgrade: usage = "usage: %prog upgrade [options] [PACKAGE ...]" else: usage = "usage: %prog install [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) parser.add_option("-n", action="store_true", dest="dryrun", help="show what will be done, rather than doing it") parser.add_option("-f", action="store_true", dest="reinstall", help="force reinstallation of specified packages") parser.add_option("-k", action="store_true", dest="keep_going", help="keep going when some packages can't be installed") parser.add_option("-b", action="store_true", dest="use_garball", help="use binary packages rather than compiling") parser.add_option("-B", action="store_true", dest="install_build", help="install build dependencies (even for binary packages)") parser.add_option("-d", action="store_true", dest="developer", help="update package checksums (developer mode)") parser.add_option("-i", action="append", dest="ignore", metavar="PACKAGE", help="do not install/upgrade PACKAGE") parser.add_option("--older", type="int", dest="older", metavar="DAYS", help="install packages only if more than DAYS old") options, args = parser.parse_args(args) # Which packages did the user want to install or upgrade? if args == []: if upgrade: # Upgrade all installed packages by default. args = ["@installed"] else: parser.error("no packages to install") requested = set(parse_package_list(args)) # Figure out the set of packages that need to be checked for # up-to-dateness, and which should be removed. build = options.install_build or not options.use_garball to_check = set() to_remove = set() for package in requested: if not package in get_package_names(): status(package, ": no longer exists; will remove") to_remove.add(package) continue to_check.add(package) for dep in get_deps_rec(package, build=build): to_check.add(dep) # Check whether packages should be installed or upgraded. to_install = set() for package in to_check: if package in requested and options.reinstall: status(package, ": installation forced with -f") to_install.add(package) continue vars = get_package_vars(package) if vars is None: status(package, ": needs to be installed") to_install.add(package) continue old_hash = vars.get("GARHASH") new_hash = get_package_hash(package) if old_hash != new_hash: status(package, ": needs to be upgraded") to_install.add(package) # Remove packages that should be ignored. now = time.time() for package in list(to_install): if (options.ignore is not None) and (package in options.ignore): status(package, ": would install, but ignored with -i") to_install.remove(package) if package in get_ignore_list(): status(package, ": would install, but in IGNORE_DEPS") to_install.remove(package) if options.older is not None: older = options.older * 24 * 60 * 60 try: st = os.lstat(in_root(get_packages_dir() + "/" + package)) if st.st_mtime >= (now - older): status(package, ": would install, but installed recently") to_install.remove(package) except OSError: pass status("checked %d packages; %d to install, %d to remove" % (len(to_check), len(to_install), len(to_remove))) installed = set() failed = set() skipped = set() for package in sorted(to_install, cmp=cmp_install): dep_failed = False for dep in get_deps_rec(package, build=build): if dep in failed: status("Cannot install ", package, " because ", dep, " failed") dep_failed = True if dep_failed: skipped.add(package) continue status("Installing ", package) if options.dryrun: pass elif options.use_garball: install_garball(package) else: try: make_operation(package, "clean") if options.developer: make_operation(package, "makesum") make_operation(package, "build") make_operation(package, "check-deps") make_operation(package, "install") except CommandException, e: if options.keep_going: status("command failed: ", " ".join(e.cmd)) failed.add(package) continue else: raise e installed.add(package) for package in sorted(to_remove, cmp=cmp_install, reverse=True): status("Removing ", package) if not options.dryrun: remove_package(package) for package in failed: status("Package failed to build: ", package) status("installed %d packages; %d failed, %d skipped" % (len(installed), len(failed), len(skipped))) def cmd_upgrade(args): cmd_install(args, upgrade=True) def cmd_install_version(args): usage = "usage: %prog install-version [options] PACKAGE VERSION" parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) != 2: parser.error("install-version needs exactly two arguments") install_version(args[0], args[1]) def cmd_remove(args): usage = "usage: %prog remove [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) parser.add_option("-n", action="store_true", dest="dryrun", help="show what will be done, rather than doing it") options, args = parser.parse_args(args) if len(args) == 0: parser.error("no packages to remove") to_remove = parse_package_list(args) to_remove.sort(cmp_remove) installed = set(get_installed().keys()) removed = set() failed = set() for package in to_remove: if not package in installed: status(package, " is not installed") continue if package in get_package_names(): # The package has a port; check nothing that depends on # it is still installed. blockers = set(get_reqs_rec(package)).intersection(installed) if len(blockers) != 0: status("Cannot remove " + package + " since depending packages are still installed: " + (" ".join(blockers))) failed.add(package) continue status("Removing ", package) if not options.dryrun: remove_package(package) installed.remove(package) removed.add(package) for package in failed: status("Package could not be removed: ", package) status("removed %d packages; %d failed" % (len(removed), len(failed))) def cmd_depgraph(args): usage = "usage: %prog depgraph [options]" parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) != 0: parser.error("depgraph takes no arguments") deptree = get_dep_tree() if args == []: s = '' else: packages = parse_package_list(args) s = ' from ' + ' '.join(packages) showdeps = {} for p in packages: showdeps[p] = deptree[p] for d in get_deps_rec(p): showdeps[d] = deptree[d] deptree = showdeps # Trim the dependencies so that if x -> y -> z and also x -> z, then # the x -> z dependency is dropped. depd = {} cats = {} cat_colours = {} for (p, deps) in deptree.items(): depd[p] = set() for d in get_deps_rec(p): depd[p].add(d) cats[p] = get_categories(p)[0] cat_colours[cats[p]] = None for (p, deps) in deptree.items(): left = set() for d in deps: found = False for e in deps: if e != d and d in depd[e]: found = True if not found: left.add(d) deptree[p] = left cols = cat_colours.keys() num_cols = len(cols) for i in range(num_cols): cat_colours[cols[i]] = (1.0 / num_cols) * i print 'digraph "garstow deps' + s + '" {' print 'rankdir=LR;' for (p, deps) in deptree.items(): print '"%s" [shape=box,color=black,style=filled,fillcolor="%f,%f,%f"];' % (p, cat_colours[cats[p]], 0.2, 0.8) for d in deps: print '"%s" -> "%s" [color="%f,%f,%f"];' % (p, d, random.random(), 1.0, 0.5) print '}' def cmd_build_garball(args): usage = "usage: %prog build-garball [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) parser.add_option("-o", action="store_false", dest="recursive", default=True, help="don't build dependencies too") parser.add_option("-f", action="store_true", dest="force", help="build garballs even if already present") options, args = parser.parse_args(args) packages = parse_package_list(args, ["@installed"]) to_build = set() for package in packages: to_build.add(package) if options.recursive: for dep in get_deps_rec(package, build=False): to_build.add(dep) for package in sorted(to_build): output_fn = make_garball(package, options.force) if output_fn is None: print "%s already built" % package else: print "%s -> %s" % (package, output_fn) def cmd_clean_garballs(args): usage = "usage: %prog clean-garballs [options]" parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) != 0: parser.error("clean-garballs takes no arguments") for (dirpath, dirnames, filenames) in os.walk(get_garball_dir()): for fn in sorted(filenames): full_fn = "%s/%s" % (dirpath, fn) if is_garball_up_to_date(full_fn) is False: status("Removing ", full_fn) os.unlink(full_fn) def cmd_get_arch(args): usage = "usage: %prog get-arch [options]" parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) != 0: parser.error("get-arch takes no arguments") print get_arch() def cmd_get_hash(args): usage = "usage: %prog get-hash [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) == 0: parser.error("no packages specified") for package in parse_package_list(args): print get_package_hash(package) def cmd_write_metadata(args): usage = "usage: %prog write-metadata [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) == 0: parser.error("no packages specified") for package in parse_package_list(args): write_package_vars(package) def cmd_config(args): usage = "usage: %prog config [options]" parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) != 0: parser.error("config takes no arguments") print '''gar_dir="''' + gar_dir + '''" stow_dir="''' + get_stow_dir() + '''" packages_dir="''' + get_packages_dir() + '''"''' def cmd_setup(args): usage = "usage: %prog setup [options]" parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) != 0: parser.error("setup takes no arguments") stow_dir = get_stow_dir() print "PATH=%s/bin:%s/gar.scripts:$PATH;" % (stow_dir, gar_dir) print "LD_LIBRARY_PATH=%s/lib:$LD_LIBRARY_PATH;" % stow_dir print "export PATH LD_LIBRARY_PATH" def cmd_owner(args): usage = "usage: %prog owner [options] FILE ..." parser = optparse.OptionParser(usage=usage) options, args = parser.parse_args(args) if len(args) == 0: parser.error("no files specified") for filename in args: print get_owning_versioned(filename) def cmd_plan_removal(args): usage = "usage: %prog plan-removal [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) parser.add_option("-v", action="store_true", dest="verbose", help="show packages that are still used") options, args = parser.parse_args(args) # Build a list of packages and their dependencies in removal order. packages = parse_package_list(args) to_consider = list(packages) for package in packages: to_consider += get_deps_rec(package, build=True) to_consider = list(set(to_consider)) to_consider.sort(cmp_remove) pns = get_package_names() want_remove = set(args) can_remove = set() for package in to_consider: # Find who needs this (that we haven't already "removed"). reqs_left = set(get_requirements(package, build=True)) - can_remove - want_remove pn = pns[package] if len(reqs_left) == 0: can_remove.add(package) print "%s: can remove" % pn elif options.verbose or package in want_remove: print "%s: used by: %s" % (pn, " ".join(sorted(reqs_left))) if len(can_remove) > 0: print print "Could remove: " + " ".join(sorted([pns[package] for package in can_remove])) def cmd_unused(args): usage = "usage: %prog unused [options] PACKAGE ..." parser = optparse.OptionParser(usage=usage) parser.add_option("-v", action="store_true", dest="verbose", help="show packages that are still used") options, args = parser.parse_args(args) to_consider = parse_package_list(args, ["@all"]) for package in to_consider: reqs = get_requirements(package, build=True) pn = get_package_names()[package] if len(reqs) == 0: print "%s: unused" % pn elif options.verbose: print "%s: used by: %s" % (pn, " ".join(sorted(reqs))) commands = { "remove-old": cmd_remove_old, "clean": cmd_clean, "all-clean": cmd_clean, # for compatibility "show": cmd_show, "list": cmd_list, "install": cmd_install, "install-version": cmd_install_version, "upgrade": cmd_upgrade, "remove": cmd_remove, "depgraph": cmd_depgraph, "build-garball": cmd_build_garball, "clean-garballs": cmd_clean_garballs, "get-arch": cmd_get_arch, "get-hash": cmd_get_hash, "write-metadata": cmd_write_metadata, "config": cmd_config, "setup": cmd_setup, "owner": cmd_owner, "plan-removal": cmd_plan_removal, "unused": cmd_unused, } def main(args): usage = ("usage: %%prog [options] %s [command options ...]" % "|".join(commands.keys())) parser = optparse.OptionParser(usage=usage) parser.disable_interspersed_args() parser.add_option("--root", dest="root", metavar="DIR", help="specify root directory of system to operate on") options, args = parser.parse_args(args) if options.root is not None: set_root_dir(options.root) if args == []: parser.error("no command specified") cmd = args[0] if cmd not in commands: parser.error("unknown command") try: commands[cmd](args[1:]) except CommandException, e: die("command failed: " + " ".join(e.cmd)) finally: garstowlib.cache.save() if __name__ == "__main__": main(sys.argv[1:])