#!/usr/bin/python # Command-line frontend for GARStow # Copyright 2003, 2004, 2005, 2009, 2010 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 cmd_remove_old(args = []): if len(args) != 0: die("usage: garstow remove-old") 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_all_clean(args): if len(args) != 0: die("usage: garstow all-clean") for n in get_package_names().keys(): 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 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? requested = set(args) if args == []: if upgrade: # Upgrade all installed packages by default. requested = set(get_installed().keys()) else: parser.error("no packages to install") # 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): if len(args) != 2: die("usage: garstow install-version ") install_version(args[0], args[1]) def cmd_remove(args): if len(args) == 0: die("usage: garstow remove ...") args.sort(cmp_remove) ips = get_installed() for p in args: if not ips.has_key(p): status(p, " is not installed") continue has_port = p in get_package_names() if has_port: reqs = get_reqs_rec(p) blockers = intersection(ips.keys(), reqs) if len(blockers) != 0: die("Cannot remove " + p + " since depending packages are still installed: " + (" ".join(blockers))) status("Removing ", p) remove_package(p) del ips[p] status("Done") def cmd_depgraph(args): deptree = get_dep_tree() if args == []: s = '' else: s = ' from ' + ' '.join(args) showdeps = {} for p in args: 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) if len(args) == 0: args = get_installed().keys() to_build = set() for package in args: 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): if len(args) != 0: die("usage: garstow clean-garballs") 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): print get_arch() def cmd_get_hash(args): if len(args) != 1: die("usage: garstow get-hash PACKAGE") print get_package_hash(args[0]) def cmd_write_metadata(args): if len(args) == 0: die("usage: garstow write-metadata PACKAGE ...") for package in args: write_package_vars(package) def cmd_config(args): print '''gar_dir="''' + gar_dir + '''" stow_dir="''' + get_stow_dir() + '''" packages_dir="''' + get_packages_dir() + '''"''' def cmd_setup(args): 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" commands = { "remove-old": cmd_remove_old, "all-clean": cmd_all_clean, "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, } 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:])