blob: a5d67ac3dd7284c06aba93dcedc8979e88979f8f [file] [log] [blame]
#!/usr/bin/python
# Copyright 1999-2007 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Id$
import errno, signal, stat, sys, os
from itertools import izip
def tar_contents(contents, root, tar, protect=None, onProgress=None):
from portage_util import normalize_path
import tarfile
root = normalize_path(root).rstrip(os.path.sep) + os.path.sep
id_strings = {}
maxval = len(contents)
curval = 0
if onProgress:
onProgress(maxval, 0)
paths = contents.keys()
paths.sort()
for path in paths:
curval += 1
try:
lst = os.lstat(path)
except OSError, e:
if e.errno != errno.ENOENT:
raise
del e
if onProgress:
onProgress(maxval, curval)
continue
contents_type = contents[path][0]
if path.startswith(root):
arcname = path[len(root):]
else:
raise ValueError("invalid root argument: '%s'" % root)
live_path = path
if 'dir' == contents_type and \
not stat.S_ISDIR(lst.st_mode) and \
os.path.isdir(live_path):
# Even though this was a directory in the original ${D}, it exists
# as a symlink to a directory in the live filesystem. It must be
# recorded as a real directory in the tar file to ensure that tar
# can properly extract it's children.
live_path = os.path.realpath(live_path)
tarinfo = tar.gettarinfo(live_path, arcname)
# store numbers instead of real names like tar's --numeric-owner
tarinfo.uname = id_strings.setdefault(tarinfo.uid, str(tarinfo.uid))
tarinfo.gname = id_strings.setdefault(tarinfo.gid, str(tarinfo.gid))
if stat.S_ISREG(lst.st_mode):
# break hardlinks due to bug #185305
tarinfo.type = tarfile.REGTYPE
if protect and protect(path):
# Create an empty file as a place holder in order to avoid
# potential collision-protect issues.
tarinfo.size = 0
tar.addfile(tarinfo)
else:
f = open(path)
try:
tar.addfile(tarinfo, f)
finally:
f.close()
else:
tar.addfile(tarinfo)
if onProgress:
onProgress(maxval, curval)
def quickpkg_main(options, args, eout):
from portage import catsplit, dblink, 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
import portage_exception
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()
binpkg_path = bintree.getname(cpv)
ensure_dirs(os.path.dirname(binpkg_path))
os.rename(binpkg_tmpfile, binpkg_path)
bintree.inject(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] <list of package atoms>"
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="<y|n>",
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="<y|n>",
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 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)