#!/usr/bin/python # Copyright 1999-2007 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Id$ import errno, signal, sys, os from itertools import izip def quickpkg_main(options, args, eout): from portage import catsplit, dep_expand, flatten, isvalidatom, xpak from portage.dep import use_reduce, paren_reduce from portage.util import ConfigProtect, ensure_dirs from portage.exception import InvalidData, InvalidDependString from portage.dbapi.vartree import dblink, tar_contents from portage.checksum import perform_md5 import tarfile import portage root = portage.settings["ROOT"] trees = portage.db[root] vartree = trees["vartree"] vardb = vartree.dbapi bintree = trees["bintree"] try: ensure_dirs(bintree.pkgdir) except portage.exception.PortageException: pass if not os.access(bintree.pkgdir, os.W_OK): eout.eerror("No write access to '%s'" % bintree.pkgdir) return errno.EACCES successes = [] missing = [] config_files_excluded = 0 include_config = options.include_config == "y" include_unmodified_config = options.include_unmodified_config == "y" fix_metadata_keys = ["PF", "CATEGORY"] for arg in args: try: atom = dep_expand(arg, mydb=vardb, settings=vartree.settings) except ValueError, e: # Multiple matches thrown from cpv_expand eout.eerror("Please use a more specific atom: %s" % \ " ".join(e.args[0])) del e missing.append(arg) continue except InvalidData, e: eout.eerror("Invalid atom: %s" % str(e)) del e missing.append(arg) continue if not isvalidatom(atom): eout.eerror("Invalid atom: %s" % atom) missing.append(arg) continue matches = vardb.match(atom) pkgs_for_arg = 0 for cpv in matches: excluded_config_files = [] bintree.prevent_collision(cpv) cat, pkg = catsplit(cpv) dblnk = dblink(cat, pkg, root, vartree.settings, treetype="vartree", vartree=vartree) dblnk.lockdb() try: if not dblnk.exists(): # unmerged by a concurrent process continue iuse, use, restrict = vardb.aux_get(cpv, ["IUSE","USE","RESTRICT"]) iuse = [ x.lstrip("+-") for x in iuse.split() ] use = use.split() try: restrict = flatten(use_reduce( paren_reduce(restrict), uselist=use)) except InvalidDependString, e: eout.eerror("Invalid RESTRICT metadata " + \ "for '%s': %s; skipping" % (cpv, str(e))) del e continue if "bindist" in iuse and "bindist" not in use: eout.ewarn("%s: package was emerged with USE=-bindist!" % cpv) eout.ewarn("%s: it may not be legal to redistribute this." % cpv) elif "bindist" in restrict: eout.ewarn("%s: package has RESTRICT=bindist!" % cpv) eout.ewarn("%s: it may not be legal to redistribute this." % cpv) eout.ebegin("Building package for %s" % cpv) pkgs_for_arg += 1 contents = dblnk.getcontents() protect = None if not include_config: confprot = ConfigProtect(root, portage.settings.get("CONFIG_PROTECT","").split(), portage.settings.get("CONFIG_PROTECT_MASK","").split()) def protect(filename): if not confprot.isprotected(filename): return False if include_unmodified_config: file_data = contents[filename] if file_data[0] == "obj": orig_md5 = file_data[2].lower() cur_md5 = perform_md5(filename, calc_prelink=1) if orig_md5 == cur_md5: return False excluded_config_files.append(filename) return True existing_metadata = dict(izip(fix_metadata_keys, vardb.aux_get(cpv, fix_metadata_keys))) category, pf = portage.catsplit(cpv) required_metadata = {} required_metadata["CATEGORY"] = category required_metadata["PF"] = pf update_metadata = {} for k, v in required_metadata.iteritems(): if v != existing_metadata[k]: update_metadata[k] = v if update_metadata: vardb.aux_update(cpv, update_metadata) xpdata = xpak.xpak(dblnk.dbdir) binpkg_tmpfile = os.path.join(bintree.pkgdir, cpv + ".tbz2." + str(os.getpid())) ensure_dirs(os.path.dirname(binpkg_tmpfile)) tar = tarfile.open(binpkg_tmpfile, "w:bz2") tar_contents(contents, root, tar, protect=protect) tar.close() xpak.tbz2(binpkg_tmpfile).recompose_mem(xpdata) finally: dblnk.unlockdb() bintree.inject(cpv, filename=binpkg_tmpfile) binpkg_path = bintree.getname(cpv) try: s = os.stat(binpkg_path) except OSError, e: # Sanity check, shouldn't happen normally. eout.eend(1) eout.eerror(str(e)) del e eout.eerror("Failed to create package: '%s'" % binpkg_path) else: eout.eend(0) successes.append((cpv, s.st_size)) config_files_excluded += len(excluded_config_files) for filename in excluded_config_files: eout.ewarn("Excluded config: '%s'" % filename) if not pkgs_for_arg: eout.eerror("Could not find anything " + \ "to match '%s'; skipping" % arg) missing.append(arg) if not successes: eout.eerror("No packages found") return 1 print eout.einfo("Packages now in '%s':" % bintree.pkgdir) import math units = {10:'K', 20:'M', 30:'G', 40:'T', 50:'P', 60:'E', 70:'Z', 80:'Y'} for cpv, size in successes: if not size: # avoid OverflowError in math.log() size_str = "0" else: power_of_2 = math.log(size, 2) power_of_2 = 10*int(power_of_2/10) unit = units.get(power_of_2) if unit: size = float(size)/(2**power_of_2) size_str = "%.1f" % size if len(size_str) > 4: # emulate `du -h`, don't show too many sig figs size_str = str(int(size)) size_str += unit else: size_str = str(size) eout.einfo("%s: %s" % (cpv, size_str)) if config_files_excluded: print eout.ewarn("Excluded config files: %d" % config_files_excluded) eout.ewarn("See --help if you would like to include config files.") if missing: print eout.ewarn("The following packages could not be found:") eout.ewarn(" ".join(missing)) return 2 return os.EX_OK if __name__ == "__main__": usage = "quickpkg [options] " from optparse import OptionParser parser = OptionParser(usage=usage) parser.add_option("--umask", default="0077", help="umask used during package creation (default is 0077)") parser.add_option("--ignore-default-opts", action="store_true", help="do not use the QUICKPKG_DEFAULT_OPTS environment variable") parser.add_option("--include-config", type="choice", choices=["y","n"], default="n", metavar="", help="include all files protected by CONFIG_PROTECT (as a security precaution, default is 'n')") parser.add_option("--include-unmodified-config", type="choice", choices=["y","n"], default="n", metavar="", help="include files protected by CONFIG_PROTECT that have not been modified since installation (as a security precaution, default is 'n')") options, args = parser.parse_args(sys.argv[1:]) if not options.ignore_default_opts: from portage import settings default_opts = settings.get("QUICKPKG_DEFAULT_OPTS","").split() options, args = parser.parse_args(default_opts + sys.argv[1:]) if not args: parser.error("no packages atoms given") try: umask = int(options.umask, 8) except ValueError: parser.error("invalid umask: %s" % options.umask) # We need to ensure a sane umask for the packages that will be created. old_umask = os.umask(umask) from portage.output import get_term_size, EOutput eout = EOutput() def sigwinch_handler(signum, frame): lines, eout.term_columns = get_term_size() signal.signal(signal.SIGWINCH, sigwinch_handler) try: retval = quickpkg_main(options, args, eout) finally: os.umask(old_umask) signal.signal(signal.SIGWINCH, signal.SIG_DFL) sys.exit(retval)